2.19 리팩터링과 기술 부채
개요와 동기
리팩터링은 코드가 바깥에서 하는 일을 바꾸지 않고 내부 구조를 바꾸는 것입니다. 변수 이름을 바꾸고, 긴 함수를 쪼개고, 클래스를 추출하고, 뒤엉킨 조건문을 독자가 따라갈 수 있는 것으로 접어도, 프로그램은 전과 정확히 똑같이 동작합니다. 그 마지막 부분이 규율 전체입니다. 리팩터링은 정의상 동작을 보존하며, 동작도 함께 바꾸는 순간 더는 리팩터링이 아닙니다. 위험한 두 가지를 한꺼번에 하면서 서로 뒤에 감추는 것입니다. 이 장은 둘을 의도적으로 별개의 행위로 다룹니다. 둘의 혼동이 대부분의 리팩터링이 잘못되는 곳이기 때문입니다.
큰 팀에서 이것은 혼자 개발하는 사람보다 더 중요합니다. 정리하는 코드가 수백 명이 읽고, 의존하고, 건드리기를 두려워하는 코드이기 때문입니다. 리팩터링은 공유 코드베이스가 여러 해와 인력 교체에 걸쳐 살 만하게 유지되는 방법입니다. 일상의 코딩 품질이 정해지는 소프트웨어 구축(2.9장)과, 리팩터링을 안전하게 하는 안전망인 테스트 전략(2.4장)에 직접 연결됩니다. 또한 더 어려운 질문인 기술 부채에도 연결됩니다. 지름길, 낡아 가는 설계, 미뤄진 정리가 쌓여 모든 미래 변경을 느리게 만드는 비용입니다. 리팩터링은 그 부채를 갚는 주된 방법이므로, 두 주제는 한 장에 속합니다.
기업과 정부 환경에서는 이해관계가 오릅니다. 이런 시스템은 오래 살고, 흔히 수십 년 되었으며, 모든 코드 변경을 통제되는 사건으로 다루는 감사 및 변경 통제 체계 아래에 있는 경우가 많습니다. 시민 급여 시스템을 긴 주말에 다시 쓸 수는 없습니다. 작고, 되돌릴 수 있고, 증거에 기반한 단계로 현대화하며, 이것이 바로 규율 있는 리팩터링이 주는 것입니다. 많은 팀과 오래 사는 시스템(10.4장)에 걸쳐 그 일을 조율하는 것은 대규모 엔지니어링의 결정적 과제 중 하나이며, 잘못하면 조직이 얼어붙어 더는 이해하지 못하는 소프트웨어를 바꿀 수 없게 됩니다.
핵심 원칙
- 리팩터링은 동작을 보존합니다. 코드가 하는 일을 바꾸고 있다면 그것은 별개의 변경이며 따로 합니다.
- 신뢰할 수 있는 테스트 스위트는 안전한 리팩터링의 선행 조건이지 선택 사항이 아닙니다.
- 작고, 이름 붙은, 되돌릴 수 있는 단계로 일하고, 매 단계 후에 코드가 동작하게 유지하십시오.
- 기술 부채를 눈에 보이고 추적되게 만든 뒤, 영웅적 행동이 아니라 꾸준한 역량으로 상환에 재원을 대십시오.
- 이미 바꾸고 있는 코드를 리팩터링하십시오. 거기서 정리가 값을 합니다.
- 모든 부채가 갚을 가치가 있는 것은 아닙니다. 안정적이거나, 거의 건드리지 않거나, 곧 폐기될 코드는 내버려 둘 수 있습니다.
- 내부 품질을 판단에 정보를 주려고 측정하고, 조작될 목표로 삼지 마십시오.
권장 사항
리팩터링과 동작 변경을 엄격히 분리한다
시작하기 전에 어느 쪽을 하는지 정하고, 하나의 커밋에서 둘을 절대 뒤섞지 마십시오. 리팩터링할 때는 전에 통과하던 테스트가 후에도 바뀌지 않고 통과해야 합니다. 관찰 가능한 동작이 움직이지 않았기 때문입니다. 동작을 바꿀 때는 그것을 고유한 테스트와 함께 자체 커밋으로 하십시오. 이유는 실용적입니다. 뒤섞인 변경이 무언가를 깨뜨리면, 구조 재편이 버그를 도입했는지 동작 변경이 그랬는지 알 수 없고, 코드 리뷰(2.5장)에서 리뷰어는 어느 쪽 절반도 깔끔하게 추론할 수 없습니다. 통하는 습관은 Martin Fowler의 두 모자 규칙입니다. 언제나 리팩터링 모자나 기능 모자 중 하나를 쓰고 있고, 어느 쪽인지 알고, 의도적으로 바꿉니다. 별도의 커밋은 버전 관리 이력도 읽을 수 있게 하여, 실패를 이분 탐색하는 엔지니어가 순수 리팩터링 커밋을 자신 있게 건너뛸 수 있습니다.
구조를 바꾸기 전에 신뢰할 수 있는 안전망을 세운다
테스트 없는 리팩터링은 그저 편집하고 바라는 것입니다. 중대한 무언가를 재구조화하기 전에, 동작 변경을 일으켰을 때 잡아낼 것이라 신뢰하는 스위트가 필요하며, 이것이 테스트 전략(2.4장)의 핵심 논거입니다. 이미 좋은 커버리지가 있는 코드는 테스트를 실행하고, 작은 단계로 리팩터링하고, 각 단계 후 다시 실행하십시오. 테스트가 없는 레거시 코드에서 정직한 수는 먼저 특성화 테스트를 쓰는 것입니다. 특성화 테스트는 코드가 해야 하는 일을 단언하지 않습니다. 코드가 지금 실제로 하는 일을 괴팍함까지 포함해 포착하여, 동작의 어떤 변화도 실패하는 테스트로 나타나게 합니다. Michael Feathers가 큰 조직이 사는 바로 그 상황, 즉 동작하고, 중요하고, 테스트가 없는 코드를 위해 이 접근을 대중화했습니다. 현재 동작이 못 박히면 그 아래서 안전하게 리팩터링하고, 그다음에야 그 위에서 동작을 바꿀 수 있습니다.
코드 스멜을 알아보고 작고 이름 붙은 리팩터링을 적용한다
코드 스멜은 그 밑에서 무언가 주의가 필요할 수 있다는 표면적 징후입니다. 너무 길게 자란 함수, 너무 많이 아는 클래스, 중복된 로직, 긴 매개변수 목록, 하는 일을 속이는 이름입니다. 스멜은 평결이 아니라 힌트이므로, 맹목적으로 따르지 말고 조사합니다. 대응은 Fowler의 카탈로그에서 가져온 작고 이름 붙은 리팩터링입니다. 함수 추출, 변수 이름 바꾸기, 메서드 이동, 조건문을 다형성으로 교체 등 수십 가지입니다. 이름 붙은 움직임을 쓰는 가치는 각각이 작고, 이해되고, 기계적으로 안전하며, IDE가 직접 지원하는 경우가 많다는 점입니다. 검증할 수 없는 하나의 큰 도약이 아니라, 많은 작고 믿을 만한 단계로 큰 개선을 구성하며 내내 코드를 초록으로 유지합니다.
기회가 닿을 때마다 리팩터링하고, 캠페인은 진짜 구조적 필요에 남겨 둔다
대부분의 리팩터링은 이미 하고 있는 일에 접어 넣은 기회주의적인 것이어야 합니다. 보이스카우트 규칙이 이를 담습니다. 코드를 찾았을 때보다 조금 더 깨끗하게 남겨 두십시오. 기능을 추가하거나 버그를 고치려고 파일을 건드릴 때는 그 구석을 이미 이해하고 있으며, 거기서의 작은 정리는 누구의 허락이나 별도 예산 없이 시간이 지나며 누적됩니다. 팀이 기능 작업을 멈추고 큰 영역을 재구조화하는 계획된 리팩터링 캠페인이 때로 필요하지만, 비싸고, 제품 압박에 맞춰 일정을 잡기 어렵고, 그 영역이 잘 테스트되지 않았다면 위험합니다. 캠페인은 기회주의적 정리가 닿을 수 없는 구조적 문제에 남겨 두고, 치르고 있는 변경 비용에 대한 증거로 필요를 주장하십시오. 작은 정리의 꾸준한 방울을 선호하십시오. 가끔의 영웅적 재작성보다 더 오래갑니다.
큰 구조 변경에는 교살자 무화과 패턴을 쓴다
서브시스템 전체를 교체해야 할 때, 1년 동안 돌다가 끝에 병합되는 빅뱅 재작성을 시도하지 마십시오. 그것이 현대화 프로젝트가 죽는 방식입니다. Martin Fowler가 나무를 휘감아 점차 대체하는 덩굴에서 이름 붙인 교살자 무화과 패턴을 쓰십시오. 옛 시스템 앞에 퍼사드를 두고, 한 번에 한 조각의 기능을 그 퍼사드 뒤의 새 코드로 라우팅하고, 프로덕션에서 검증하고, 옛 시스템이 완전히 둘러싸여 제거될 수 있을 때까지 반복합니다. 각 조각은 작고, 출시 가능하고, 되돌릴 수 있어서, 위험은 한정되고 가치는 지속적으로 도착합니다. 가까운 사촌인 추상화에 의한 분기(branch by abstraction)는 단일 코드베이스 안에서 같은 일을 합니다. 교체하려는 것 위에 추상화 계층을 도입하고, 둘이 공존하는 동안 그 뒤에 새 구현을 만들고, 소비자를 점차 전환하고, 아무것도 의존하지 않게 되면 옛 구현을 삭제합니다. 둘 다 레거시 시스템이 살아 있는 채로 진화하게 하며, 이것이 대부분의 큰 조직이 실제로 감당할 수 있는 유일한 종류의 현대화입니다.
기술 부채를 포트폴리오로 다루고 눈에 보이게 한다
Ward Cunningham이 만든 부채 은유는 두 가지를 구분합니다. 원금(지저분한 코드나 지름길 자체)과 이자(그 때문에 모든 미래 변경이 치르는 추가 노력)입니다. 모든 부채가 같지는 않습니다. Fowler의 사분면은 두 축으로 분류합니다. 의도적 대 비의도적, 신중함 대 무모함입니다. 신중하고 의도적인 부채(“지금 출시하고 다음 스프린트에 정리한다, 비용은 안다”)는 정당한 비즈니스 결정입니다. 무모하고 비의도적인 부채(“디자인 패턴이 뭐죠?“)는 그저 피해입니다. 의사결정과 거버넌스(1.5장)와 부채를 포트폴리오로 다루는 그 논의에 연결되는 관리의 일은 부채를 추론할 수 있도록 눈에 보이게 하는 것입니다. 일이 사는 곳에서 중요한 항목을 추적하고, 코드에 태그를 달고, 치르는 이자를 기록해, 가장 크게 불평하는 사람이 아니라 증거로 상환이 역량을 두고 경쟁하게 하십시오. 볼 수 없는 부채는 관리할 수 없습니다.
영웅적 행동이 아니라 꾸준한 역량으로 상환에 재원을 댄다
실패 양상은 정리를 “상황이 진정되면” 하겠다고 다루는 것이며, 그런 때는 오지 않습니다. 오래가는 패턴은 상환을 위한 고정되고 보호된 역량입니다. 매 주기의 명시적 몫이거나, 정리가 같은 영역의 기능 작업과 함께 간다는 상시 합의입니다. 통하지 않는 것은 누군가 주말을 태워 모든 것을 고치는 주기적 영웅적 스프린트입니다. 지속 불가능하고, 리뷰되지 않으며, 대개 스스로 되돌려지기 때문입니다. 꾸준한 역량은 이자 지급을 낮게 유지하고, 위기가 비싼 재작성을 강제할 때까지 부채가 쌓이는 호황-불황 주기를 피합니다. 이것은 엔지니어링 실천만큼 관리의 약속이며, 시스템 수명에 걸친 소프트웨어 유지보수(3.7장) 계획 방식에 속합니다.
내부 품질을 측정하되, 측정이 목표가 되지 않게 한다
순환 복잡도(함수를 통과하는 독립 경로의 수), 중복, 테스트 커버리지, 변경 실패율, 의심되는 영역에서 변경이 걸리는 시간 같은 신호로 내부 품질을 측정할 수 있습니다. 이 숫자는 부채가 집중되는 곳을 찾고 시간에 따른 추세를 지켜보는 데 유용합니다. 위험은 굿하트의 법칙입니다. 척도가 목표가 되면 실제 무엇도 측정하지 않게 됩니다. 커버리지 수치를 의무화하면 아무것도 단언하지 않는 테스트를 얻고, 낮은 복잡도 점수에 보상하면 지표를 피하려고 로직이 더 많은 함수에 번져 버립니다. 지표를 대화를 시작하고 핫스팟을 찾는 데 쓰고, 사람들이 조작할 동기가 있는 관문에 품질 지표를 연결하지 마십시오.
언제 리팩터링하지 않을지 안다
리팩터링은 투자이며, 어떤 코드는 결코 본전을 뽑지 못합니다. 모듈이 안정적이고 거의 건드리지 않으며 가끔 바꿔야 할 때 충분히 이해되어 있다면, 그것을 정리하는 것은 치르지 않던 이자를 위한 노력입니다. 코드가 폐기 예정이라면 리팩터링은 곧 버릴 것을 닦는 일입니다. 규율은 정리 예산을 변경이 빈번하고 고통스러운 곳, 즉 이자를 줄이는 것이 실제로 누적되는 곳에 쓰고, 조용한 구석은 내버려 두는 것입니다.
장단점
| 접근 방식 | 장점 | 단점 |
|---|---|---|
| 기회주의적 리팩터링 (보이스카우트 규칙) | 싸고, 지속적이고, 별도 예산 없음, 시간에 걸쳐 누적됨 | 고르지 않은 커버리지. 뜨거운 파일은 나아지고 차가운 파일은 썩음 |
| 계획된 리팩터링 캠페인 | 정리가 닿지 못하는 구조적 문제를 고침 | 비쌈. 기능과 경쟁. 좋은 테스트 없이는 위험 |
| 교살자 무화과 / 추상화에 의한 분기 | 점진적이고 되돌릴 수 있고 시스템을 살려 둠, 위험을 한정 | 서류상으로는 재작성보다 느림. 끝내는 규율 필요 |
| 빅뱅 재작성 | 깨끗한 백지. 레거시 제약 없음 | 높은 실패율. 가치까지 오랜 시간. 동작 간극 |
| 의도적이고 신중한 부채 | 지금 가치를 출시. 명시적이고 계획된 상환 | 상환이 일정에 잡히지 않으면 무모해짐 |
| 지표로 관문을 통제하는 품질 | 객관적, 가시적, 표류를 일찍 포착 | 조작을 부름. 뉘앙스에 불이익. 실제 품질을 떨어뜨릴 수 있음 |
핵심 긴장은 지금의 속도 대 나중의 변경 가능성이며, 실제입니다. 마감이 진짜이고 부채가 신중하고 추적될 때 지름길을 출시하는 것은 올바른 판단일 수 있습니다. 실수는 부채가 공짜인 척하거나, 시스템이 바꾸기에 너무 비싸질 때까지 보이지 않게 쌓이도록 두는 것입니다. 매번 거래를 명시적으로 만들어 해결하십시오. 부채에 이름을 붙이고, 이자를 추정하고, 의도적으로 결정하고, 상환을 잊는 대신 일정에 잡을 수 있도록 결정을 기록하십시오. 알고 빌리고 꾸준히 갚는 팀은 여러 해 동안 빠르게 남고, 모르고 빌리는 팀은 멈춰 섭니다.
팀과 논의할 질문
리팩터링과 동작 변경이 같은 커밋에 섞이는 것을 어떻게 막으며, 리뷰가 실제로 그것을 시행합니까? 이것이 이 장 전체의 기초 규율이며, 마감 압박 아래서 가장 자주 위반됩니다. “이왕 들어온 김에 이걸 정리하자”가 효율적으로 느껴지고 모두 함께 출시하고 싶기 때문입니다. 비용은 나중에 떨어집니다. 뒤섞인 커밋이 프로덕션을 깨뜨리면, 구조 재편이 원인인지 기능이 원인인지 아무도 알 수 없고, 이력을 통한 이분 탐색은 더는 신뢰할 수 없게 됩니다. 최근 풀 리퀘스트 몇 개를 가져와 몇 개가 두 모자를 섞었는지 정직하게 확인하십시오. 상충하는 고려는 마찰입니다. 작업을 별도 커밋으로 나누는 데는 선행으로 약간 더 노력이 들기 때문입니다. 답은 커밋 관례와 리뷰 체크리스트를 형성해야 합니다.
기술 부채는 어디에 있고, 그에 대해 얼마의 이자를 치르고 있으며, 무엇을 갚을지는 누가 결정합니까? 대부분의 팀이 이에 답하지 못하며, 그것이 실제 문제입니다. 볼 수 없는 부채는 비용이 실제로 있는 곳이 아니라 가장 크게 불평하는 사람에 의해 관리되기 때문입니다. 눈에 보이게 한다는 것은 중요한 항목을 추적하고, 코드에 태그를 달고, 어느 영역이 변경을 느리고 실패하기 쉽게 만드는지에 대한 증거를 모으는 것입니다. 상충하는 끌림은 상환에 쓴 모든 시간이 기능에 쓰지 못한 시간이므로, 결정이 리더십과 함께 내리는 포트폴리오 결정이어야 하며 엔지니어링 일의 거버넌스(1.5장) 방식에 연결된다는 점입니다. 변경 실패 데이터와 모두가 건드리기 싫어하는 파일의 목록을 가져오십시오. 답은 상황이 진정되면 정리하겠다는 막연한 의도가 아니라 보호되고 꾸준한 상환 역량이 되어야 합니다.
코드베이스의 어느 부분을 의도적으로 리팩터링하지 않아야 하며, 어떻게 압니까? 모든 것을 리팩터링하는 것은 아무것도 하지 않는 것만큼 실패입니다. 안정적이거나, 거의 건드리지 않거나, 곧 폐기될 코드를 정리하는 데 쓴 노력은 빚지지 않은 대출에 낸 이자이기 때문입니다. 판단은 실제입니다. 모듈은 못생겨 보여도 아무도 바꾸지 않는다면 투자할 잘못된 곳일 수 있습니다. 복잡도 신호와 함께 변경 빈도 데이터를 가져오십시오. 높은 변동과 높은 복잡도의 교차점이 정리가 누적되는 곳이고, 변동이 낮은 코드는 대개 내버려 두는 것이 최선입니다. 상충하는 위험은 “내버려 두자”가 어려운 것은 아무것도 건드리지 않는 핑계가 되는 것입니다. 답은 투자할 가치가 있는 핫스팟의 명시적 후보 목록과 조용한 구석을 무시할 허락을 주어야 합니다.
가장 바꿔야 할 코드를 리팩터링할 만큼 테스트 스위트를 신뢰하며, 어디서 먼저 특성화 테스트를 써야 합니까? 신뢰할 수 없는 안전망은 리팩터링을 편집하고 바라는 일로 바꾸며, 큰 팀에서 가장 무서운 코드는 대개 가장 덜 테스트된 코드이고 바로 정리가 가장 본전을 뽑는 곳입니다. 핫스팟의 커버리지와 변경 실패 데이터를 가져오고, 재구조화가 동작을 바꿔도 경고를 주지 않을 핵심 모듈이 어느 것인지 정직하게 말하십시오. 상충하는 고려는 레거시 코드의 특성화 테스트를 쓰는 일이 느리고 화려하지 않으며 기능을 출시하지 않아서 영원히 미루기 쉽다는 점입니다. 감사와 변경 통제 아래의 기업과 정부 시스템에서 그렇게 못 박은 테스트는 변경이 동작을 보존했다는 증거이기도 하므로, 그것에 재원을 대는 것은 안전 조치이자 컴플라이언스 조치입니다. 답은 누군가 건드리기 전에 어느 영역이 테스트 하네스를 얻는지 이름 붙여야 합니다.
서브시스템을 정말 교체해야 할 때, 점진적 교살자 무화과 접근과 재작성 사이에서 어떻게 결정하며, 재작성에 안 된다고 말할 권한은 누구에게 있습니까? 빅뱅 재작성은 테이블 위에서 가장 매혹적이고 실패하기 쉬운 선택지입니다. 백지는 옛 제약과 사는 것보다 서류상 항상 더 싸 보이기 때문입니다. 큰 조직에서 점진적 경로(퍼사드, 한 번에 한 조각, 프로덕션에서 검증)는 시스템을 살려 두고 위험을 한정하지만, 더 느리고, 끝내는 규율을 요구하며, 새 출발을 바라는 욕구와 경쟁합니다. 서브시스템의 변경 빈도 지도, 재작성이 가치를 낳기까지 얼마나 오래 돌지에 대한 정직한 추정, 병렬 재작성이 메워야 할 동작 간극을 가져오십시오. 정부와 규제 환경에서 끝에 병합되는 다년간의 재작성은 감사 아래서 거의 살아남을 수 없으므로, 답은 기본적으로 교살자 무화과나 추상화에 의한 분기여야 하고 어떤 재작성이든 증거로 주장해야 하는 예외로 다뤄야 합니다.
부채가 집중되는 곳을 찾으려고 내부 품질 지표를 쓰면서, 숫자가 사람들이 조작하는 목표가 되지 않게 하려면 어떻게 합니까? 복잡도, 중복, 커버리지, 변경 실패율 같은 지표는 큰 조직이 어떤 한 사람도 읽지 않는 코드를 가로질러 볼 수 있는 유일한 방법이지만, 하나가 관문이나 성과 평가에 연결되는 순간 굿하트의 법칙이 이어받아 숫자가 실제 무엇도 측정하지 않게 됩니다. 이미 지표가 행동을 이끄는 곳의 예를 가져와, 그것이 대화를 시작하고 있는지, 아무것도 단언하지 않는 테스트와 임계값을 피하려고 함수에 번진 로직에 조용히 보상하고 있는지 물으십시오. 상충하는 끌림은 리더십이 단순한 대시보드 숫자를 원하고 “판단을 쓰라”는 초록 막대보다 설득하기 어렵다는 점입니다. 지표가 거버넌스 보고에 공급되는 기업과 정부 맥락에서는 품질 신호가 투자에 정보를 주고 핫스팟을 찾지만 개인을 관문으로 통제하지 않는다고 분명히 하십시오. 답은 배우려는 측정과 판단하려는 측정 사이에 단호한 선을 그어야 합니다.
분야별 관점
스타트업. 엔지니어 몇 명과 여유 없는 런웨이라면 기회주의적으로만 리팩터링하십시오. 커밋마다 모자 하나를 써서 이력이 이분 탐색 가능하게 유지하고, 의도적으로 택한 지름길의 짧고 정직한 목록을 유지하십시오. 정리 캠페인을 시작하거나 안정적인 모듈을 닦지 마십시오. 희소한 주의를 모두가 두려워하는 한 파일에 쓰고, 변경이 실제로 겁나는 곳에만 특성화 테스트를 쓰십시오. 이 단계에서는 의도적이고 눈에 보이는 부채는 괜찮고, 무모하고 보이지 않는 부채가 여러분을 죽입니다.
소기업. 전담 플랫폼이나 도구 전문가도 빠듯한 예산도 없으니, IDE와 언어 생태계가 공짜로 주는 것에 의지하십시오. 자동 이름 바꾸기와 추출 동작, 린터, 기본 커버리지 신호입니다. 대부분의 부채를 컨설턴트를 고용해 고치는 것이 아니라 일상 업무 과정에서 관리하는 것으로 다루고, 직접 만들어 나중에 리팩터링해야 하는 것보다 잘 유지되는 라이브러리를 사는 쪽을 선호하십시오. 드문 유료 노력은 느림이 고객을 직접 잃게 하는 한 시스템에 남겨 두십시오.
대기업. 많은 팀에 걸쳐 문제는 포트폴리오 거버넌스입니다. 공유 부채 대장, 변경 빈도와 복잡도별 핫스팟의 일관된 태깅, 정리가 기본적으로 기능에 지는 일이 멈추도록 모든 팀 역량의 상환용 보호된 몫입니다. 팀 사이를 옮겨 다니는 어떤 엔지니어든 같은 규칙을 찾도록 두 모자 규율과 특성화 테스트 실천을 표준화하고, 그룹 간에 조율되는 구조 변경에는 교살자 무화과와 추상화에 의한 분기를 쓰십시오. 품질 지표를 정보용으로 유지해 성과 평가에서 조작되지 않고 부채를 찾게 하십시오.
정부. 엄격한 감사와 변경 통제 아래의 오래 사는 시스템은 규율 있는 리팩터링을 엔지니어링만이 아닌 컴플라이언스 자산으로 만듭니다. 재구조화를 동작 변경과 엄격히 분리하면 감사자가 어느 커밋이 동작을 바꿨고 어느 것이 단지 정돈했는지 정확히 볼 수 있습니다. 조달과 투명성 규칙은 빅뱅 재작성보다 작고, 되돌릴 수 있고, 증거에 기반한 단계를 선호하므로, 동작이 보존됨을 문서화하는 특성화 테스트와 함께 교살자 무화과를 기본으로 하십시오. 부채 대장과 그 상환 계획을 시스템의 유지보수 기록의 일부로 만들어 감독 기관이 요구하는 추적성을 얻게 하십시오.
사례
스타트업. 여섯 명 규모의 스타트업이 빠르게 출시하면서 부채를 지고 있음을 알고, 두 가지 싼 일을 잘합니다. 모든 풀 리퀘스트가 모자 하나를 씁니다. 리팩터링 커밋은 기능 커밋과 분리되어, 높은 속도에서도 이력이 이분 탐색 가능하게 유지됩니다. 그리고 의도적으로 택한 지름길의 짧고 정직한 목록을 각각이 치르는 이자에 대한 한 줄 메모와 함께 유지합니다. 결제 모듈이 모두가 두려워하는 파일이 되었을 때, 그 목록과 변경 실패 이력이 이틀을 들여 더 깨끗한 경계를 추출하자는 논거를 세웁니다. 특성화 테스트로 현재 동작을 못 박고, IDE의 이름 바꾸기와 추출 동작으로 그 아래서 리팩터링하고, 아무도 바꾸지 않는 안정적인 모듈은 건드리지 않습니다. 그들이 지는 부채는 의도적이고 눈에 보여서 무모한 종류가 되지 않습니다.
대기업. 한 글로벌 물류 회사가 많은 팀이 매주 바꾸는 15년 된 주문 시스템을 운영합니다. 재작성 대신 교살자 무화과 패턴을 채택합니다. 모놀리스 앞에 퍼사드가 서고, 한 번에 하나의 경계가 있는 능력이 그 뒤의 새 서비스로 다시 라우팅되며, 다음 조각이 시작되기 전에 프로덕션에서 검증됩니다. 팀과 오래 사는 시스템(10.4장)에 걸친 조율이 어려운 부분이므로, 공유 부채 대장을 유지하고, 변경 빈도와 복잡도로 핫스팟을 태깅하고, 각 팀 역량의 고정된 몫을 상환용으로 확보합니다. 내부 품질 지표는 어디를 볼지 알려 주지만 누구의 성과 평가도 관문으로 통제하지 않아 숫자가 정직하게 유지됩니다. 2년에 걸쳐 모놀리스는 꾸준히 줄고 어떤 단일 변경도 시스템 전체를 위험에 빠뜨리지 않습니다.
정부. 한 국가 세무 기관이 모든 코드 변경이 통제되고 증거에 기반한 사건인 엄격한 감사와 변경 통제 규칙 아래 수십 년 된 평가 플랫폼을 현대화해야 합니다. 빅뱅 재작성은 불가능하므로 추상화에 의한 분기를 씁니다. 레거시 계산 엔진 위에 추상화 계층이 도입되고, 그 뒤에 새 구현이 만들어지고, 소비자는 한 번에 하나의 세금 규칙씩 이전되며, 각 이전은 동작이 바뀌지 않았음을 증명하는 특성화 테스트와 함께 작고 되돌릴 수 있는 변경으로 문서화됩니다. 리팩터링이 어떤 입법 동작 변경과도 엄격히 분리되어 있으므로, 감사자는 어느 커밋이 동작을 바꿨고 어느 것이 단지 재구조화했는지 정확히 볼 수 있습니다. 부채 대장과 그 상환 계획은 시스템 유지보수 기록(3.7장)의 일부가 되어 감독 기관이 요구하는 추적성을 줍니다.
비즈니스 사례: 동기, ROI, TCO
리팩터링과 부채 상환의 수익은 소프트웨어를 싸게 바꿀 수 있는 지속적 능력이며, 대부분의 시스템에서 수명 비용의 대부분이 유지보수이므로 총소유비용이 크게 결정되는 곳이 여기입니다. 기술 부채의 이자는 리더십이 이미 추적하는 통화로 치러집니다. 느린 전달, 높은 변경 실패율, 인시던트에서 복구하는 더 긴 시간, 가장 무서운 코드를 피하는 엔지니어입니다. 부채를 눈에 보이게 하고 상환에 꾸준히 재원을 대면, 가장 중요한 영역에서 모든 미래 변경의 비용이 낮아지고, 방치된 부채가 비싼 비상 재작성을 강제하는 호황-불황 패턴을 피합니다.
도입 비용은 소소하고 대부분 문화적입니다. 두 모자 규율을 세우고, 리팩터링해야 할 곳에 안전망을 구축하고, 부채 대장을 유지하고, 상환을 위한 꾸준한 역량의 몫을 보호합니다. 방치의 비용은 조용히 누적됩니다. 이자는 속도가 무너지고 조직이 얼어붙어 더는 이해하지 못하는 시스템을 안전하게 수정할 수 없게 될 때까지 모든 변경에 쌓이며, 이것이 모든 것 중 가장 비싼 결과입니다. 리더십을 설득하려면 부채를 그들이 이미 신경 쓰는 전달 지표에 직접 연결하고, 상환을 엔지니어가 정리할 시간을 요청하는 것이 아니라 측정 가능한 수익이 있는 포트폴리오 결정으로 설명하십시오.
안티패턴과 함정
- 리팩터링과 동작 변경의 혼합: 하나의 커밋이 둘 다 하여, 깨짐을 귀속시킬 수 없고 이력이 신뢰할 수 없게 되는 것.
- 안전망 없는 리팩터링: 테스트되지 않은 코드를 재구조화하고 바라는 것. 믿음에 의한 편집.
- 빅뱅 재작성: 동작하는 시스템을 한꺼번에 교체하는 것. 높은 실패율과 가치까지의 긴 시간을 지닌 패턴.
- 영웅적 주말로서의 리팩터링: 꾸준한 역량 대신 스스로 되돌려지는, 리뷰되지 않고 지속 불가능한 정리.
- 보이지 않는 부채: 아무도 추적하지 않는 지름길로, 상환이 실제 비용이 아닌 불평의 양에 의해 이끌리는 것.
- 품질 지표의 조작: 측정이 목표가 되었기 때문에 실제 품질이 떨어지는 동안 커버리지나 복잡도 목표를 달성하는 것.
- 엉뚱한 코드 리팩터링: 진짜 핫스팟이 계속 비용을 치르는 동안 안정적이거나 곧 폐기될 모듈을 닦는 것.
- 영원한 리팩터링: 가치를 출시하지 않는 끝없는 재구조화. 전혀 정리하지 않는 것의 거울상.
성숙도 모델
- 1단계, 시작: 리팩터링이 그때그때 이루어지고 반응적이며, 같은 커밋에서 동작 변경과 섞이는 경우가 많습니다. 신뢰할 수 있는 안전망이 없고, 기술 부채는 보이지 않고 추적되지 않으며, 정리는 가끔의 영웅적 폭발로만 일어나거나 전혀 일어나지 않습니다.
- 2단계, 발전: 일부 팀이 리팩터링을 동작 변경과 분리하고 있는 곳에서는 테스트에 의지하며, 이름 붙은 리팩터링과 특성화 테스트가 구석에서 나타납니다. 관행은 팀 간에 일관되지 않고, 부채는 논의되고 가끔 기록되며, 상환은 기능과 그때그때 경쟁하다가 대개 집니다.
- 3단계, 표준화: 두 모자 규율, 레거시 코드를 위한 특성화 테스트, 작고 이름 붙은 리팩터링이 문서화되어 조직 전체에서 기대됩니다. 부채는 원금과 이자를 구분하는 공유 대장에 추적되고, 상환을 위한 보호된 역량이 매 주기 계획되어 리뷰에서 시행됩니다.
- 4단계, 관리: 부채와 정리가 기준선에 대한 데이터로 측정되고 통제됩니다. 변경 빈도와 복잡도를 추적해 핫스팟을 찾고, 리팩터링된 영역의 변경 실패율과 변경 리드 타임을 지켜보며, 각 중요한 항목이 치르는 이자를 기록해, 상환 결정이 증거에 근거하고 폐기-투자 판단이 불평의 양이 아닌 추세로 이루어집니다. 품질 신호는 사람들이 조작할 수 있는 관문에 연결되지 않고 투자에 정보를 줍니다.
- 5단계, 오케스트레이션: 부채가 제품 및 유지보수 계획과 통합된, 조직 전체에서 지속적으로 재균형되는 포트폴리오로 관리됩니다. 구조 변경은 팀 간에 조율되는 교살자 무화과와 추상화에 의한 분기를 일상적으로 사용하고, 상환은 지속적이며 변경이 빈번하고 고통스러운 곳에 맞춰지고, 실천은 시스템과 위험 그림이 이동함에 따라 적응하여, 오래 사는 코드가 수십 년 동안 바꿀 수 있게 유지됩니다.
논의를 위한 아이디어
- 리팩터링을 동작 변경과 분리하는 팀의 실제로 시행되는 규칙은 무엇이며, 마감 압박 아래서 어디에서 무너집니까?
- 어떤 코드가 정리할 가치가 있고 어떤 것이 내버려 두는 것이 최선인지 증거로 어떻게 결정합니까?
- 특성화 테스트가 지금 피하고 있는 레거시 영역을 안전하게 리팩터링하게 해 줄 곳은 어디입니까?
- 다음 주요 현대화에서 교살자 무화과 접근은 어떤 모습이며, 먼저 어떤 퍼사드나 추상화를 도입하겠습니까?
- 기술 부채 대장은 누가 소유하며, 상환은 기능 작업에 맞서 실제로 어떻게 역량을 이깁니까?
핵심 요점
- 리팩터링은 동작을 보존합니다. 동작 변경과 엄격히, 별도의 커밋으로 분리하십시오.
- 신뢰할 수 있는 테스트 스위트는 안전한 리팩터링의 선행 조건이며, 특성화 테스트가 레거시 코드에 그것을 줍니다.
- 작고, 이름 붙고, 되돌릴 수 있는 단계로 일하고, 기회주의적 정리를 선호하고, 큰 구조 변경에는 교살자 무화과나 추상화에 의한 분기를 쓰십시오.
- 기술 부채를 눈에 보이게 하고, 원금과 이자를 구분하고, 영웅적 행동이 아니라 꾸준한 역량으로 상환에 재원을 대십시오.
- 내부 품질은 판단을 이끌려고 측정하고 조작될 목표로 삼지 마십시오. 안정적이거나 폐기 예정인 코드는 리팩터링하지 마십시오.
참고 문헌과 더 읽을거리
- Martin Fowler, Refactoring: Improving the Design of Existing Code, second edition
- Michael Feathers, Working Effectively with Legacy Code
- Ward Cunningham, The WyCash Portfolio Management System (OOPSLA 1992 experience report, origin of the debt metaphor)
- Martin Fowler, “TechnicalDebtQuadrant” and “StranglerFigApplication” (martinfowler.com)
- Kent Beck, Tidy First? A Personal Exercise in Empirical Software Design
- Steve McConnell, Code Complete: A Practical Handbook of Software Construction
- Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship