본문 바로가기

TIL

Refactoring. 리팩터링이 필요한 경우

저번 장에서도 간단하게 언제 리팩터링이 필요한지 간단하게 정리했지만 3장에서는 필요한 경우를 좀 더 디테일하게 다룬다. 이 부분은 익숙해질 때까지 자주 와서 읽어야겠다.  시작해 보자

코드를 명료하게 표현하는 데 가장 중요한 요소 하나는 이름이다. 함수, 모듈, 변수, 클래스 등은 그 이름만 보고도 각각이 무슨 일을 하고 있는지 어떻게 사용해야 하는지 명확히 알 수 있도록 신경 써서 이름을 지어야 한다.

마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높다. 그래서 혼란스러운 이름을 잘 정리하다  보면 코드가 훨씬 간결해질 때가 많다.

다음으로 필요한 경우는 코드가 중복되는 경우다. 코드가 중복되면 각각을 볼 때마다 서로 차이점은 없는지 주의 깊게 살펴봐야 하는 부담이 생긴다. 그중 하나를 변경할 때는 다른 비슷한 코드들도 모두 살펴보고 적절히 수정해야 한다.

여기에 해당하는 기법으로는 함수 추출하기, 문장 슬라이드하기, 메서드 올리기 등이 있다.

오랜 기간 잘 활용되는 프로그램들은 하나같이 짧은 함수로 구성됐다. 예전에는 서브루틴을 호출하는 비용이 컸기 때문에 짧은 함수를 꺼렸다. 하지만 요즘 언어는 프로세스 안에서의 함수 호출 비용을 없애버렸다. 또한 개발 환경에서 호출부와 선언부를 빠르게 이동하거나 동시에 보여주는 기능이 있어서 편해졌다. 짧은 함수로 구성된 코드를 이해하기 쉽게 만드는 가장 확실한 방법은 좋은 이름이다. 함수 이름을 잘 지어두면 본문 코드를 볼 이유가 사라진다.

함수 본문에는 원래 주석으로 설명하려던 코드가 담기고, 함수이름은 동작 방식이 아닌 의도가 드러나게 짓는다. 이렇게 함수로 묶는 코드는 여러 줄일 수도 있고 단 한 줄일 수도 있다. 심지어 원래 코드보다 길어지더라도 함수로 뽑는다. 단, 함수 이름에 코드의 목적을 드러내야 한다. 여기서 핵심은 함수의 길이가 아닌, 함수의 목적(의도) 과 구현 코드의 괴리가 얼마나 큰 가다. 즉, '무엇을 하는지'를  코드가 잘 설명해주지 못할수록 함수로 만드는 게 유리하다.

함수를 짧게 만드는 작업의 대부분은 '함수 추출하기'가 차지 한다고 한다. 다른 기법들도 많은데 나머지는 뒤에서 내용 나오면 따로 정리하겠다. 

그렇다면 추출할 코드 덩어리는 어떻게 찾아낼까? 한 가지 좋은 방법은 주석을 참고하는 것이다. 주석은 코드만으로는 목적을 이해하기 어려운 부분에 달려 있는 경우가 많다. 이런 주석을 찾으면 주석이 설명하는 코드와 함께 함수로 빼내고, 함수 이름은 주석 내용을 토대로 짓는다. 코드가 단 한 줄이어도 따로 설명할 필요가 있다면 함수로 추출하는 게 좋다.

전역 데이터를 주의해야 한다는 말은 유명하다. 그래서 보통 함수 매개변수로 필요한것을 전달했는데, 이게 길어지면 이해하기가 힘드니 다양한 리팩터링 기법으로 해결해 준다. 질의 함수로 바꿔준다던지, 객체로 만든다던지, 등이 있다. 또한 전역 변수를 방지하기 위한 대표적인 리팩터링은 변수 캡슐화하기다.

데이터를 변경했더니 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다. 이런 이유로 함수형 프로그래밍에서는 데이터는 절대 변하지 않고, 데이터를 변경하려면 반드시 변경하려는 값에 해당하는 복사본을 만들어서 반환한다는 개념을 기본으로 삼고 있다. 하지만 모든 언어가 이렇지 않기 때문에 다른 리팩터링 방식으로 해결할 수 있다. 위에서 말한 변수 캡슐화하기나 하나의 변수에 용도가 다른 값들을 저장하느라 값을 경신하는 경우라면 변수 쪼개기를 이용하여 용도별로 독립 변수에 저장하게 하여 값 경신이 문제를 일으킬 여지를 없앤다. API를 만들 때는 질의 함수와 변경 함수 분리하기를 활용해서 꼭 필요한 경우가 아니라면 부작용이 있는 코드를 호출할 수 없게 한다.

코드를 수정할 때는 시스템에서 고쳐야 할 딱 한 군데를 찾아서 그 부분만 수정할 수 있기를 바란다. 이렇게 할  수 없다면 divergent change  와 shotgun surgery 중 하나의 경우라 한다. 뒤엉킨 변경은 SRP(단일 책임 원칙) 이 제대로 지켜지지 않을 때 나타난다. 즉, 하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경되는 일이 많을 때 발생한다. 예를 들어 지원해야 할 데이터베이스가 추가될 때마다 함수 세 개를 바꿔야 하고, 금융 상품이 추가될 때마다 또 다른 함수네 개를 바꿔야 하는 모듈이 있다면 divergent change 발생했다는 뜻이다. 데이터베이스 연동과 금융 상품 처리는 서로 다른 맥락에서 이뤄지므로 독립된 모듈로 분리해야 프로그래밍이 편하다. 데이터베이스에서 데이터를 가져와서 금융 상품 로직에서 처리해야 하는 일처럼 순차적으로 실행되는 게 자연스러운 맥락이라면, 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 식으로 단계를 분리한다. 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 하는 호출하는 빈도가 높다면, 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모은다. 그러면 처리 과정이 맥락 별로 구분된다. 이때 여러 맥락의 일에 관여하는 함수가 있다면 옮기기 전에 함수 추출하기부터 수행한다.

다음으로 위에서 말한 shotgun surgery 를 보자. 이건 divergent change와 비슷하지만 정반대이다. 냄새나는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍긴다. 변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽다. 이럴 때는 함께 변경되는 대상들을 함수 옮기기와 필드 옮기기로 모두 한 모듈에 묶어두면 좋다고 한다. 만약 비슷한 데이터를 다루는 함수가 많다면 여러 함수를 클래스로 묶기를 적용한다고 한다.

 

뒤에 추가적으로 여러 경우들이 나오는데, 읽어보니 위에서 말한것처럼 시간 날 때마다 읽어서 익숙해지는 게 중요할 거 같다.

 

 

 

 

 

참고 및 출처: <리팩터링 2판> (마틴 파울러 지음, 개앞맵시, 남기혁 옮김, 한빛미디어, 2020)