[리팩터링 2판] CHAPTER 02. 리팩터링 원칙 (1)
리팩터링 2판 책을 읽고 공부한 내용을 기록한다. 2판은 자바스크립트를 기반으로 예시를 제공하지만 저자 마틴 파울러는 리팩터링의 핵심 요소는 언어와 상관없이 동일하다고 이야기한다.
해당 기록에서는 책에서 다루는 예제 코드 대신 개인적으로 작성한 예제 코드를 사용한다. 즉, 책의 모든 내용과 순서를 그대로 담지 않을 수 있다.
각 장/절 별로 작성한 코드는 깃허브 개인 저장소에서 관리한다.
불릿 기호는 책의 내용 중 의미있다고 판단한 것을 옮겨 적은 것이다.
2장의 2.1절부터 2.5절까지 학습한 내용을 기록한다. 리팩터링 원칙에 대한 저자의 생각 중 공감되는 부분과 나의 의견을 함께 작성한다.
2.1 리팩터링 정의
- 리팩터링: [명사] 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
- 리팩터링(하다): [동사] 소프트웨어의 겉보기 동작은 그대로 유지한 채, 여러 가지 리팩터링 기법을 적용해서 소프트웨어를 재구성하다.
- 누군가 “리팩터링하다가 코드가 깨져서 며칠이나 고생했다” 라고 한다면, 십중팔구 리팩터링한 것이 아니다.
리팩터링은 기본적으로 작업 전과 후의 코드가 동일하게 동작해야 함을 전제로 한다. 종종 리팩터링 작업 후, 기존의 기능이 정상적으로 동작하지 않는 경우를 발견한다. 리팩터링 정의에 따르면 이 경우는 리팩터링을 수행했다고 볼 수 없다.
2.2 두 개의 모자
- 나는 소프트웨어를 개발할 때 목적이 ‘기능 추가’ 냐, 아니면 ‘리팩터링’ 이냐를 명확히 구분해 작업한다.
한 사례가 떠오른다. 동료들이 특정 풀 리퀘스트에 대해 적극적으로 논의 중이었다. 논제를 살펴보니 해당 풀 리퀘스트의 크기가 너무 크고, 크기가 커지게 된 이유가 새로운 기능을 개발하면서 리팩토링을 함께 수행했기 때문으로 밝혀졌다. 변경 크기가 작거나 범위가 좁다면 기능 개발과 리팩터링을 함께 작업하고 하나의 풀 리퀘스트에 담는 것은 충분히 가능하다. 하지만 당시의 문제는,
- 리뷰하기에 풀 리퀘스트의 크기가 너무 큼
- 작성자는 리팩터링과 해당 기능 개발을 분리할 수 없다고 주장
두 가지로 요약할 수 있다. 여기서는 2번의 이유가 중요한데, 2.1절의 리팩터링 정의에 따라 성립될 수 없다. 리팩터링은 기존 기능/동작은 동일하게 유지하면서 재구성하는 기법이기에 새로운 기능을 추가하는 것과 분리될 수 있어야 한다. 해당 경험 이후 나 역시, 작업을 수행할 때 혹은 작업에 대해 동료들에게 설명할 때 해당 작업의 목적이 새로운 기능인지 아니면 구조의 개선인지를 명확히 하려 한다.
2.3 리팩터링하는 이유
- 리팩터링하면 소프트웨어 설계가 좋아진다: 규칙적인 리팩터링은 코드의 구조를 지탱해줄 것이다.
- 리팩터링하면 소프트웨어를 이해하기 쉬워진다: 내 소스 코드를 컴퓨터만 사용하는게 아니다. 예컨대 몇 달이 지나 누군가 내 코드를 수정하고자 읽게 될 수 있다. 사실 프로그래밍에서는 사람이 가장 중요하지만 소홀하기 쉽다.
- 리팩터링하면 버그를 쉽게 찾을 수 있다: 프로그램의 구조를 명확하게 다듬으면 그냥 ‘이럴 것이다’ 라고 가정하던 점들이 분명히 드러나는데, 버그를 지나치려야 지나칠 수 없을 정도까지 명확해진다.
- 리팩터링하면 프로그래밍 속도를 높일 수 있다: 내부 설계가 잘 된 소프트웨어는 새로운 기능을 추가할 지점과 어떻게 고칠지를 쉽게 찾을 수 있다.
처음부터 완벽한 요구사항이란 없고, 즉 처음부터 완벽한 설계도 존재하기 어렵다. 일정 수준 이상의 설계 품질을 유지하면서 지속적인 변화를 잘 반영하기 위한 도구 중 하나로 리팩터링을 바라볼 필요가 있다.
2.4 언제 리팩터링해야 할까?
- 준비를 위한 리팩터링 ‘기능을 쉽게 추가하게 만들기’: 리팩터링하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전이다. 구조를 살짝 바꾸면 다른 작업을 하기가 훨씬 쉬워질 만한 부분을 찾는다.
- 이해를 위한 리팩터링 ‘코드를 이해하기 쉽게 만들기’: 코드를 분석할 때 리팩터링을 해보면, 그렇지 않았더라면 도달하지 못했을 더 깊은 수준까지 이해하게 된다.
- 쓰레기 줍기 리팩터링: 코드를 파악하던 중에 일을 비효율적으로 처리하는 모습을 발견할 때가 있다.
- 계획된 리팩터링과 수시로 하는 리팩터링: 개발에 들어가기 전에 리팩터링 일정을 따로 잡아두지 않고, 기능을 추가하거나 버그를 잡는 동안 리팩터링도 함께 한다. 프로그래밍 과정에 자연스럽게 녹인 것이다.
- 오래걸리는 리팩터링: 주어진 문제를 작게 조금씩 해결해갈 수 있는 리팩터링 기법을 활용한다. 리팩터링이 코드를 깨트리지 않는다는 장점을 활용하는 것이다.
- 코드 리뷰에 리팩터링 활용하기: 코드 작성자가 참석해야 맥락을 설명해줄 수 있고 작성자도 리뷰어의 변경 의도를 제대로 이해할 수 있으므로, 이왕이면 참석자가 참석하는 방식이 좋다.
요약하자면, 리팩터링을 거대한 계획처럼 실행하기 보다는 작업 과정 중 하나로 자연스럽게 습관화하는 것이 좋겠다. 새로운 기능을 개발하기 전 직접적으로 관련된 코드를 개선하거나, 관련 코드를 분석하다가 개선의 여지가 많은 코드를 변경하거나 하는 관점이겠다. 간혹 우리는 다른 기능 추가를 중단하면서까지 리팩터링 자체를 큰 계획으로 잡는 경우가 있다. 그 계획이 기대처럼 달성되지 않는 경우, 해결되지 않은 복잡한 코드 덩어리와 낭비된 시간이라는 결과만 남길 가능성이 크다. 즉, 리팩터링이 필요한 부분을 작게 쪼개서 수행할 수 있는 방법을 찾으려는 노력이 필요하다. 그렇다면 자연스럽게 별도의 계획이 아닌, 작업 과정 중의 일부로 수행할 수 있게 된다.
2.5 리팩터링 시 고려할 문제
- 리팩터링의 궁극적인 목적은 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것이다.
- 오로지 경제적인 이유로 하는 것이다. 리팩터링은 개발 기간을 단축하고자 하는 것이다.
- 자가 테스트 코드는 리팩터링을 할 수 있게 해줄 뿐만 아니라, 새 기능 추가도 훨씬 안전하게 진행할 수 있도록 도와준다.
- 레거시 시스템을 파악할 때 리팩터링이 굉장히 도움된다. … 대규모 레거시 시스템을 테스트 코드 없이 명료하게 리팩터링하기는 어렵다. 이 문제의 정답은 당연히 테스트 보강이다.
중요한 것은 ‘리팩터링은 결국 개발 기간을 단축하는 효과를 만든다.’ 그리고 ‘테스트 코드 마련은 리팩터링의 효율성과 안정성을 향상시킨다.’