Peter Cho

레거시 코드에서 이해하기 쉬운코드로 리팩토링

2019.05.26 | 6 Minute Read

글의 목적

한번쯤은 다른 누군가에게 전해받은 레거시 코드를 받아 프로젝트를 진행해야 되는 경험이 있었을 것이다. 나는 2018년 10월부터 i.kakao.com으로 서비스 되고 있는 카카오 i 오픈빌더 리팩토링를 이어 받았다.

프로젝트는 초기 빌딩 시 프런트 전문가들이 투입되지 않아 개선점이 많이 필요했다. 현재는 프런트 인력이 추가되어 리팩토링이 진행될 예정인데, 그 과정에 앞서 지금까지 진행했던 내용을 공유할 필요성이 있어 정리한 자료이다.

목차

2018.10~11 오픈빌더 투입

초기에 오픈빌더는 서버 개발자로 구성된 조직에서 프런트를 담당해서 진행했다. 12월에 2.0으로 오픈하는 시점에 프로젝트의 대대적인 개선이 필요하여 프런트 개발자가 투입되었다. 기술스택으론 Angular, TypeScript, RxJs를 사용하고 있다.

기존 1.0의 문제점

기존 1.0 구조에서 해결이 시급한 것은 3가지가 있었다.

첫번째는 협업의 장벽이다. 웹사이트의 통합 CSS를 전달해주는 협력업체와 협업의 장벽이 있었다. 컴포넌트별로 각각에 스타일 파일을 사용되어 CSS가 관리가 되지 않고 사이드 이펙트도 무수히 많이 발생하고 있었다.

두번째는 비구조화이다. Angular 구조인 컴포넌트, 서비스, 모듈, 라우팅이 일관성 없이 배치되어 구조화가 필요했다.

세번째는 중복코드가 많다. 각 컴포넌트별로 사용되는 중복코드가 무수히 많았다. 중복 코드가 파편화되어 동일한 기능을 어디서 사용되는 지 파악하기도 힘들었다.

기존 1.0의 문제점 해결


첫번째, 협업의 장벽은 컴포넌트별 각각 스타일 파일을 삭제하고, 협력업체에서 전달되는 파일을 바로 적용할 수 있도록 변경했다. 초기에는 서비스의 스타일 코드와 협력업체의 스타일 코드가 싱크가 맞지 않아 둘중 어느 코드가 가장최신 스타일 코드를 구분하기 힘들었다.

하지만 지속적으로 커뮤니케이션을 진행하고 스타일 코드를 한곳으로 옮겨 최신코드를 유지하게 되었고 전달받은 스타일 산출을 적용하는 비용을 최대한 줄일 수 있었다.


두번째, 비구조화는 Angular의 보편적으로 사용되는 폴더구조를 따르게 수정했다. URL Path 기준으로 Feature Module로 정의하고 Lazy Loading을 적용했다. 그리고 공통 모듈인 Core/Shared Module을 정의했다. Core Module는 앱 전역에서 싱글톤으로 정의되는 요소를 정의하고, Shared Module을 Feature Module에서 반복적으로 사용되는 요소를 정의한다.

세번째, 중복코드는 컴포넌트화와 Shared Module를 분리하는 작업을 했고, 코드레벨에서는 수동루프를 map, filter, reduce, find등을 사용하여 추상화를 높이는 작업을 했다.

// Not Cool
const ages = [];
for (let i = 0; i < users.length; i++) {
  ages.push(users[i].age);
}
// Cool
const ages = users.map(({age}) => age)

2018.12 코드 리팩토링 시작

코드들이 수정이 용이하지 않거나 오픈소스의 변경이 용이하지 않는 날이 오게되면 프로젝트는 갈아 엎어야 되는 지경이 온다. 오픈빌더 코드가 몇년이 지나도 수정이 용이하고 오픈소스의 버전업와 마이그레이션이 용이한 구조를 구성해서 롱런하는 프로젝트를 만들기 위해 시작하였다.

리팩토링 과정

첫번째는 미사용 코드를 삭제하는 것이다. 미사용 코드의 종류는 아래와 같은 코드들이 있었다.

  • 코드를 주석 처리해서 사용하지 않는 코드
  • IDE에서 Unused method/field/specifier로 표시해주는 코드

이런 미사용코드들이 생기는 이유는 나중에 사용하려고 하거나 스펙아웃된 코드들이 잔류한 코드이다. 미사용 코드를 삭제하고 LOC를 측정해보니 프로젝트 코드의 10분의 1이 미사용코드였다.

두번째는 외부라이브러리에 결합도가 강한코드들을 정리했다. 많은 파일들에 외부 의존성이 생기면 나중에 수정하기 힘들뿐더러 중복코드 발생된다.

예를들어 Material 라이브러리의 MatSnackBarthis.snackBar.open(Component, null, {duration: 3000})과 같은 형태를 가진다. 여기서 두번째와 세번째 인자는 모든 파일에 동일한 옵션으로 사용하고 있다.

인자순서, 옵션형태, 옵션값의 변경이 필요하면 무수히 많은 파일들을 수정하려면 쉽게 수정이 불가능할것이다.


해결방법은 외부라이브러리의 진입점을 담당하는 TooltipService를 만들어 해당 서비스를 통해서만 진입하는 것이다.

세번째는 단방향 데이터 흐름을 만드는 것이다. 현재는 컴포넌트상태/뮤테이션/액션들의 모두 정의되어 있고, 서비스API 서버와 통신하는 역할만 한다. 컴포넌트는 뷰를 담당하기 때문에 변경이 자주일어나고 뷰의 액션과 데이터를 전달하는 역할만해도 충분하다고 생각한다.


그래서 서비스상태/뮤테이션/액션들을 정의하고 컴포넌트서비스에 상태/액션을 요청하는 것으로 수정한다.

네번째는 역할이 2개이상 정의된 파일들을 정리한다. 여기서 말하는 역할은 Class와 Interface로 정의된 사용자형을 이야기한다. 사용자형이 한 파일에 2개이상 정의되었다는 것은 응집도이 낮다는 의미임으로 각 역할별로 파일을 별도로 존재하게 수정하였다.

마지막은 라인의 길이가 긴 메소드를 정리한다. 긴 메소드는 일반적으로 행위가 두개 이상이 정의될 가능성이 크다. 메소드 정리 기법인 메소드 추출, 메소드 내용 직접 삽입, 알고리즘 전환 등을 사용하여 해결했다.

2019.01 오픈빌더 기술적부채상환 전략

프로젝트가 롱런가능한 프로젝트를 만들기 위해 리팩토링을 일부 진행했다. 하지만 물리적인 한계가 있어서 여전히 기술적부채가 남아 있는 상황이다. 그래서 다른 형태로 진행할 필요성이 생겼다.

기술적부채의 해결은 보이스카우트 원칙을 우선시하는 것이다. 보이스카우트 원칙이란 자기가 머물렀던 자리를 떠날 때는 자기가 왔을 때 보다 깨끗이 치워야 한다는 규칙이다. 처음 코드를 작성한 사람이 누구인지와는 상관없이, 조금씩이라도 코드를 개선해야 한다.

Code Climate 도구의 기술적 부채 분석 포인트 10가지

Code Climate라는 GitHub와 연동하여 코드 전반적으로 기술적 부채를 분석해주는 기능이 있다. 여기서 사용되는 분석 포인트 10가지가 있다.

  1. 인자 갯수 : 많은 수의 인자로 정의된 메소드 또는 함수
  2. 복잡한 불리언 로직 : 이해하기 힘든 불리언 로직
  3. 파일 길이 : 한파일에 과도한 라인의 코드
  4. 동일한 코드 블록 : 형식이 다를 수 있으나 구문적으로 동일한 중복 코드
  5. 메소드 수 : 많은 수의 함수 또는 메소드로 정의 된 클래스
  6. 메서드 길이 : 단일 함수 또는 메서드 내에서 코드가 지나치게 많음
  7. 중첩 제어 흐름 : if 또는 case와 같이 깊게 중첩 된 제어 구조
  8. return 문 : 많은 수의 return 문을 사용하는 함수 또는 메서드
  9. 유사한 코드 블록 : 동일한 변수를 다루지만 동일한 구조를 공유하는 중복 코드
  10. 메소드 복잡성 : 이해하기 어려울 수 있는 함수 또는 메소드

2019.02 설정 페이지 리팩토링

설정 페이지를 구성하는 컴포넌트 하나에 모든 마크업, 상태관리, 이벤트 처리를 모두 기입되어 요구사항이 늘어날수록 코드베이스가 굉장히 많이 증가를 하게 되었다.

시작할 때는 볼륨이 작았는 데, 이제는 리팩토링을 하지 않으면 요구사항을 받아들이는 데 시간이 많이 소비되고 수정하기 힘들거라 예상되어 리팩토링을 바로 진행했다.

설정 리팩토링 과정

  1. 설정 페이지의 전용 서비스SettingService를 만들었다.
  2. 컴포넌트에서 공유할 상태를 추출하여 서비스로 이동했다.
  3. 우선적으로 필요한 기능부터 자식 컴포넌트로 분리했다.
  4. 다른 페이지에서도 사용되는 기능은 Core, Shared ServiceUtils로 이동하여 중복코드를 최소화했다.

2019.05 응답형식 구조 리팩토링


응답형식에서는 출력플랫폼에 따라 카카오톡과 카카오미니가 있고, 카카오톡은 응답하는 방법에 따라 6가지(이미지, 텍스트, 리스트, 커머스, 카드, 뮤직)가 있다. 이러한 많은 역할을 응답형식 컴포넌트에서 담당을 하고 있었다.

한 컴포넌트에서 많은 역할을 담당하게 되면 코드의 볼륨은 커지고 많은 정보를 알고 있어야 한다. 컴포넌트를 최대한 분리하여 마지막 순간까지 결정을 미루어 가장 적합한 컴포넌트가 최선의 결정을 내릴 수 있게 하였다.

컴포넌트 리팩토링

응답형식 컴포넌트의 개선 포인트는 4가지가 있다.

첫번째는 응답형식의 출력플랫폼에 따라 분리 필요하다. 카카오톡카카오미니에서 출력하는 형태를 분리할 필요가 있다.

두번째는 응답형식 그룹 분리가 필요하다. 카카오톡 응답형식은 최대 3개의 그룹에서 다른 형식의 카드들이 출력된다. 응답형식을 그룹핑하는 역할을 분리할 필요가 있다.

세번째는 응답카드 관리 분리가 필요하다. 응답카드는 6가지의 타입이 존재한다. 각 타입에 따라 컴포넌트를 결정하는 기능이 필요한다.

마지막으로 네이밍 컨벤션 정돈이다. 컴포넌트 네이밍 컨벤션이 위배되는 부분과 의미를 명확히 알 수 없는 컴포넌트의 네이밍을 변경한다.

AS IS

ResponseComponent의 역할이 많아 알아야할 내용들이 많았다.

ResponseComponent-AS IS

TO BE

ResponseComponent의 역할들을 최대한 분리하여 각 컴포넌트의 역할과 책임을 최소화하였다.

ResponseComponent-TO BE

폴더 리팩토링

컴포넌트 리팩토링을 하게 되면 역할과 책임이 분리된다. 폴더 구조도 컴포넌트와 동일한 구조를 가질 수 있도록 수정했다.

폴더 리팩토링

요약

  • 협업장벽 해결
  • 보이스카우트 원칙으로 기술적부채상환
  • 폴더 구조와 컴포넌트 구조 싱크
  • 비구조화에서 구조화
  • 단일 책임 원칙
  • 외부 의존성 코드를 진입점 한곳에서 관리
  • 의사결정 최대한 미뤄 역할과 책임 최적화
  • 중복코드 해결 전략 수립
  • 미사용 코드 제거
  • 네이밍 컨벤션 위반 부분 수정

참고자료

  • 리팩토링 - 마틴 파울러
  • 클린코드 - 로버트 C. 마틴
  • 읽기 좋은 코드가 좋은 코드다 - 더스틴 보즈웰, 트레버 파우커
  • 프로그래밍의 정석 - 우에다 이사오