인턴으로 근무하며 레거시 코드를 개선한 경험을 작성해보려고 합니다.
여러가지를 개선 했지만 그 중 개발방식을 통합한 사례를 작성해보겠습니다.
시작하기 전에 최소한의 도메인 지식을 전달하고 시작하겠습니다.
여기서 도메인은 "레거시 코드"를 의미합니다. 그리고 레거시 코드는 보고서 기능을 구현합니다.
보고서의 특징은 다음과 같습니다.
특징을 나열했지만 가장 중요한 사실은 동일한 레이아웃을 다른 방식으로 구현해야 했다는 점입니다.
위의 그림처럼 말이죠. 이런 특징 때문에 문제들이 발생했습니다.
PDF와 HWP는 서로 다른 기술로 개발되고 있었습니다.
PDF 보고서는 kotlinx.html과 wkhtmltopdf를 사용해서 개발합니다.
HWP 보고서는 hwplib를 사용해서 개발합니다.
기술이 다르다 보니 개발 방식의 일관성이 없었습니다. 일관성이 없다보니 개발자가 헷갈리기 시작했습니다.
즉, 개발자의 생산성이 상당히 떨어졌습니다.
PDF와 HWP는 네이밍, 스타일 선언 방법이 상이했습니다.
PDF와 HWP의 코드 조각을 보며 설명하겠습니다.
초록색 동그라미는 테이블의 열을 정의하는 코드입니다.
변수의 이름을 살펴보면 TABLE_ROW
, tr
로 두 보고서가 상이한 모습입니다.
파란색 동그라미는 테이블의 셀을 정의하는 코드입니다.
마찬가지로 TABLE_CELL_NO_BORDER
, td
로 두 보고서가 상이합니다.
이것도 코드 조각을 보며 설명해보겠습니다.
문제는 왼쪽 즉, PDF에 있었는데요.
두 가지가 문제였습니다.
문자열 하드 코딩은 에러가 발생하기 쉽습니다. 예를들어, 아래 코드에서 에러를 찾아봅시다.
"width: 526px; height: 28px; margin-left: 10px" +
"font-family: NanumGothic; font-size: 9px;" +
"border-top: solid 2px #13264d; padding-top: 1px;
정답은 첫번째 줄 마지막에 있습니다. ;
이 빠져 있죠.
지금은 코드 조각만 봤기 때문에 상대적으로 찾기기가 쉽습니다.
그런데 코드의 수가 많아지고 작업 시간이 길어질 수록 에러를 찾아내기가 쉽지가 않았습니다.
PDF에서는 레이아웃 레이어에서 스타일 선언까지 책임을 졌습니다.
PDF 기술 특성상 심지어 레이아웃 레이어 외에도 파일 생성 레이어에서도 스타일을 선언할 수 있었습니다.
산발적으로 적용되는 스타일과 CSS의 cascade 특성 때문에 디버깅과 유지보수가 어려워졌습니다.
반면에 HWP는 상대적으로 양호했습니다.
두 가지가 지켜지고 있었습니다. 때문에 PDF의 개발 방식을 HWP에 맞추기로 했습니다.
그럼에도 불구하고 HWP에도 문제점은 있었습니다.
반복이 지나치게 많이 발생하고 있었습니다.
기획이 변동될 가능성을 염두해서 의도적으로 코드를 반복 했다는 사실을 파악 했지만, 감안하더라도 너무 많은 반복이었습니다.
반복은 마찬가지로 개발자의 시간을 앗아가는 존재입니다.
여담이지만 동아리 활동을 하면서 트레이드오프에 관한 토론을 많이 구경했었는데요.
어떤 트레이드 오프에 대해서 열띈 토론을 하면 결국 결론은 하나였습니다.
휴먼 리소스가 가장 비싸다.
가장 비싼 것을 지키기 위해서 개발 방식의 통합과 반복 제거가 시급했습니다.
리팩토링은 세 가지 목적을 가지고 진행되었습니다.
일관성을 확보하기 위해서 하드 코딩 규칙을 정해봤습니다.
하드코딩 규칙
1. 첫번째 줄엔 레이아웃의 크기 관련 설정만 넣자.
2. 둘째 줄엔 폰트 관련 설정만 넣자.
3. 셋째 줄엔 레이아웃 관련된 설정만 넣자.
규칙과 약속은 깨지라고 있는 것이죠. 규칙은 잘 지켜지지 않았습니다.
예외 상황이 계속 발생했습니다.
예외 상황들에 의해 결국 규칙은 지켜지지 않았습니다. 그리고 이런 생각이 들었습니다.
규칙을 만든 나도 지키지 못하는 규칙을 동료들이 지킬 수 있을까?
일관성 유지를 위해 다른 방법이 필요했습니다.
글로벌 상수를 정의하고 재사용함으로써 반복을 제거하고자 했습니다.
보고서 종이 사이즈, 폰트 등을 글로벌 상수로 선언하고 재사용했습니다.
글로벌 상수로 선언함으로써 장단점이 있었는데요.
PDF의 종이 사이즈는 문자열로 선언되고 있었습니다. 그리고 종이 사이즈 선언 레이어가 일관적이지 않았습니다.
그러다보니 작업을 할 때마다 정확한 수치를 알기 위해서 이전 코드를 참고 해야 했습니다.
글로벌 상수로 선언 후 재사용함으로써 이전 코드를 참고할 필요가 없어졌습니다.
즉, 반복이 제거되고 일관성을 확보할 수 있었습니다.
글로벌 상수의 접근 제어자는 public
이기 때문에 보고서 기능이 아닌 코드에서도 접근이 가능했습니다.
이는 곧 IDE 사용성 하락으로 이어졌습니다.
위의 사진처럼 비슷한 이름을 가진 변수들을 무자비하게 추천 받을 수 있었습니다.
가장 큰 문제는 글로벌 상수가 다른 동료들에게는 독이 될 수도 있다는 점이었습니다.
동료가 보고서 외의 로직을 개발하다가 IDE의 추천을 받고 글로벌 상수를 사용한다면 의존성이 발생합니다.
이는 의도치 않게 버그를 발생시키고 디버깅 하기도 어려울 것이라고 예상 했습니다.
장점보다 단점이 많으니 당연히 더 좋은 방법이 필요했습니다.
스타일 생성을 책임지는 유틸리티 클래스를 선언했습니다.
일단 코드부터 보겠습니다.
제가 정의한 PDF 유틸리티 클래스의 조각인데요. 위의 코드에서 알 수 있는 장점이 있습니다.
static class
로 선언이 되어 있어서 언제든지 사용이 가능합니다.private
변수들을 의존성 걱정 없이 마음껏 재사용 할 수 있습니다.public
변수들도 클래스를 통해서 접근해야 하기 때문에 약한 의존성으로 재사용할 수 있습니다.이제 HWP 유틸리티 클래스도 살펴볼까요?
코드의 형태만 다를 뿐 철학은 같습니다.
이제 유틸리티 클래스를 사용한 후와 이후를 비교하며 리팩토링 목적을 어떻게 달성했는지 살펴 보겠습니다.
유틸리티 클래스를 사용해서 리팩토링 목적을 어떻게 달성했는지 살펴 보겠습니다.
그림을 보며 설명해보겠습니다.
이제 PDF와 HWP 모두 열을 그릴 때는 TR
, 셀을 그릴 때는 TD
를 사용합니다.
이제는 헷갈리지 않겠죠?
모든 스타일은 너비(width)를 직접 설정해야 합니다. 그림을 살펴볼까요?
그림을 보면 PDF와 HWP 모두 너비만 설정하고 있습니다. 기존 방식에 비해 엄청나게 일관성이 생긴 모습입니다.
그렇지만 모든 스타일이 너비만 설정하면 되는 것은 아닙니다.
또 그림을 살펴보겠습니다.
HWP는 HwpStyleFactory
의 메서드에 너비만 넘겨주면 스타일 적용이 끝입니다.
반면에 PDF는 HtmlStyleFactory
의 변수를 호출하고 너비를 추가로 넘겨줘야 합니다.
CSS와 hwplib의 기술적 차이 때문에 일관성을 완전히 지키지는 못했습니다.
PDF와 HWP와의 일관성은 지키지 못했지만 PDF 보고서 끼리의 일관성은 잘 지켜진 모습입니다.
스타일 관련 로직을 유틸리티 클래스에 모두 위임함으로써 레이어를 분리할 수 있었습니다.
모든 스타일은 HtmlStyleFactory
, HwpStyleFactory
의 멤버 메서드, 변수를 호출해서 적용합니다.
더 이상 레이아웃 레이어에서 문자열 하드코딩 등 유지보수가 곤란한 상황을 연출하지 않습니다.
언제든지 유틸리티 클래스의 메서드, 변수를 호출해서 재사용하도록 변경했습니다.
불필요한 반복이 제거 됐습니다.
코드를 파일의 숫자로 따지자면 8개의 파일이 2개의 파일로 줄었습니다.
코드의 라인 수는 대략 1/4 정도로 줄었습니다.
유틸리티 클래스가 가지는 가장 큰 장점이라고 생각합니다.
이제 최소한의 시간을 투자해서 일정한 품질의 결과물을 도출해낼 수 있도록 개선했습니다.
개선 후 작업 시간이 4시간 -> 30분으로 약 8배 감소했습니다.
이번 리팩토링을 하면서 배운 것이 있습니다.
코드의 반복을 제거하는 것이 무조건 좋지 않다는 점 입니다.
예를들어, 레이아웃과 스타일 중 "지역" 이라는 레이아웃이 있다고 가정하겠습니다.
또, "지역" 레이아웃은 영상 보고서, 주간 보고서에서 재사용하고 있다고 가정하겠습니다.
이런 상황에서 만약 영상 보고서의 "지역" 만 수정하고 싶으면 어떻게 해야 할까요?
상당히 골치가 아파집니다.
"지역" 을 재사용하고 있는 곳이 영상, 주간 뿐만 아니라 영상, 일일, 프로젝트 등 모든 부분에서 사용하고 있었다면?
유틸리티 클래스 내부에 regionScene
, regionWeekly
, regionMonthly
, regionCommon
등이 하나씩 생깁니다.
그래서 재사용 단위 선정이 너무 중요했습니다.
재사용 선정 기준은 딱 2가지 였습니다.
재사용 선정 기준을 정하기 위해선 팀원들과의 의사소통이 중요했습니다.
회사는 정말 바빴습니다. 그리고 선배 개발자님은 회사보다 더 바빴습니다.
그래서 무언가를 질문하기가 쉽지 않았습니다. 항상 회의에 참여하시고 자리에 안계셨거든요.
그래서 대부분 혼자 결정해야 했는데 저는 경험도 부족하고 지식도 부족했습니다.
그래도 선배 개발자님을 최대한 활용하고 싶었습니다.
그래서 찰나를 포착해서 모든 내용을 질문해야 했습니다. 질문을 위해 했던 노력들을 나열해보겠습니다.
이런 노력 끝에 팀원들과 원활한 의사소통을 할 수 있었습니다.
주간 회의와 일간 회의가 있었습니다. 그 회의들을 적극적으로 활용했습니다.
PM님들께도 저의 작업 상황을 공유할 수 있었고, 선배 개발자님들께도 작업 상황을 공유할 수 있는 좋은 기회였습니다.
출근 직후 5분은 데일리 미팅을 준비하는 습관을 들였고, 스프린트 회의의 워크 아이템도 직접 추가하려고 노력했습니다.
돌아보니 의사 결정 과정에는 항상 동료들의 도움이 있었네요. 항상 감사한 마음입니다.
많이 아쉽습니다 ㅎㅎ 더 잘 할 수 있었을텐데 너무 아쉽습니다.
유틸리티 클래스 안에 모든 스타일을 집어넣다보니 클래스가 너무 커졌습니다.
심슨 아저씨와 같은 모습이네요 ㅎㅎ...
그렇지만 인턴 당시의 저는 최선을 다했습니다.
서버 개발자로 취업 했지만
이렇게 퍼블리셔 직군처럼 레이아웃을 직접 확인하기도 했고 우여곡절이 많았습니다.
전 무엇을 배웠을까요?
이 정도를 배우고 느낀 것 같습니다.
확실한 건 이번 사례를 계기로 정말 많이 성장했고 다음에 비슷한 문제를 마주한다면 더 좋은 방법으로 개선할 수 있다는 자신감이 생겼습니다.
마무리를 어떻게 해야 할지 모르겠지만 앞으로도 정진하겠습니다. 화이팅!