Chapter 1 에서 리팩토링이 무엇인지 충분히 감 잡았을 것이다.
이를 토대로 이번 장에서는 잠시 시야를 넓혀 리팩터링 전반에 적용되는 원칙 몇 가지를 이야기하는 시간을 갖자.
수많은 다른 소프트웨어 개발 용어와 마찬가지로 리팩토링(Refactoring) 도 엔지니어들 사이에서 다소 두리뭉실한 의미로 통용된다.
하지만 나는 이 용어를 더 구체적인 의미로 사용해야 더 유용하다고 생각한다.
리팩토링이란 용어는 명사로도 사용할 수 있고 동사로도 사용하는게 가능하다.
먼저 명사의 뜻은 다음과 같다.
리팩토링: [명사] 소프트웨어의 겉보기 동작을 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 바꾸는 것
앞 장에서 사용한 함수 추출하기와 조건부 로직을 다형성으로 바꾸기가 이 기법에 적용된다.
동사 버전의 리팩토링의 뜻은 다음과 같다.
리팩토링(하다): [동사] 소프트웨어의 겉보기 동작을 그대로 유지한 채, 여러가지 리팩토링 기법을 적용해서 소프트웨어를 재구성한다.
여기서는 리팩토링 기법이라는 말에 주목하자.
리팩토링은 여러 작은 단계로 나눠서 실행한다. (컴파일-테스트-커밋) 이를 통해 소프트웨어의 동작을 유지할 수 있다는 뜻이다.
누군가 "리팩토링하다가 코드가 깨져서 며칠이나 고생했다." 라고 한다면 그는 리팩토링을 한 것이 아니다.
그리고 리팩토링 기법도 체계적으로 여러가지가 있다. (뭐 변수를 인라인 함수로 만들기, 문장 이동시키기, 함수 추출하기, 함수 옮기기, 단계 나누기 등)
나는 단순히 코드 베이스를 정리하거나 구조를 바꾸는 모든 작업은 재구성(Restructuring) 이라고 한다. (코드를 다듬는 용으로는 Polish 라는 용어를 사용하기도 한다.)
리팩토링은 성능 최적화와 비슷하다.
둘 다 코드를 변경하지만 프로그램의 기능은 정상적으로 작동한다.
단지 목적만 다를 뿐이다.
리팩토링은 코드를 이해하고 수정하기 쉽게 바꾸는 것이라면
성능 최적화는 오로지 속도에만 싱경쓴다.
그래서 목표 성능에 반드시 도달해야 한다면 코드의 이해는 더 어려워질 수 있다.
나는 소프트웨어를 개발할 때 목적을 두 개로 나눈다.
'기능 추가' 와 '리팩토링' 을 명확히 구별한다.
켄트 백은 이를 두 개의 모자에 비유했다.
기능을 추가할 때는 '기능 추가' 모자를 쓴 다음 기존 코드는 절대 건드리지 않고 새 코드를 쓰는 것에만 집중한다.
진척도는 테스트를 통과하느냐를 확인하는 방법으로 측정한다. (TDD 를 말하는 것인듯.)
반면 리팩토링할 때는 '리팩토링 모자' 를 쓴 다음 기능 추가는 절대 하지 않기로 다짐하고 오로지 코드 재구성에만 신경쓴다.
이는 앞 과정에서 놓친 테스트 케이스를 발견하지 않는다면 절대 테스트도 추가하지 않는다.
부득이 인터페이스를 변경할 때만 기존 테스트를 수정한다. (인터페이스를 바꾸는 건 기능 추가의 종류 중 하나라고 생각한다.)
소프트웨어를 개발하는 동안 나는 두 모자를 자주 바꿔 쓴다.
새 기능을 추가하다 보면 코드 구조를 바꿔야 작업하기 훨씬 수월하겠다는 생각이 드는데 그러면 잠시 '기능 추가 모자' 에서 '리팩토링 모자' 로 바꿔쓴다.
그 다음에 리팩토링이 다 끝나면 기존의 기능 추가 모자를 다시 쓴다.
정리하면 소프트웨어를 개발할때 내가 어떤 모자를 쓰고 있는지 늘 점검하면서 개발하자.
리팩토링의 목적은 모든 문제점을 해결하는 것이 아니다.
오로지 코드를 이해하기 쉽고 수정하기 쉽도록 바꾸는 것이다.
리팩토링은 다양한 용도로 활용하는게 가능한데 이를 살펴보자.
리팩토링 하지 않으면 소프트웨어의 내부 설계(아키텍처) 가 썩기 쉽다.
아키텍처를 충분히 이해하지 못한 채 단기 목표만을 위해 코드를 수정하다 보면 기반 구조가 무너지기 쉽다. (DIP 에 의지해야 한다는 점.)
코드만 봐서는 설계를 파악하기 어려워지고 코드 구조가 무너지기 시작하면 악효과가 누적된다.
코드만으로 설계를 파악하기 어려워질수록 설계를 유지하기 어려워지고, 설계가 부패된다. (확장을 할 수 없는 단계를 말하는 것이라고 생각한다.)
같은 일을 하더라도 설계가 나쁘면 코드가 길어진다.
사실상 같은 일을 하는 코드가 여러곳에 나타나게 된다. (코드 중복을 야기한다.)
중복 코드가 낳는 문제는 수정하는 비용과 이해하는 비용이 크게 증가한다는 점이다.
프로그래밍은 여러 면에서 컴퓨터와 대화하는 것과 같다.
컴퓨터에게 시킬 일을 표현하는 코드를 작성하면 컴퓨터는 정확히 시킨대로 반응한다.
그래서 컴퓨터에게 시킬 일과 이를 표현하는 코드의 차이를 최대한 줄여야 한다.
그러므로 프로그래밍은 내가 원하는 바를 정확히 표현하는 것이다.
그치만 코드는 컴퓨터만 읽는게 아니다.
나와 같이 일하는 또는 개발자도 코드를 읽는다.
코드를 이해하기 쉽게 쓰지 않았더라면 이는 다른 개발자를 배려하지 못한 것이다.
리팩토링은 코드가 더 잘 읽히게 도와준다.
잘 작동하지만 이상적인 구조가 아닌 코드가 있다면 잠깐 시간을 내서 리팩토링 하는걸 추천한다.
코드의 목적이 더 잘 드러나게 의도가 더 잘 표현되게 개선하자.
리팩토링은 다른 사람들 뿐 아니라 나 자신을 위해서라도 해야한다.
내가 작성한 코드들을 다 외우지 않는다. 언제든지 잊어버릴 수 있다. 실제로 그렇기도 하고.
코드를 이해하기 십다는 말은 버그를 찾기 쉽다는 말과도 같다.
리팩토링 하면 코드가 하는 일이 명확해지므로 이는 버그를 발견하는데 도움을 준다.
리팩토링을 통해 프로그램의 구조를 명확하게 다듬으면 '그냥 이럴 것이다' 라고 가정하던 부분들이 명확히 드러나는데 이를 통해 버그를 쉽게 찾을 수 있다.
이 사실은 켄트 백의 말을 떠올리게 해준다. "난 뛰어난 프로그래머가 아니에요. 단지 뛰어난 습관을 지닌 괜찮은 프로그래머일 뿐이에요."
리팩토링은 견고한 코드를 작성하는 데 무척 효과적이다.
지금까지 제시한 장점을 추상화하면 다음과 같다.
리팩토링하면 코드 개발 속도를 높일 수 있다.
얼핏 그 반대가 아닌가 생각할 수도 있다.
리팩토링이 내부 설계와 코드 퀄리티를 높일 수 있다는 점은 대부분 순응한다.
하지만 리팩토링하는 데 시간이 드니 전체 개발 속도가 떨어질까봐 걱정할 수도 있다.
한 시스템을 오래 개발하는 개발자들과 얘기하다 보면 초기에는 진척이 빨랐지만 현재는 새 기능을 하나 추가하는데 훨씬 오래 걸린다는 말을 많이 한다.
새로운 기능을 추가할수록 기존 코드베이스에 잘 녹여낼 방법을 찾는 데 드는 시간이 늘어난다는 것이다.
게다가 기능을 추가하다보면 버그가 발생하는 일이 잦고, 이를 해결하는 시간은 한층 더 걸린다.
코드베이스는 패치에 패치가 덧붙여지면서 프로그램의 동작을 파악하기가 더욱 어려워진다.
이러한 부담이 점점 늘어나다보면 차라리 처음부터 새로 개발하는 편이 낫겠다고 생각하는 지경에 다다른다.
하지만 좋은 설계를 지닌 팀은 기존에 작성한 코드를 최대한 활요할 수 있어서 새 기능을 더 빨리 추가하는게 가능해진다.
이렇게 차이 나는 원인은 소프트웨어의 내부 품질에 있다.
내부 설계가 잘 된 소프트웨어는 새로운 기능을 추가할 때 모듈화가 잘되어있어서 코드의 작성 포인트를 쉽게 찾을 수 있다.
코드가 명확하면 버그를 만들 가능성도 줄고, 버그를 만들더라도 디버깅하기가 훨씬 쉽다.
내부 품질이 뛰어난 코드베이스는 새 기능 구축을 돕는다.
나는 이 효과를 설계 지구력 가설 (Design Stamina Hypothesis) 라고 부른다.
내부 설계에 심혈을 기울이면 소프틍뤠어의 지구력이 높아져서 빠르게 개발할 수 있는 상태를 더 오래 지속할 수 있다.
나는 프로그래밍할 때 거의 한 시간 간격으로 리팩토링한다.
그러다 보니 내 작업 흐름에 리팩토링을 녹이는 방버이 여러 가지임을 알게 됐다.
이건 돈 로버츠 (Don Roberts) 가 내게 제시한 가이드다.
- 처음에는 그냥 한다.
- 비슷한 일을 두 번째로 하게 되면 (중복이 생겼다면) 일단 계속 진행한다.
- 비슷한 일을 세 번째 하게 되면 리팩토링 한다.
야구를 좋아하는 사람은 "스트라이크 세 번이면 리팩토링하라" 하고 기억해도 좋다.
리팩토링하기 가장 좋은 시점은 코드베이스에 기능을새로 추가하기 직전이다.
이 시점에서 현재 코드를 살펴보면서, 구조를 살짝 바꾸면 다른 작업을 하기가 훨씬 쉬워질 만한 부분을 찾는다.
가령 내 요구사항을 모두 맍고하지만 리터럴 값 몇 개가 방해되는 함수가 있을 수 있다.
함수를 복제해서 해당 값만 수정해도 되지만, 그러면 중복 코드가 생긴다.
나중에 이 기능을 바꾼다면 수정 포인트가 여러개가 되므로 리팩토링한다.
코드를 수정하려면 먼저 코드를 파악해야 한다.
나는 코드를 파악할 때마다 그 코드의 의도가 명확하게 드러나도록 리팩토링 할 여지가 없는지 찾아본다. (조건부 로직도 찾아보고, 함수의 이름이 올바른지도 찾아본다.)
이쯤되면 코드를 이해하게 되고 그것들을 후에 기억하기 쉽게 리팩토링 해주면 된다. (리팩토링 하지 않으면 코드를 또 다시 이해하는데 시간이 오래 걸리기 떄문)
코드를 파악하던 중에 일을 비효율적으로 처리하는 모습을 발견할 때가 많다.
로직이 쓸데없이 복잡하거나 매개변수화한 함수 하나면 될 일을 거의 똑같이 함수 여러 개로 작성해놨을 수 있다.
이떄는 절충이 필요하다.
원래 하려던 작업과 관련 없는 일에 너무 많은 시간을 쓰기는 싫을 것이다.
그렇다고 쓰레기가 나뒹굴게 방치해서 일을 방해하도록 내버려 두는 것도 좋지 않다.
나라면 간단히 수정할 수 있는 것은 지금하고 그렇지 못한 것은 메모해두고 일을 끝내고 처리한다.
이것이 이해를 위한 리팩토링의 변형된 부분인 쓰레기 줍기 리팩토링이다.
물론 수정하려면 몇 시간이나 걸리고 당장 더 급한 일이 있을 수 있다.
그렇더라고 캠핑 규칙을 따르자. (항상 처음 봤을 때보다 깔끔하게 정리하고 떠나자.)
앞에서 본 준비를 위한 리팩토링, 이해를 위한 리팩토링, 쓰레기 줍기 리팩토링은 모두 기회가 될 때만 한다.
나는 개발에 들어가기 전에 리팩토링 일정을 따로 잡아두지 않고 기능을 추가하거나 버그를 잡을 때 리팩토링을 같이 한다. (효율성을 추구하기 떄문에 이렇게 하는듯)
이는 프로그래밍 과정에 자연스럽게 리팩토링을 녹인 것이다.
리팩토링은 프로그래밍과 별개의 것이 아니다.
사람들은 리팩토링을 과거에 저지른 실수를 바로잡거나 보기 싫은 코드를 정리하는 작업이라고 오해한다.
보기 싫은 코드를 보면 리팩토링 함은 당연하지만 잘 작성된 코드 역시 수많은 리팩토링을 거쳐야 한다.
오랫동안 사람들은 소프트웨어 개발이 뭔가 '추가' 하는 과정으로 여겼다.
기능을 추가하다 보면 대개 새로운 코드를 작성해 넣게 된다.
하지만 뛰어난 개발자는 새 기능을 추가하기 쉽도록 코드를 '수정' 하는 것이 그 기능을 가장 빠르게 추가하는 길일 수 있음을 안다.
소프트웨어 개발은 끝이 없다. 새 기능이 필요할 때마다 소프트웨어는 이를 반영하기 위해 수정된다.
이때 새로 작성해 넣는 코드보다 기존 코드의 수정량이 더 큰 경우가 대체로 많다.
수시로 리팩토링을 해야하지만 계획된 리팩토링도 필요한 경우도 물론 있다.
그동안 리팩토링에 소홀 했다면 따로 시간을 내서 새 기능을 추가하기 쉽도록 코드베이스를 개선할 필요가 있다.
버전 관리 시스템에서 리팩토링 커밋과 기능 추가 커밋을 분리해야 한다는 조언을 들은 적이 있다.
이렇게 할 때의 가장 큰 장점은 두 가지 활동을 구분해서 별개로 검토하고 승인할 수 있다는 점이다.
하지만 나는 이 견해에 동의하지는 않는다. (수시로 리팩토링을 하기 때문에 기능 추가와 리팩토링이 섞일 수 있다는 점 때문인듯.)
리팩토링은 기능 추가와 밀접하게 연관되어 있기 때문에 굳이 나누는 것은 시간 낭비다.
리팩토링은 대부분 몇 분 안에 끝난다. 길어야 몇 시간 정도다.
하지만 팀 전체가 달려들어도 몇 주는 걸리는 대규모 리팩토링이 있다.
라이브러리를 새 것으로 교체하는 작업일 수도 있고, 일부 코드를 다른 팀과 공유하기 위해 컴포너느를 빼내는 작업일 수도 있다.
또는 그동안 작업하면서 쌓여온 골치 아픈 의존성을 정리하는 작업일 수도 있다.
나는 이런 상황에 처하더라도 팀 전체가 리팩토링에 매달리는 것은 회의적이다.
그보다는 주어진 문제를 몇 주에 걸쳐 조금씩 해결해가는 편이 효과적일 때가 많다. (리팩토링을 대규모로 하면 안되는 작업들이 생길 수 있기 떄문에.)
이는 리팩토링이 코드를 깨트리지 않는다는 장점을 활용하는 것이다.
일부를 변경해도 모든 기능이 항상 올바르게 작동한다.
예컨대 라이브러리를 바꾸는 일이라면 기존 것과 새로운 것 모두 포용하는 추상 인터페이스를 만들고 기존 코드가 추상 인터페이스를 호출하도록 하면 라이브러리를 훨씬 쉽게 고칠 수 있다. 이 전략을 추상화로 갈아타기 라고 한다.
코드 리뷰를 정기적으로 수행하는 조직도 있다. 그렇지 않는 조직이라면 해보면 유익할 것이다.
코드 리뷰는 개발팀 전체에 지식을 전파하는 데 좋다.
경험이 더 많은 개발자의 노하우를 더 적은 개발자에게 전수할 수 있다.
코드 리뷰를 하면 대규모 소프트웨어 시스템의 다양한 측면을 더 많은 사람이 이해하는데 도움을 준다.
그리고 깔끔한 코드를 작성하는 데도 도움이 될 수 있고 다른 사람의 아이디어를 얻을 수 있다는 장점도 있다.
그러므로 기회가 닿는 대로 코드 리뷰는 하는게 좋다.
리팩토링은 다른 이의 코드를 리뷰하는 데도 도움된다.
리팩토링을 활용하기 전에는 코드를 읽고, 그럭저럭 이해한 뒤 몇 가지 개선 사항을 제시헀다면
지금은 새로운 아이디어가 떠오르면 리팩토링을 해본다.
이 과정을 몇 번 반복하다 보면 내가 떠올린 아이디어의 실제 적용했을 때의 모습을 명확하게 볼 수 있다.
그러지 않았다면 절대 떠올릴 수 없는 한 차원 높은 아이디어가 떠오르기도 한다.
리팩토링은 그러므로 코드리뷰의 결과를 더 구체적으로 도출하는데 도움을 준다.
개선안들을 제시하는 데서 그치지 않고, 그 중 상당수를 즉시 구현해볼 수 있기 때문이다.
코드 리뷰를 이런 식으로 진행하면 훨씬 큰 성취감을 얻을 수 있다.
내가 가장 많이 질문 받는 것 중 하나는 "관리자에게 리팩토링에 대해 어떻게 말해야 할까요?" 이다.
관라자와 고객은 리팩토링을 누적된 오류를 잡는 일이거나, 혹은 가치 있는 기능을 만들어 내지 못하는 작업이라고 오해하는 경우가 있기 때문이다.
리팩토링 만을 위한 일정을 몇 주씩 잡는 개발팀을 보면 이런 오해는 더욱 커진다.
관라자가 기술에 정통하고 설계 지구력 가설도 잘 이해하고 있다면 리팩토링의 필요성을 쉽게 설득할 수 있다.
이런 관리자는 오히려 정기적인 리팩토링을 권장할 뿐 아니라 팀이 리팩토링을 충분히 하고 있는지 살펴보기도 한다.
지금까지의 이야기가 무조건 리팩토링을 권장한다고 들릴 수 있는데 리팩토링하면 안되는 상황도 있다.
나는 지저분한 코드를 발견해도 굳이 수정할 필요가 없다면 안한다.
외부 API 다루듯 호출해서 쓰는 코드라면 지저분해도 그냥 둔다.
내부 동작을 이해해야 할 시점에 리팩토링해야 효과를 제대로 볼 수 있다.
리팩토링하는 것보다 처음부터 새로 작성하는게 쉬울 대도 리팩토링하지 않는다.
리팩토링은 직접 해보기 전에는 알기 어렵기 떄문이다.
나는 누가 특정한 기술, 도구, 아키텍처 등을 내세울 때마다 항상 문제점을 찾는다. (Trade-off 를 항상 계산한다는 말인듯. 이런 자세가 중요하다고 생각한다. 실버 불렛은 없기 떄문에)
난 리팩토링이 많은 팀에서 적극적으로 도입해야 할 중요한 기법이라고 믿지만 리팩토링이 딸려오는 문제점도 엄연히 있다.
이런 문제가 언제 발생하고 어떻게 대처해야 하는지 반드시 알고 있어야 한다.
앞 절과 같은 이야기다.
많은 사람들이 리팩토링 떄문에 새 기능 개발하는 속도가 느려진다고 생각한다.
하지만 리팩토링의 궁극적인 목적은 개발 속도를 높이는 데 있다.
리팩토링의 궁극적인 목표는 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것이다.
리팩토링이 필요해 보이지만 추가하려는 기능이 아주 작아서 기능 추가부터 하고 싶은 상황에 마주할 수도 있다.
이 경우라도 준비를 위한 리팩토링을 하면 변경을 훨씬 쉽게 할 수 있다.
그래서 새 기능을 구현해넣기 편해지겠다 싶은 리팩토링이라면 주저하지 말고 리팩토링부터 한다.
내가 직접 건드릴 일이 거의 없거나 불편한 정도가 그리 심하지 않는다고 판단되면 리팩토링 하지 않는 편이다.
여기서 주의할 점은 리팩토링을 "클린 코드" 를 위해 너무 집착하는 것이다. (확실히 클린 코드를 너무 신경쓰다 보면 생산성이 떨어지는 것 같다.)
리팩토링의 본질을 잊지말자. 리팩토링은 개발 기간을 단축하고자 하는 것이다.
리팩토링의 동력은 경제적인 효과를 늘 생각하자.
리팩토링하다 보면 모듈의 내부뿐 아니라 시스템의 다른 부분과 연동하는 방식에도 영향을 주는 경우도 많다.
함수 이름을 바꾸고 싶어서 함수를 호출하는 곳을 모두 찾아서 바꿀 수도 있지만 그 코드의 소유자가 다른 팀이라서 이를 바꿀 권한이 없을 수 있다.
또는 바꾸려는 함수가 고객에게 제공해주는 API 라면 쓰는 사람 모두를 알기 어렵다.
코드 소유권이 나눠져있다면 리팩토링 하기 어렵다.
그렇다고 해서 리팩토링을 할 수 없는게 아니다.
예컨대 함수 이름을 바꾸고 싶다면 함수 바꾸기를 적용하고 기존 함수는 새 함수를 호출하도록 만들면 된다.
이러면 인터페이스는 복잡해지지만 클라이언트에 영향을 주지 않는다.
이처럼 코드 소유권은 리팩토링에 영향을 주기 떄문에 나는 코드 소유권을 작은 단위로 나누는 것에 반대한다.
내가 선호하는 방식은 코드 소유권을 팀에 두는 것이다. 그래서 팀에 속한 팀원이라면 누구나 코드르 수정할 수 있다.
현재 흔히 볼 수 있는 팀 단위 작업 방식은 버전 관리 시스템을 사용해 팀원마다 코드 베이스를 하나식 맡아서 작업하다가 결과물이 쌓이면 마스터 브랜치에 통합해서 다른 팀원과 광유하는 방식이다.
이렇게 하면 기능 전체를 한 브랜치에다 구현해놓고 프로덕션 버전으로 릴리스 할때가 되서야 마스터에 통합하는 경우가 많다.
이 방식을 선호하는 이들은 마스터 브랜치를 건강하게 만들어 둘 수 있다는 장점 때문이다. (마스터 브랜치에 버전 별로 나눌 수 있어서.)
하지만 이 기능은 단점이 있는데 독립 브랜치로 작업하는 기간이 길어질 수록 마스터와 통합하기가 어려워진다.
이 고통을 줄이기 위해 많은 이들이 마스터를 개인 브랜치로 수시로 리베이스 하거나 머지한다.
하지만 여러 브랜치에서 동시에 개발이 진행될 떄는 이런식으로 해결할 수 없다.
나는 머지와 통합을 명확한다.
마스터를 브랜치로 머지하는 작업은 단방향이다. 브랜치만 바뀌고 마스터는 그대로다.
반면 통합은 마스터를 개인 브랜치로 가져와서 작업한 결과를 다시 마스터에 올리는 양방향 처리를 말한다.
그래서 마스터와 브랜치 모두 변경된다.
이 경우 생각해야 하는 경우는 다른 브랜치에서 함수를 호출하는 코드를 추가했는데, 내 브랜치에서 그 함수의 이름을 변경했다면 프로그램이 동작하지 않게 된다.
이처럼 머지가 복잡해지는 문제는 기간이 길어질수록 늘어난다.
이 때문에 기능별 브랜치의 통합 주기를 2~3 일 주기로 가져가야 한다는 사람이 많다.
나와 같은 사람들은 이를 더욱 짧게 가져가야 한다고 생각한다.
이 방식을 지속적 통합(Continuous Integration) 이라고 하낟.
CI 에 따르면 모든 팀원이 하루에 최소 한 번은 마스터와 통합한다.
이렇게 하면 다른 브랜치들과의 차이가 크게 벌어지는 브랜치가 없어져 머지의 복잡도를 상당히 낮출 수 있다.
하지만 CI 를 적용하기 위해선 생각해야 하는 부분은 거대한 기능을 짧게 쪼개서 통합이 잘되도록 하는 것과 완료되지 않은 기능이 시스템 전체를 망치지 않도록 하는 것이다.
기능별 브랜치를 사용하면 안된다는 말은 아니다.
브랜치를 자주 통합할 수만 있다면 문제가 발생할 가능성을 크게 줄일 수 있다.
리팩토링의 두드러진 특성은 프로그램의 겉보기 동작이 유지된다는 것이다.
절차를 지켜서 리팩토링하면 동작이 꺠지지 않는다.
깨지더라도 금방 복구하는게 가능해진다.
이를 위해선 코드의 다양한 측면을 감시하는 다양한 테스트 케이스가 필요하다.
테스트 코드는 리팩토링 할 수 있게 해줄 뿐 아니라 새 기능 추가도 훨씬 안전하게 진행하도록 도와준다.
사람들 대부분은 많이 물려받을수록 좋아한다.
하지만 프로그래밍할 때는 그렇지 않다. 물려받은 레거시 코드는 대체로 복잡하고 테스트도 제대로 갖춰지지 않은 것이 많다.
무엇보다도 다른 사람이 작성한 것이다.
레거시 시스템을 파악할 때 리펙토링이 굉장히 도움된다. 제 기능과 맞지 않은 함수 이름을 바꿔 잡고 어설픈 프로그램 구문을 매끄럽게 다듬어서 거친 원석 같던 프로그램을 반짝이는 보석으로 만들 수도 있다.
하지만 이렇게 레거시 코드를 리팩토링 할 때는 테스트 코드 없이는 명료하게 리팩토링 되기 어렵다.
이 문제의 정답은 당연히 테스트 코드의 보강이다.
막상 해보면 이는 조금 어렵다. 보통은 테스트를 염두해두고 설계한 시스템만 쉽게 테스트할 수 있다.
물론 그런 시스템이라면 테스트를 갖추고 있을 것이라서 애초에 이런 걱정할 일도 없다.
이 기법의 핵심은 커다란 변경들을 쉽게 조합하고 다룰 수 있는 데이터 마이그레이션 스크립트를 작성하고, 접근 코드와 데이터베이스 스키마에 대한 구조적 변경을 이 스크립트로 처리하게끔 통합하는 데 있다.
간단한 예로 필드의 이름을 변경하는 경우를 생각해보자.
함수 선언 바꾸기에 따르면 데이터의 구조를 원래 선언과 이 데이터 구조를 호출하는 코드를 모두 찾아서 한 번에 변경해야 한다.
그런데 예전 필드를 사용하는 데이터 모두가 새 필드를 사용하도록 반환해야 하는 부담도 따른다.
이럴 때 나는 이 변환을 수행하는 코드를 간단히 작성한 다음, 선언된 데이터 구조나 접근 루틴을 변경하는 코드와 함께 버전 관리 시스템에 저장한다.
그런 다음 데이터베이스를 다른 버전으로 이전할 때마다 현재 버전에서 원하는 버전 사이에 있는 모든 마이그레이션 스크립트를 실행한다.