리팩토링2판
- 리팩토링 2판 읽고나서 계속 기억하고싶은 핵심 메시지만 카드형식으로 정리
- 한줄평 : 왜 리팩토링이 중요하고, 왜 이렇게 코드가 작성되어야만 하는지에 대해서 알고싶으시다면 이 책을 추천합니다!
리팩토링 절차
- 결과적으로 코드가 절대 망가지지 않는 상태를 유지하면서, 작은 단계들을 하나씩 조합하여 실질적이고 큰 변화를 만들어내는 것이 빠르고 안전한 리팩토링 절차 입니다
리팩토링 절차 1 : 견고한 테스트 스위트 구축
- 리팩토링을 본격적으로 시작하기 전, 스스로 검증 가능한(self-checking) 탄탄한 테스트 스위트를 마련해야 합니다
- 테스트는 리팩토링 과정에서 발생할 수 있는 실수를 잡아내는 버그 탐지기 역할을 하여 코드를 안전하게 변경할 수 있도록 보호해 줍니다
리팩토링 절차 2 : 아주 작은 단계로 변경
- 코드를 한 번에 크게 수정하지 않고, 아주 작고 관리 가능한 단계로 나누어 변경해야 합니다
- 변경 단위가 작을수록. 실수를 하더라도 오류를 파악하고 수정하기가 훨씬 쉬워집니다
리팩토링 절차 3 : 각 변경 직후 즉시 테스트
- 코드를 조금 수정할 때마다 즉시 컴파일하고 테스트를 실행해야 합니다
- 리팩토링에서 가장 중요한 것은 "테스트, 작은 변경, 테스트, 작은 변경"이라는 리듬을 유지하는 것입니다
리팩토링 절차 4 : 자주 커밋하기
- 테스트를 통과한 성공적인 리팩토링 직후에는 로컬 버전 관리 시스템에 변경 사항을 커밋하는 것이 좋습니다
- 이렇게 하면 이후에 실수를 하더라도 언제든 정상 작동하던 상태로 쉽게 되돌릴 수 있습니다
리팩토링의 장점 소프트웨어 설계 개선 및 유지: 코드는 단기적인 목표나 설계에 대한 완전한 이해 없이 변경되면서 점점 구조를 잃고 부패하기 쉽습니다. 리팩토링은 코드의 형태를 정돈하고 중복을 제거하여 훌륭한 내부 설계(아키텍처)를 유지하도록 돕습니다 . 코드 이해도 향상: 코드를 작성할 때 미래에 코드를 읽고 수정할 다른 개발자(혹은 미래의 자신)를 배려하게 됩니다. 코드가 정확히 무슨 의도를 가지고 있는지 명확하게 전달되도록 구조를 개선하여, 시스템 파악에 드는 시간을 획기적으로 줄여줍니다 . 숨은 버그 발견: 코드를 리팩토링하면서 프로그램의 구조와 자신이 세운 가정을 깊이 이해하게 되고, 이 과정에서 코드에 숨어있던 버그를 쉽게 발견할 수 있습니다 . 전체적인 개발 속도 향상: 단기적으로는 시간이 걸릴지 몰라도, 좋은 설계를 유지하면 버그를 찾고 중복 코드를 파악하는 데 드는 시간을 줄여주어 궁극적으로 새로운 기능을 추가하거나 버그를 수정하는 속도가 훨씬 빨라집니다
리팩토링의 단점 및 한계 단기적인 성능 저하 가능성: 코드를 이해하기 쉽게 잘게 나누고 구조를 변경하는 과정에서 프로그램의 실행 속도가 단기적으로 느려질 수 있습니다. 다만, 잘 구조화된 코드는 추후 성능 병목을 찾아 최적화(튜닝)하기 훨씬 쉽습니다 . 잘못된 접근 시 버그 유발 위험: 체계적이고 통제된 방식이 아니라 비공식적으로(ad hoc) 코드를 파헤치며 변경할 경우, 오히려 작동하던 코드에 미묘하고 새로운 버그를 유발하는 위험이 따릅니다 . 막대한 시간 소요(대규모 리팩토링의 경우): 복잡하게 얽힌 상속 구조를 풀거나 절차적 코드를 객체지향으로 바꾸는 등의 대규모 리팩토링은 몇 달 혹은 몇 년이 걸릴 수도 있습니다 . 리팩토링의 장애물 및 개발자가 꺼리는 이유 오버헤드 및 일정 지연이라는 인식: 많은 개발자와 관리자들이 리팩토링을 '수익을 창출하는 새 기능을 개발하는 대신 수행하는 불필요한 오버헤드'나 '새 기능 추가를 지연시키는 작업'으로 오해합니다 . 기존 프로그램을 망가뜨릴 것에 대한 두려움: 변경으로 인해 잘 돌아가던 기존 코드가 깨질 수 있다는 두려움이 리팩토링을 주저하게 만듭니다 . 장기적인 보상 구조: 리팩토링의 주요 이점은 미래의 유지보수성에 있는데, 개발자 자신이 프로젝트에 끝까지 남아 그 이점을 누릴지 확신할 수 없다면 동기부여가 떨어질 수 있습니다 . 테스트가 없는 레거시 코드: 테스트 스위트(안전망)가 없는 방대한 레거시 시스템은 리팩토링 도중 발생한 오류를 즉시 잡아낼 수 없기 때문에 리팩토링하기 매우 위험하고 까다롭습니다 . 코드 소유권과 공개된 인터페이스(Published Interfaces): 다른 팀이 소유한 코드이거나 외부에 이미 공개되어 사용 중인 API의 경우, 클라이언트의 코드를 깨뜨리지 않고서는 인터페이스(함수명 등)를 마음대로 변경하기 어렵습니다 . 데이터베이스와의 강한 결합: 비즈니스 애플리케이션이 데이터베이스 스키마와 강하게 결합되어 있는 경우, 객체 모델을 변경할 때 데이터 마이그레이션까지 함께 수행해야 하므로 리팩토링의 큰 장애물이 됩니다 . 기능 브랜치(Feature Branches)의 남용: 여러 개발자가 긴 수명의 독립적인 브랜치에서 작업할 경우, 리팩토링으로 인한 코드 베이스 전반의 작은 변경들이 병합될 때 심각한 의미론적 충돌(semantic merge conflicts)을 일으키기 쉽습니다 . 도구 지원 부족: (역사적으로) 자동화된 리팩토링 도구가 부족한 환경에서는 리팩토링이 번거롭고 시간이 오래 걸리는 작업이 되어 방해 요소가 되었습니다 .
리팩토링의 의미와 목적 그리고 실질적인 실천전략 의미: 소프트웨어의 외부 동작(기능)은 변경하지 않으면서 이해하기 쉽고 수정하기 편하도록 내부 구조를 개선하는 과정입니다 . 버그 발생 가능성을 최소화하며 코드를 체계적으로 정리하는 훈련된 방법입니다 . 목적: 소프트웨어의 내부 설계(아키텍처)를 건강하게 유지하고, 코드의 가독성을 높이며, 숨은 버그를 쉽게 발견하게 하여 궁극적으로 전체적인 개발 속도를 크게 향상시키는 데 있습니다 . 두 개의 모자 메타포 (The Two Hats): 개발할 때 '새로운 기능 추가'와 '리팩토링'을 명확히 구분해야 합니다. 리팩토링할 때는 새로운 기능을 추가하지 않고 오직 코드 구조만 개선해야 합니다 . 3의 법칙 (The Rule of Three): 비슷한 작업을 세 번째 반복하게 될 때 리팩토링을 수행해야 합니다 . 기회주의적 리팩토링: 별도의 시간을 내서 하기보다는 새로운 기능을 추가하기 직전(준비 리팩토링), 코드를 이해하려고 할 때(이해 리팩토링), 혹은 코드의 더러운 부분을 지나칠 때 쓰레기를 줍듯 즉시 수행하는 것이 가장 좋습니다 . 리팩터링 프로세스와 일반 원칙 아주 작은 단계와 빈번한 테스트: 코드를 한 번에 크게 수정하지 않고, 의미 있는 아주 작은 단위로 변경하며 매 단계 직후에 자동화된 테스트를 실행해야 합니다 . 안전망 구축: 리팩토링을 시작하기 전, 스스로 검증 가능한(self-checking) 탄탄한 테스트 스위트가 마련되어 있는지 확인하는 것이 가장 첫 번째 단계입니다 . 안전한 후퇴 (Backtrack): 변경 후 코드가 통제 불능 상태가 되거나 테스트가 실패하여 원인을 즉시 찾을 수 없다면, 디버깅에 시간을 쏟기보다 마지막으로 작동하던 상태(초록색 막대)로 신속히 되돌아가 더 작은 단계로 다시 시도해야 합니다 . 시스템적 접근: 임기응변식(ad hoc)으로 파고들면 더 큰 구렁텅이에 빠질 위험이 있으므로, 입증된 체계적인 리팩토링 기법을 적용해야 합니다 . 리팩터링 가능성이 있는 코드에서 풍기는 악취 인식하는법 기이한 이름 (Mysterious Name): 코드가 무슨 일을 하는지 명확히 전달하지 못하는 함수, 변수, 클래스의 이름은 가장 흔하고 중요한 악취입니다 . 중복 코드 (Duplicated Code): 한 곳 이상에서 동일하거나 매우 유사한 코드 구조가 반복되는 현상으로 변경 시 누락될 위험을 높입니다 . 긴 함수 / 거대한 클래스 (Long Function / Large Class): 너무 많은 일을 처리하여 이해하기 어려운 긴 함수나, 너무 많은 인스턴스 변수를 가져 중복을 낳는 거대한 클래스입니다 . 뒤엉킨 변경 / 산탄총 수술 (Divergent Change / Shotgun Surgery): 하나의 모듈이 다양한 이유로 여러 번 변경되어야 하거나(뒤엉킨 변경), 하나의 요구사항 변경 때문에 여러 클래스를 자잘하게 수정해야 하는 경우(산탄총 수술)입니다 . 기능 편애 (Feature Envy): 어떤 함수가 자신이 속한 모듈보다 다른 모듈의 데이터나 함수와 더 많이 소통하는 현상입니다 . 데이터 뭉치 (Data Clumps): 여러 곳에서 항상 함께 뭉쳐 다니는 세 네 개의 데이터 항목들로, 객체로 묶어야 할 대상입니다 . 전역 데이터 / 가변 데이터 (Global Data / Mutable Data): 코드베이스 어디서든 접근 및 변경이 가능하여 예기치 못한 부작용과 추적하기 어려운 버그를 낳는 데이터입니다 . 리팩터링을 수행하는 견고한 테스트 구축하는법 자가 검증 테스트 (Self-checking): 테스트는 사람의 눈으로 일일이 확인할 필요 없이, 예상되는 결과와 실제 결과를 자체적으로 비교하여 성공 여부만 명확하게(초록색/빨간색으로) 출력해야 합니다 . 자주 실행 (Run Frequently): 컴파일할 때마다, 혹은 코드를 조금 수정할 때마다 빈번하게 테스트를 실행하여 방금 수정한 코드의 버그를 즉시 잡아내야 합니다 . 위험 중심 테스트 (Risk-driven): 모든 public 메서드를 기계적으로 100% 테스트하려 들지 말고, 가장 오류가 발생할 확률이 높거나 복잡한 위험 영역에 집중하여 가장 높은 효율을 얻어야 합니다 . 경계 조건 확인 (Probing Boundaries): 조건이 잘못될 수 있는 예외 상황이나 경계 조건(예: 빈 문자열, 0 미만의 값 등)을 염두에 두고 이를 잡아낼 수 있도록 집중적으로 테스트를 작성해야 합니다 . 버그 발견 시 단위 테스트 먼저 작성: 버그 리포트를 받으면 버그를 수정하기 전에, 해당 버그를 명확히 노출시키는 단위 테스트를 먼저 작성하여 차후 동일한 버그가 재발하는 것을 방지해야 합니다 .