Back to the Basics

[REFACTORING - 2] CH02 리랙터링의 원칙 정리 본문

Books & Reviews

[REFACTORING - 2] CH02 리랙터링의 원칙 정리

9Jaeng 2025. 3. 2. 22:41
728x90

들어가며

1장에서는 리팩터링이 무엇인지 감을 잡기 위한 것이었다면, 2장은 리팩터링의 정의와 이유, 시기, 그리고 리팩터링 시 고려해야 할 다양한 측면들을 다룬다.

2장 요약

2.1 리팩터링의 정의

코드를 정리하거나 구조를 바꾸는 작업을 재구성(restructuring)이라고 하며, 리팩터링은 이러한 재구성의 한 형태다. 리팩터링의 핵심 목적은 코드를 더 이해하기 쉽고 수정하기 쉽게 만드는 것이다. 구체적으로 리팩터링은 "기본 동작은 그대로 유지한 채, 여러 기법들을 사용해서 소프트웨어를 재구성하는 것"을 의미한다.

리팩터링은 작은 단계들을 통해 코드를 점진적으로 수정하고, 이러한 단계들이 연결되어 궁극적으로 큰 변화를 만들어낸다. 가장 중요한 점은 코드의 구조는 변경하지만, 프로그램의 전반적인 기능은 그대로 유지해야 한다는 것이다. 리팩터링으로 인해 기능 변경이 발생해서는 안 된다.

2.3 리팩터링을 하는 이유

많은 개발자들이 코드에 기능을 추가하다 보면 리팩터링을 하고 싶은 욕구를 느낄 것 이라 생각한다. 책에서는 소프트웨어 개발 과정에서 기능 추가와 리팩터링을 목적에 맞게 명확하게 구분해야 한다고 강조한다. 리팩터링을 진행할 때는 테스트 코드조차 추가하지 않는다.

리팩터링의 필요성은 대부분의 개발자가 인식하고 있다고 생각한다. 지속적인 리팩터링은 소프트웨어 설계를 개선하고 코드를 이해하기 쉽게 만든다. 코드가 읽기 쉬워지면 버그도 더 빠르게 발견할 수 있고 유지보수도 용이해진다. 코드를 쉽게 파악할 수 있게 되면 작업 속도 또한 자연스럽게 향상된다.

2.4 리팩터링은 언제 해야할까?

이 책에서는 리팩터링을 세 가지 유형으로 분류한다:

  1. 준비를 위한 리팩터링: 기능 추가 직전에 작업을 더 쉽게 만들기 위해 수행하는 리팩터링
  2. 이해를 위한 리팩터링: 코드를 더 이해하기 쉽게 만들기 위한 리팩터링
  3. 쓰레기 줍기 리팩터링: 비효율적인 로직을 발견했을 때 수행하는 리팩터링

이러한 리팩터링은 모두 기회가 될 때마다, 즉 코드를 작성할 때마다 자연스럽게 진행하는 것이 좋다. 리팩터링을 한 이유와 배경을 알 수 있도록 작업 과정에서 지속적으로 수행하는 것이 중요하다. 또한 수시로 리팩터링을 함으로써 더욱 효율으로 작업할 수 있다. 예를 들면, 새 기능을 추가할 때는 코드를 수정하여 기능 추가가 쉽도록 만드는 것이 가장 효율적인 방법이다.

리팩터링에 소홀하여 별도로 시간을 내어 계획된 리팩터링을 진행할 수도 있지만, 책에서는 이 방법을 크게 추천하지 않는다. 코드에 대한 리팩터링의 필요성은 코드를 작성하는 시점에 가장 명확하기 때문이다.

팀 전체가 참여해야 하는 대규모 리팩터링이 필요한 경우도 있지만, 한 번에 모든 것을 수정하기보다는 해당 코드 관련 작업을 할 때마다 점진적으로 개선해 나가는 방법을 추천한다.

2.5 리팩터링 시 고려할 문제

리팩터링을 하면서 발생할 수 있는 대표적인 문제들은 다음과 같다:

새 기능 개발 속도 저하

리팩터링은 얼핏 보면 새 기능 개발 속도를 저하시킨다는 인상을 줄 수 있다. 그러나 책에서는 "리팩터링의 궁극적인 목적은 개발 속도를 높여서 더 적은 노력으로 더 많은 가치를 창출하는 것"이라고 설명한다. 기능을 추가하기 쉽게 코드를 만든 후 기능을 추가하는 것이 장기적으로는 더 빠른 방법이기 때문이다.

코드 소유권과 브랜치

코드 소유권이 명확하게 나뉘어 있으면 리팩터링에 방해가 될 수 있다. 내가 소유하지 않은 코드를 리팩터링할 때 다른 부분에 영향을 주지 않도록 하는 복잡성이 발생한다. 이를 해결하기 위해 코드 소유권을 팀 단위로 두고, 팀 구성원 모두가 코드를 수정할 수 있는 느슨한 방식을 채택할 수 있다.

지속적 통합(Continuous Integration) 또는 트렁크 기반 개발(Trunk-Based Development)을 도입하여 기능별 브랜치와 마스터 브랜치가 통합되는 기간을 최소화하는 것도 좋은 방법이다. 이러한 방식에서는 구성원들이 하루에 최소 한 번은 마스터와 통합하도록 하여 브랜치 간 차이가 크게 벌어지지 않도록 한다. 이를 통해 병합의 복잡도를 낮추고, 코드베이스 전반에 걸친 리팩터링으로 인한 충돌을 최소화할 수 있다. 여기서 통합이란, 마스터를 개인 브랜치로 pull 하고 작업 결과를 마스터에 올리는 push 하는 작업을 의미한다.

( 팀마다 다르겠지만 master가 아닌 코드 통합 및 테스트의 기본 브랜치(예를 들면 develop)일 것이라 생각한다 )

테스팅

리팩터링의 핵심은 리팩터링 전후의 기능 동작이 변하지 않아야 한다는 것이다. 이를 보장하려면 자동화된 테스트 코드가 필수적이다. 테스트 코드는 리팩터링 과정에서 기능 변경 여부를 확인할 수 있는 중요한 도구가 된다. 또한 지속적 통합 과정에서 활용하여 코드 통합 시 발생할 수 있는 충돌을 효과적으로 식별할 수 있다.

요즘엔 개발을 진행하면서 중요한 비즈니스 로직에는 테스트 코드를 추가하고 있다. 이를 통해 코드를 수정할 때 다른 코드와의 충돌 여부나 중요한 비즈니스 로직이 의도치 않게 변경되지 않았는지 확인할 수 있어 매우 유용하다.

레거시 코드

대규모 레거시 시스템을 테스트 코드 없이 리팩터링하는 것은 매우 어렵다. 이런 경우에는 테스트 보강이 가장 효과적인 접근법이다. 서로 관련된 부분끼리 나누어 하나씩 체계적으로 개선해 나가는 것이 중요하다.

2.6 아키텍처와 YAGNI

리팩터링이 아키텍처에 미치는 가장 큰 효과는 요구사항 변화에 따라 자연스럽게 대응할 수 있도록 코드를 설계해 준다는 점이다.

실제로 소프트웨어를 사용해 보고 나서야 무엇이 적합한지 알게 되는 경우가 대부분이다. 범용성을 고려한 유연성 메커니즘을 미리 구현할 수도 있지만, 이는 실제 요구사항 변화에 따라 달라지는 경우가 많다. 리팩터링은 미리 추측하지 않고 현재까지 파악한 요구사항만을 해결하도록 코드를 구축하도록 한다. 개발을 진행하면서 사용자의 요구사항을 더 깊이 이해하게 되면 그에 맞게 아키텍처도 리팩터링하여 변경한다.

YAGNI(You Aren't Going To Need It)는 "당장 필요한 기능만으로 최대한 간결하게 만들라"는 원칙을 의미한다. 이는 나중에 문제를 더 깊이 이해하게 되었을 때 처리하는 진화형 아키텍처의 핵심 개념이기도 하다.

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

  • 테스트 코드와 지속적 통합, 리팩터링을 함께 적용하면 YAGNI 설계 방식으로 효과적인 개발을 진행할 수 있다.
  • 불확실한 추측에 기반한 유연성 메커니즘보다는 단순한 시스템이 변경하기가 훨씬 쉽다.
  • 이 세 가지 실천 방법(테스트, 지속적 통합, 리팩터링)을 조화롭게 활용하면 요구사항 변화에 신속하게 대응할 수 있다.
  • 지속적 배포는 소프트웨어를 언제든 릴리스할 수 있는 상태로 유지해 준다. 이를 통해 비즈니스 요구에 맞춰 릴리스 일정을 유연하게 계획할 수 있다.

2.8 리팩터링과 성능

  • 리팩터링의 접근 방식은 코드를 튜닝하기 쉽게 만들고, 그 후에 원하는 속도를 달성할 수 있도록 최적화하는 것이다.
  • 리팩터링은 궁극적으로 성능이 좋은 소프트웨어를 만드는 데 기여한다.

마치며

이번 장에서는 리팩터링 전반에 적용되는 다양한 원칙들을 살펴보았다. 리팩터링의 정의와 목적, 적용 시기, 그리고 리팩터링 시 고려해야 할 요소들, 소프트웨어 아키텍처와의 관계, 성능과의 관계 등을 종합적으로 다루었다.

특히 인상 깊었던 부분은 "당장 필요한 기능만으로 최대한 간결하게"라는 의미의 YAGNI 원칙과 이를 통한 진화형 아키텍처 접근법이었다. 최근 진행 중인 레거시 프로젝트 마이그레이션에서 새로운 아키텍처 구성에 대해 고민하던 중이었는데, 책에서 언급한 것처럼 한 번에 모든 요구사항을 예측하는 것은 불가능하며, 모든 상황을 고려한 유연성 메커니즘을 구현하려다 보면 리팩터링 범위가 커질 수 있다는 점을 실감했다.

현재 작업 중인 레거시 코드는 새로운 기능이 필요할 때마다 기존 구조를 고려하지 않고 계속 코드를 추가하다 보니 복잡해진 코드가 많이 있다. 테스트 코드도 없는 상황에서 점진적으로 테스트를 추가하며 리팩터링과 마이그레이션을 진행하고 있는데, 이 과정에서 테스트 코드와 리팩터링의 중요성을 더욱 실감하고 있다. 물론 일정을 맞추기 위해 속도가 중요할 때도 있어 항상 트레이드오프를 고려해야 한다.

이번 장을 통해 특별히 기억하고 싶은 세 가지 핵심 원칙은 다음과 같다:

  1. 테스트 코드를 작성하자
  2. 수시로 리팩터링을 하자
  3. 당장 필요한 기능만으로 최대한 간결하게 개발하자
728x90
Comments