소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법.
리팩터링은 성능 최적화와 비슷하다. 둘다 코드를 변경하지만, 다른점이라면 리팩터링은 코드를 이해하기 쉽도록 변경하는 것이고, 성능 최적화는 오로지 성능에만 신경쓴다.
웬만하면 리팩터링시에는, 기능추가와 별개로 리팩터링에만 집중해야한다. 기능을 더 추가하다 보면 오히려 코드가 복잡해져서 기존의 코드조차 동작하지 않는 경우가 생긴다.
소프트웨어 설계가 좋아진다.
아키텍처를 이해하지 못한 채 단기 목표만을 위해 코드를 수정하다 보면 프로그램의 크기가 커질 수록 기반 구조가 무너지기 쉽다.
리팩터링하면 소프트웨어를 이해하기 쉬워진다.
코드의 목적이 더 잘 드러나게, 다시 말해 내 의도를 더 명확하게 전달하도록 개선할 수 있다.
리팩터링하면 버그를 쉽게 찾을 수 있다.
프로그램의 구조를 명확하게 다듬으면, 머리속으로 두루뭉실했던 코드가 확연해지고 버그를 지나칠 수 없는 정도까지 명확해 진다. 켄트 백은 이런말을 했다. "난 뛰어난 개발자가 아니다. 단지 뛰어난 습관을 지닌 괜찮은 개발자일 뿐이다."
프로그래밍 속도를 높일 수 있다.
한 시스템을 오래 개발 중인 개발자들과 얘기하다 보면 초기에는 진척이 빨랐지만 현재는 새기능을 하나 추가하는데 훨씬 오래 걸린다는 말을 많이 한다. 나쁜 코드는 이해하는데 어려움을 겪고, 코드가 길어질 수록 이해하는 시간에 시간자원을 많이 쏟아야 한다. 그에 비해 모듈화, 아키텍처 설계가 잘 되어있다면, 확실이 이해 속도가 빨라질 것이다.
리팩터링 하기 가장 좋은 시점은 기능을 추가하기 직전이다. 왜냐하면 리팩터링으로 인해 설계 구조가 깔끔해진 코드를 쉽게 이해하며 그 곳에 기능을 추가하기 때문이다. 결과물도 좋을 것이 분명하다.
코드의 의도가 더 명확하게 드러나도록 리팩터링할 여지는 없는지 찾아보자. 혹은 자잘한 세부 코드마저 이해를 위해 리팩터링 해보자. 내가 이해한 것을 코드에 반영해두면 나 스스로 코드에 대한 이해를 더 오래 보존할 수 있을 뿐만 아니라 동료들도 그렇다.
... 등등
코드를 파악하던 중에 일을 비효율적으로 처리하는 모습을 발견할 때가 있다. 로직이 쓸데없이 복잡하거나, 매개변수화한 함수 하나면 될 일을 거의 똑같은 함수 여러 개로 작성해놨을 수 있다. 이때 약간 절충을 해야한다.
간단히 수정할 수 있는 것은 즉시 고치고, 시간이 좀 걸리는 일은 짧은 메모만 남긴 다음, 흐름이 끊기지 않도록 하던 일을 끝내고 나서 처리한다.
앞서 말했던 준비를 위한 리팩터링, 이해를 위한 리팩터링, 쓰레기 줍기 리팩터링은 모두 기회가 될 떄만 진행한다. 필자는 리팩터링 일정을 따로 잡아두지 않고, 기능을 추가하거나 버그를 잡는 동안 리펙터링을 주기적으로 진행한다. 여기서 중요한 점은, 리팩터링이 프로그래밍 과정에 있어 별개의 존재가 아니라는 것이다.
또 한가지, 리팩터링은 보기 싫은 코드를 정리하는 작업이라고 오해할 수 있다. 하지만 리팩터링은 보기싫은 코드를 수정하는 것 뿐 아니라, 잘 작성된 코드 역시 리팩터링 범주에 속한다. 이유는 어제 적합했던 코드가 앞으로 다른 작업에 있어서 맞지 않을 수 있기 때문이다. 오랫동안 사람들은 소프트웨어 개발이란 뭔가 '추가'하는 과정으로 여겼다. 하지만 뛰어난 개발자는 새 기능을 추가하기 쉽도록 코드를 '수정'하는 것이 기능을 빠르게 추가하는 길임을 안다.
버전 관리 시스템에서 리팩터링 커밋과 기능 추가 커밋을 분리해야 한다는 말이 있다. 하지만 이도 한번 생각해볼 필요는 있는 듯 하다. 리팩터링은 기능 추가와 밀접하게 엮인 경우가 많다. 따라서 리팩터링과 기능 추가를 별도로 완전히 분리해 버린다면, 왜 리팩터링을 했는지 맥락 정보를 잃어버리기 쉽다. 그렇기 때문에 무조건 커밋을 분리해야 한다는 것이 아님을 명심하고, 팀에 적합한 방식을 실험을 통해 찾아가 보자.
우선 코드리뷰의 장점을 먼저 살펴보자.
이제 리팩터링을 코드리뷰에 적용했을 시 어떤 효과가 있는지 알아보자. 일반적으로 코드리뷰를 하면 떠오르는 프로세스는 다음과 같다.
위의 프로세스를 버리고 리팩터링이라는 관점에서 코드리뷰를 바로 시작해보면, 결과물이 리팩터링이 되기 때문에 훨씬 구체적인 리뷰 결과와 성취감을 맛보게 된다.
리팩터링으로 코드리뷰를 계속 하다보면, 내가 떠올린 아이디어를 실제로 적용한 모습을 점점 명확하게 볼 수 있게 된다. 짐작이 아닌 결과물을 마음속으로 직접 확인하는 것이다.
코드 리뷰에 리팩터링을 접목하는 구체적인 방법은 리뷰의 성격에 따라 다르다. 그런데 전반적으로 효과적인 방법은 코드를 작성한 사람이 참여하는 방식이다. 어떤 의도로 코드를 작성했는지 맥락을 설명해주는 작성자가 있다면 이해가 빠르고, 제대로 코드의 의도를 파악할 수 있기 때문에 좀 더 효과적이다. 따라서 자연스럽게 짝 프로그래밍을 통해 코드리뷰와 리팩터링을 하게 된다.
프로 개발자에게 주어진 임무는 효과적인 소프트웨어를 최대한 빠르게 구현하는 것이다. 관리자가 기술에 정통하고 설계 지구력 가설(잘 설계된 소프트웨어가 대규모 소프트웨어로 접어들수록 기능을 추가하기 쉽고 빠르다는 가설)도 잘 이해하고 있다면 이미 리팩터링을 시도하고 있을 것이다. 왜냐하면 기능을 가장 빠른 구현하는 방법이 바로 리팩터링이기 때문이다. 하지만 관리자가 이를 모르고 있다면, 해당작업을 리팩터링한다고 말하지 말기를 권장한다.
리팩터링의 궁극적인 목적은 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것이다. 하지만 그렇더라도 상황에 맞게 조율해야 한다. 예를 들어 추가하려는 새 기능이 아주 작아서 기능 추가부터 하고 싶은 상황에 마주칠 수 있다. 이럴 때 유연하게 대처해야 한다.
만약 구현 시작 전, 리팩터링으로 새 기능을 구현해넣기 편해지겠다 싶은 경우 주저하지 않고 선 리팩터링 후 기능 구현 순서로 진행한다. 반면 직접 건드릴 일이 없거나, 불편한 정고가 그리 심하지 않다고 판단되면 리팩터링하지 않는 편이다. 떄로는 어떻게 개선해얗 라지 확실히 떠오르지 않아서 미루기도 한다. 물론 개선점이 떠오르면 시험 삼아 고쳐보고 더 나아지는지 살펴본다.
리팩터링을 클린코드나 바람직한 엔지니어링 습관처럼 도적적인 이유로 정당화하는 것! 리팩터리으이 본질은 코드베이스를 예쁘게 꾸미는 데 있지 않다. 오로지 경제적인 이유로 하는 것이다. 개발 기간을 단축하고, 기능 추가 시간을 줄이고, 버그 수정 시간을 줄여야한다. 스스로 그렇게 인식하는데 그치치 말고 다른 사람과 대화할 때도 이 점을 명심하자. 이를 명확히 이해하는 개발자, 관리자, 고객이 많아질수록 좋은 설계 곡선으 더 많이 볼 수 있다.
모듈의 내부뿐 아니라, 시스템의 다른 부분과 연동하는 방식에도 영향을 주는 경우가 있다. 함수 이름을 바꾸고 싶을 때 호출하는 곳을 모두 찾을 수 있다면 간단히 함수 선언 바꾸기를 적용하면 된다. 하지만, 예를 들어 함수를 호출하는 코드의 소유자가 다른 팀이라서 나에게는 쓰기 권한이 없거나, 또는 함수가 API로 제공되고 있다면 리팩터링이 쉽지 않다.
즉, 코드 소유권이 나뉘어 있으면 리팩터링에 방해가 된다. 소유권이 있는 자가 클라이언트(소유권이 없는 곳)에 영향을 주지 않고서는 원하는 형태로 변경할 수 없기 때문이다. 그렇다고 리팩터링으 할 수 없는건 아니다.
이 처럼 복잡해 지기 때문에 코드 소유권을 작은 단위로 관리하는 것을 지양하자.
소유권을 큰 단위로 관리하지만 적합하지 않은 예시를 살펴보자.
이렇게 했을 때 단점은, 코드베이스에서 수정하면 되는 것을 인터페이스를 수정하는 등 별도의 인터페이스 관리가 또 필요하다.
코드의 소유권을 팀에 두는 것이다. 코드를 킨원이라면 누구나 수정할 수 있게 한다. 설사 다른 사람이 작성했더라도 말이다. 프로그래머마다 각자가 책임지는 영역이 있을 수는 있다. 이 말은 자신이 맡은 영역의 변경 사람을 관리하라는 뜻이지, 다른 사람이 수정하지 못하게 막으라는 뜻이 아니다.
이 방식을 여러 팀으로 구성된 대규모 시스템에도 적용할 수 있다.
어떤 팀은 다른 팀 사람이 자기 팀 코드의 브랜치를 따서 수정하고 커밋을 요청하는, 흡사 오픈소스 개발 모델을 권장하기도 한다. 이렇게 하면 함수의 클라이언트도 코드를 수정 할 수 있다.
이 방식의 장점 아래 두 방식의 장점을 적절히 타협한 것이다.