Skip to main content

2. 리팩토링 원칙

2.1 리팩토링 정의#

  • 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
  • 소프트웨어의 겉보기 동작은 그대로 유지한 채, 여러가지 리팩터링 기법을 적용해서 소프트웨어를 재구성하는 방법

즉, 리팩토링을 하다가 코드가 깨져서 고생을 했다면 리팩토링을 한 것이 아닙니다. 코드 베이스를 정리하거나 구조를 바꾸는 모든 작업을 재구성이라는 포괄적인 용어로 표현하고, 리팩터링재구성 중 특수한 형태로 볼 수 있습니다.

리팩터링은 성능 최적화와 비슷한 부분이 있습니다. 둘 다 코드를 변경하고 전반적인 기능을 유지합니다. 다만 목적이 다릅니다. 리팩터링은 코드를 이해하고 수정하기 쉽게 만드는 것이고, 성능 최적화는 오로지 속도 개선에만 신경을 씁니다.


2.2 두 개의 모자#

소프트웨어를 개발할 때 목적이 기능 추가인지 리팩터링인지를 명확하게 구분해서 작업해야합니다. 기능 추가를 하는 경우에는 기존 코드를 절대 건드리지 않고 새 기능을 추가해야하고, 리팩터링은 기능 추가를 절대하지 않고 오로지 코드 재구성에만 전념해야합니다.


2.3 리팩터링의 이유#

리팩터링은 모든 소프트웨어의 문제를 해결하는 것은 아니지만, 코드를 건강하게 유지하게 도와주는 약입니다. 다음의 장점이 있습니다.

리팩터링하면 소프트웨어 설계가 좋아집니다.#

  • 리팩터링을 하지 않으면 코드가 썩기 쉽습니다.
  • 규칙적인 리팩터링은 코드의 구조를 지탱해줍니다.
  • 대표적인 예시로 중복 코드를 제거하면 모든 코드가 언제나 고유한 일을 수행함을 보장할 수 있습니다.

리팩터링하면 소프트웨어를 이해하기 쉬워집니다.#

  • 컴퓨터에게 시키려는 일과 이를 표현하는 코드의 차이를 줄이는 것이 필요합니다.
  • 리팩터링을 통해 코드를 이해하기 쉽게 바꾸면 나와 다른 사람 모두에게 좋습니다.

리팩터링하면 버그를 쉽게 찾을 수 있습니다.#

  • 코드를 이해하기 쉽다는 말은 버그를 찾기도 쉽다는 의미입니다.
  • 프로그램 구조를 명확하게 다듬으면 그냥 이럴 것이라고 가정한 점이 드러나게 되고, 이를 지나칠 수 없을 정도로 명확해집니다.

리팩터링하면 프로그래밍 속도를 높일 수 있습니다.#

  • 리팩터링을 통해 코드 개발 속도를 높일 수 있습니다.
  • 내부 설계와 가독성이 개선되고 버그가 줄어들면서 모든 품질 향상에 직결됩니다.
  • 좋은 설계는 나쁜 설계와 달리 시간이 갈수록 기능 구현에서 장점을 가집니다.

2.4 리팩토링의 시점#

작성자의 일반적으로 리팩토링의 시점은 다음과 같습니다.

3의 법칙#

  • 처음에는 그냥합니다.
  • 비슷한 일을 두번째로 하게 되면, 일단 진행합니다.
  • 비슷한 일을 세번째 하게 되면 리팩터링을 합니다.

준비를 위한 리팩터링 : 기능을 쉽게 추가하게 만들기#

  • 리팩터링하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전입니다.
  • 현재 코드를 살펴보며, 구조를 살짝 바꾸면 다른 작업을 하기가 훨씬 쉬어질 만한 부분을 찾습니다.
  • 반복적인 코드가 발생하면 이를 처리하기 위해 함수 매개변수화 등을 적용합니다.
  • 이를 통해 버그 발생 가능성이 줄어듭니다.

이해를 위한 리팩터링 : 코드를 이해하기 쉽게 만들기#

  • 코드를 수정하려면 먼저 그 코드가 하는 일을 파악해야합니다.
  • 자잘한 세부 코드에 이해를 위한 리팩터링을 하는 것이 좋습니다.
  • 코드를 분석할 때 리팩터링을 하면 깊은 이해를 얻을 수 있습니다.

쓰레기 줍기 리팩터링#

  • 코드를 파악하는 중에 일을 비효율적으로 처리하는 모습을 발견할 때가 있습니다.
  • 간단히 수정할 수 있는 것은 즉시 고치고, 시간이 좀 걸리는 일은 짧은 메모만 남긴 다음 하던 일을 끝내고 나서 처리합니다.
  • 리팩터링의 멋진 점은 각각의 작은 단계가 코드를 깨뜨리지 않습니다.

계획된 리팩터링과 수시로 하는 리팩터링#

  • 앞서 나온 준비를 위한 리팩터링, 이해를 위한 리팩터링, 쓰레기 줍기 리팩터링 모두 기화가 될 때만 지행합니다.
  • 이외의 기능 추가를 하거나 버그를 잡는 동안 수시로 리팩터링을 진행합니다.
  • 보기 싫은 코드를 발견하면 리팩터링하고, 잘 작성된 코드 역시 수많은 리팩터링을 거쳐야합니다.
  • 무언가 수정하려 할 때는 먼저 수정하기 쉽게 정돈하고 그런 다음에 수정해야합니다.

오래 걸리는 리팩터링#

  • 대부분의 리팩터링은 몇 분 안에 끝납니다. 하지만, 팀 전체가 달려들어도 몇 주는 걸리는 대규모 리팩터링이 있습니다.
  • 다만, 팀 전체가 리팩터링을 하는 것은 좋지 않습니다. 일부의 사람이 조금씩 해결해가는 편이 효과적일 때가 많습니다.
  • 리팩터링은 코드를 깨뜨리지 않는 장점이 있기 때문에 항상 올바르게 동작합니다.

코드 리뷰에 리팩터링 활용하기#

  • 코드 리뷰를 정기적으로 수행하는 조직이 있습니다.
  • 코드 리뷰는 개발팀 전체에 지식을 전파하는 데 좋습니다.
  • 리팩터링은 다른 이의 코드를 리뷰하는 데 도움이 됩니다.
  • 리팩터링은 코드 리뷰의 결과를 더 구체적으로 도출하는 데에 도움이 됩니다.
  • 이러한 방법 죽 대표적인 예시가 짝 프로그래밍(pair programming)입니다.

리팩터링을 하면 안되는 경우.#

  • 지저분한 코드를 발견해도 수정할 필요가 없다면 리팩터링을 하지 않습니다.
  • 리팩터링하는 것보다 처음부터 새로 작성하기 쉬울 때는 리팩터링을 하지 않습니다. (다만, 이를 판단하기는 어렵습니다.)

2.5 리팩터링 시 고려할 문제#

리팩터링은 여러 문제가 생기고 어떻게 대처할 지에 대해 알아야합니다.

새 기능 개발 속도 저하#

  • 리팩터링의 목표는 궁극적인 목적은 개발 속도를 높이는 데 있습니다. 이를 통해 더 적은 노력으로 더 많은 가치를 창출하는 것이 중요합니다.
  • 다만 리팩터링을 하는 것도 상황에 맞게 조율해야합니다. 기능 추가와 리팩터링 중 무엇을 먼저할지 고민이 될 수 있는데 대부분의 경우에는 리팩터링을 먼저 하는 것이 좋습니다.
  • 리팩터링은 클린코드나 바람직한 엔지니어링 습관과 다릅니다. 즉, 코드 베이스를 이쁘게 하는 것이 목표가 아니라 경제적 이유로 하는 것입니다. 개발 기간을 줄이고, 기능 추가 시간과 버그 수정 시간을 줄이는 것이 중요합니다.

코드 소유권#

  • 코드 소유권을 작은 단위로 나눠 관리하면 리팩터링에 일부 방해가 됩니다. (다만 할수는 있습니다.)

브랜치#

흔히 사용되는 팀 단위 작업 방식은 팀원 마다 코드 베이스의 브랜치를 하나씩 맡아서 작업하다가 결과물이 어느 정도 쌓이면 마스터 브랜치에 통합해서 다른 팀원과 공유하는 것입니다. 하지만 이는 단점이 존재합니다.

  • 독립 브랜치 작업 기간이 길어질수록 작업 결과를 마스터로 통합하기 어렵습니다.
  • 함수 호출과 함수명 변경이 다른 브랜치에서 합치는 경우에는 프로그램이 동작하지 않게 됩니다.

이를 해결하는 방법은 지속적 통합(CI, Continuous Integration) 또는 트렁크 기반 개발(TBD, Trunk-Based Development)입니다.

  • 모든 팀원이 하루에 최소 한 번은 마스터와 통합합니다.
  • 마스터를 항상 건강하게 유지해야하고, 기능을 잘개 쪼개는 방법, 각 기능을 끌 수 있는 기능 플래그를 적용해야합니다.
  • 리팩터링과 궁합이 아주 좋습니다.
  • 이를 합친 것은 익스트림 프로그래밍(XP, eXtreme Programming)이라고 합니다.

테스팅#

  • 리팩터링의 두드러진 특징은 프로그램의 겉보기 동작은 똑같이 유지되는 것입니다.
  • 이때 핵심은 오류를 빠르게 잡는 것이 중요합니다.
  • 자동 리팩터링 기능을 활용한다면 테스트를 안해도 안전합니다.

레거시 코드#

  • 레거시 코드는 일반적으로 매우 복잡하고 테스트도 제대로 갖춰지지 않은 경우가 많습니다.
  • 레거시 시스템을 파악할 때 리팩터링은 굉장히 도움이 됩니다.
  • 이때 좋은 방법 중 하나는 테스트 보강입니다.
  • 다만 테스트만으로 레거시 코드를 아름다운 코드로 바꿀 수 없으므로, 조금씩 자주 개선하는 작업이 필요합니다.

데이터베이스#

  • 커다란 변경들을 쉽게 조합하고 다룰 수 있는 데이터 마이그레이션 스크립트를 작성하고, 접근 코드와 데이터베이스 스키마에 대한 구조적 변경을 스크립트로 처리하게끔 통합하는 데 있습니다.
  • 데이터베이스를 다른 버전으로 이전할 때마다, 현재 버전에서 원하는 버전 사이에 있는 모든 마이그레이션 스크립트를 실행합니다.
  • 전체 변경 과정을 작고 독립된 단계로 쪼개는 것이 핵심이며, 특히 이를 단계별로 릴리스 하는 것이 좋습니다. (되돌리기 매우 쉽습니다.)

2.6 리팩토링, 아키텍처, 애그니(YAGNI)#

  • 리팩터링은 소프트웨어 아키텍처를 바라보는 관점을 바꾸어 놓았습니다.
  • 아키텍처 면으로 보게 되면, 실질적인 효과는 요구사항 변화에 자연스럽게 대응하도록 코드 베이스를 잘 설계 해줍니다.
  • 향후 변경에 유연하게 대처할 수 있는 유연성 메커니즘을 소프트웨어에 심어두는 방법도 있습니다. 다만, 이 경우는 리팩터링을 미뤄서 나중에 훨씬 힘들어진다는 확신이 들때만 적용하는 것이 좋습니다.
  • 매개 변수를 추가해야할 시점이 오면 함수 매개변수화를 사용합니다.

이런 설계를 간결한 설계(simple design), 점진적 설계(incremental design), YAGNI(에그니, you aren't going to need it) 등으로 부릅니다. 이는 아키텍처와 설계를 개발 프로세스에 녹이는 방식이며, 리팩터링을 통해서야만 사용됩니다.

이러한 설계는 더 발전해 진화형 아키텍처로 변경되게 되었습니다. 진화형 아키텍처는 시간을 두고 반복해 내릴 수 있다는 장점을 활용하는 패턴과 실천법을 추구합니다.


2.7 리팩토링과 소프트웨어 개발 프로세스#

XP의 두드러진 특징은 지속적 통합, 자가 테스트 코드, 리팩터링 등의 개성이 강하며 상호 의존하는 기법들을 하나로 묶은 프로세스입니다. 이때 자가 테스트 코드와 리팩터링을 묶어 테스트 주도 개발(TDD, Test-Driven Development) 라고 합니다.

리팩터링의 토대는 다음과 같습니다.

  1. 자가 테스트 코드
  2. 지속적 통합
  3. 리팩터링

위 세가지 방법을 통해 YAGNI 설계 방식으로 개발을 진행할 수 있습니다. 리팩터링과 YAGNI는 서로에게 긍정적인 영향을 줍니다. 이를 통해 요구사항 변화에 재빠르게 대응하고 안정적인 선순환 구조를 코드베이스에 심을 수 있습니다.


2.8 리팩터링과 성능#

리팩터링을 하면 프로그램 성능이 느려질까봐 걱정하는 사람이 많습니다. 직관적인 설계와 성능은 중요한 주제이나, 대부분의 상황에서는 직관적인 설계가 중요합니다. 그 이유는 직관적인 설계가 이후 성능 튜닝에 더 쉬운 장점을 들고 있기 때문입니다.

빠른 소프트웨어를 작성하는 방법은 크게 세가지를 예시로 들 수 있습니다.

  • 시간 예산 분배(time budgeting)
    • 설계를 여러 컴포넌트로 나눠 컴포넌트마다 자원과 예산을 할당합니다.
  • 끊임 없이 관심을 기울이는 것
    • 성능을 개선하기 위한 최적화가 프로그램 전반에 퍼지나 다만, 컴파일러나 하드웨어 동작을 이해하지 못하는 경우도 많습니다.
  • 성능 최적화에 돌입전까지는 성능에 신경을 쓰지 않고, 성능 최적화 단계가 되면 프로그램을 튜닝합니다.
    • 대부분의 시간과 공간을 많이 잡아먹는 지점은 한 곳입니다.
    • 성능 튜닝에 투입할 시간을 벌 수 있습니다.
    • 리팩터링이 잘 되어 있는 프로그램은 성능을 더 세밀하게 분석할 수 있습니다.

2.9 리팩터링의 유래#

(생략)


2.10 리팩터링 자동화#

  • 좋은 IDE는 자동 리팩터링을 제공합니다. (Intellij나 이클립스)

참고 자료#

추천 책 자료#

  • 디자인 패턴, GoF
  • 리팩터링 워크북
  • 패턴을 활용한 리팩터링
  • 리팩터링 데이터베이스
  • 레거시 코드 활용 전략
  • Refactoring: Ruby Edition
Last updated on