리팩터링이란?

리팩터링의 주된 목적은 기술 부채를 없애는 것이다. 복잡한 코드를 클린 코드와 단순한 디자인으로 바꿔주기 위함이다.

클린 코드

그래서, 클린 코드란 무엇일까? 이는 다음의 특징을 갖고 있는 코드다.

클린 코드는 다른 개발자에게도 분명하게 코드를 설명할 수 있다.

정교한 알고리즘에 대해 이야기 하는 것이 아니라, 잘못된 변수 명칭, 부풀려진 클래스 및 메서드, 매직넘버 등과 같은 것들은 코드를 엉성하고 이해하기 어렵게 만든다.

클린 코드는 중복을 포함하지 않는다.

코드에 중복이 있는 경우, 이 중복 코드를 변경해야 할 때마다, 모든 인스턴스에 동일한 변경 사항을 적용해야 한다는 점을 기억해야 한다. 이는 인지 부하(cognitive load)를 증가시키고 작업의 속도를 늦춘다.

클린 코드는 클래스 및 기타 동작하는 부분(moving part)들을 최소한의 수로 유지한다.

적은 코드는 머릿 속에 저장해야 할 내용 자체가 적다. 적은 코드는 유지 관리가 쉽고, 버그도 줄어든다. 코드는 책임이므로, 짧고 단순하게 작성되어야 한다.

클린 코드는 모든 테스트를 통과한다.

테스트의 95%만 통과하는 경우, 코드가 더럽다는 것을 알 수 있다. 테스트 커버리지가 0%라면, 문제가 있다는 것을 알 수 있다.

기술적 부채 (Technical debt)

누구든 처음부터 훌륭한 코드를 작성하기 위해 최선을 다한다. 애초에 프로젝트에 해를 끼칠 목적으로 의도적으로 언클린(unclean) 코드를 작성하는 프로그래머는 아마 없을 것이다. 하지만, 언제부터 클린 코드가 언클린 코드로 변해가는 것일까?

불결한 코드와 관련하여 기술적 부채라는 은유는 본래 워드 커닝햄이 제안한 것이다.

은행에서 대출을 받으면, 빠르게 물건을 구매할 수 있지만, 이후 원금 뿐 아니라 대출에 대한 추가 이자도 갚아야 한다. 말할 것도 없이, 이러한 이자가 너무 많이 쌓이게 되면, 그 금액이 총 수입을 초과하여 전액 상환 자체가 어려워질 수 있다.

이는 코드에서도 동일하다. 새로운 기능에 대한 테스트를 작성하지 않고도 이를 진행하여 일시적으로 속도를 높일 수는 있으나, 결국에는 테스트를 작성하여 기술적 부채를 청산하지 않는 한, 매일 조금씩 진행 속도가 더뎌지게 된다.

기술적 부채의 원인

비즈니스 압박

때로는 비즈니스적인 상황으로 인하여 기능이 완전히 완성도 되기 전에 출시해야 할 수 있다. 이 경우, 프로젝트의 미완성 부분을 숨기기 위해 코드에 패치와 편법(kludge)이 나타나게 된다.

기술 부채의 결과에 대한 이해 부족

때때로 고용주들은 이러한 기술적 부채의 늘어나는 "이자"로 인해 개발 속도가 저하된다는 사실을 이해하지 못한다. 경영진들이 리팩터링의 가치를 인식하지 못하면, 팀이 리팩터링에 시간을 할애하기가 상당히 어려워질 수 있다.

컴포넌트의 엄격한 일관성에 대처하지 못함

이는 프로젝트가 개별적인 모듈이라기보다는, 하나의 모놀리스와 같은 형태를 띠는 경우 발생한다. 이 경우, 프로젝트의 한 부분을 변경하면 다른 부분에도 영향을 끼치게 된다. 이 경우 개별 구성원의 작업을 분리하기 어렵기 때문에 팀단위 개발이 더욱 어려워진다.

테스트 부족

즉각적인 피드백의 부족은 위험한 해결책(workaround)나, 편법(kludge)을 사용하게 된다. 최악의 경우, 이러한 변경 사항은 사전 테스트 없이 바로 프로덕션에 구현 및 배포되고, 그 결과는 치명적일 수 있다. 이를테면, 아무 문제가 없어보이는 핫픽스가 수천명의 고객에게 이상한 테스트 메일을 전송하거나, 심각하게는 전체 데이터베이스를 플러시(flush)하거나 손상시킬 수 있다.

문서화 부족

이로 인해 프로젝트에 새 인력이 유입되는 속도가 느려지고, 핵심 인력이 프로젝트를 떠날 경우 개발이 중단될 수 있다.

팀원 간의 소통 부족

지식이 회사 전체에 배포되지 않으면, 사람들은 프로젝트에 대한 프로세스 및 정보를 구식으로 이해한 상태로 작업에 들어가게 된다. 이러한 상호아은 주니어 개발자가 멘토로부터 잘못된 교육을 받으면 더더욱 악화될 수 있다.

여러 브랜치에서부터의 장기간 동시적인 개발

이로 인해 기술 부채가 누적될 수 있으며, 변경 사항이 머지될 때 부채는 더욱 증가한다. 개별적으로 변경된 사항이 많을수록 총 기술 부채는 더욱 커진다.

지연된 리팩터링

프로젝트의 요구 사항은 지속적으로 변화하고 있으며, 어느 시점에서 코드 일부가 더 이상 사용되지 않거나 번거로워져 새로 설계되어야 할 수 있다.

한편, 프로젝트의 프로그래머는 더 이상 사용되지 않는 부분과 함께 작동하는 새로운 코드를 매일 작성하게 된다. 따라서 리팩터링이 지연되는 경우, 더 많은 종속 코드를 재작업해야 한다.

규정준수 모니터링 부족

이는 모든 프로젝트 참여 인원이 적합해보인다고 생각되는 대로 코드를 작성할 때 발생한다. (ex. 지난 프로젝트에서 작성했던 것과 동일한 방식으로 작성)

무능력

개발자가 제대로 된 코드를 작성하는 방법을 모르는 경우이다.

언제 리팩터링되어야 하는가?

Rule of Three

리팩터링의 3원칙

  1. 처음으로 무엇인가를 해야할 때는, 그냥 되도록 만든다.
  2. 비슷한 일을 두번째로 할 때는, 반복해야 한다는 사실에 움찔하겠지만, 어쨌든 똑같이 처리한다.
  3. 비슷한 일을 세번째로 해야할 때는, 리팩터링을 시작한다.

When adding a feature

기능을 추가해야 할 때

  • 리팩터링은 다른 사람의 코드를 이해하는 데에 도움이 된다. 다른 사람의 지저분한 코드를 다루어야 한다면, 먼저 리팩터링을 시도해보자. 클린 코드는 훨씬 이해하기 쉽다. 내 자신을 위해서뿐만 아니라, 이후 코드를 사용하는 사람들을 위해서도 코드를 개선할 수 있다.
  • 리팩터링을 하면 새로운 기능을 더 쉽게 추가할 수 있다. 클린 코드에서 변경 사항을 추가하는 것이 훨씬 쉽기 때문이다.

When fixing a bug

버그를 고쳐야 할 때

  • 코드 내 버그(bug)는 마치 실생활의 벌레와 마찬가지로 가장 어둡고 더러운 곳에 존재한다. 코드를 리팩터링하다보면 오류는 거의 스스로 발견된다.
  • 관리자는 사전 리팩터링 덕분에 나중에 특별한 리팩터링 작업을 할 필요가 없으므로 이를 높게 평가한다.

During a code review

코드 리뷰 도중

  • 코드 리뷰는 코드에 퍼블릭 상태에 놓이기 전에 코드를 정리할 수 있는 마지막 기회가 될 수 있다.
  • 이러한 리뷰는 코드 작성자와 함께 수행하는 것이 좋다. 이를 통해 간단한 문제는 빠르게 해결하고, 더 어려운 문제를 수정하는데에 걸리는 시간을 측정할 수 있다.

리팩터링을 어떻게 해야 하는가?

리팩터링은 일련의 작은 변경을 통해 이루어져야 하며, 각 변경은 프로그램이 작동하는 상태는 여전히 유지하면서 기존 코드를 약간 개선하는 방식으로 이루어져야 한다.

올바른 리팩터링을 위한 체크리스트

리팩터링한 코드는 더 깔끔해져야 한다

만약, 리팩터링을 진행한 이후에도 코드가 지저분하다면, 시간 낭비를 한 것이다.

위와 같은 일은, 작은 변경 사항으로 리팩터링하는 것이 아니라, 여러 개의 리팩터링을 하나의 큰 변경사항으로 혼합하려고 할 때 자주 발생한다. 특히, 시간 제한이 있는 경우에는 더더욱 정신을 잃기 쉽다.

하지만, 애초에 매우 엉성한 코드를 리팩터링할 때도 이런 일이 발생할 수 있다. 이 때는 무엇을 개선하든 간에, 코드 전체가 여전히 엉망인 상태가 된다.

이 경우, 코드 일부를 완전히 다시 작성하는 방법을 생각해보는 것이 좋다. 하지만 그 전에 테스트 작성과 함께 충분한 시간을 할애하는 것이 좋다. 그렇지 않다면, 앞선 첫번째 단락에서 말한 것과 동일한 결과가 발생할 가능성이 크다.

리팩터링 이후 새로운 기능이 추가되어선 안된다

리팩터링과 함께 새로운 기능의 개발을 함께 진행하지 마라. 적어도, 각각의 커밋으로 이들 프로세스를 분리하라.

모든 기존의 테스트는 리팩터링 후 여전히 통과해야 한다

리팩터링 이후 테스트가 깨질 수 있는 두 경우가 존재한다.

  • 리팩터링 도중 에러가 생긴 경우 - 이 경우는, 말그대로 에러를 해결하면 된다.
  • 테스트가 너무 로우-레벨인 경우 - ex.) 클래스의 프라이빗 메서드를 테스팅하고 있는 경우
    • 이 경우는 테스트 자체에 책임이 있다고 볼 수 있다. 이런 상황에서는 테스트 자체를 리팩터링하거나, 더 높은 레벨의 새로운 테스트를 작성할 수 있다. 이런 상황을 피하는 가장 좋은 방법은 BDD 스타일의 테스트를 작성하는 것이다.