마틴 파울러는 "언제 리팩터링을 해야하는가"에 대한 대답으로 코드에서 "냄새"가 나는 시점이라고 말하다. ( 정확히는 켄트벡의 표현을 빌린 것이다 )
이들은 많은 코드를 봐왔고, 대체로 리팩토링이 필요한 코드들은 일정한 패턴을 가지고 있다고 한다.
본 장에서는 그 악취나는 패턴들에 대한 소개와 간단한 해결방법을 언급한다. 자세한 해결방법은 뒷장에 자세히 서술되어 있기에, 어떤 패턴이 악취인지에 조금 더 중점을 두고 내용을 정리해보았다.
1. 기이한 이름
함수, 모듈, 변수, 클래스 등은 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있게 이름을 지어야 한다.
마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높다.
2. 중복코드
코드가 중복되면, 중복된 두 개 이상의 코드에 차이가 존재하는지 일일이 살펴봐야하는 부담이 있다. 함수 추출하기 등을 통해 동일한 표현식을 가진 경우는 하나의 함수로 묶는 작업이 필요하다.
3. 긴 함수
프로그래밍 초창기에는 서브루틴을 호출하는 비용이 커서 짧은 함수를 꺼렸다고 한다. 그러나 요즘 언어는 프로세스 안에서의 함수 호출 비용을 거의 없어졌다. 다만, 긴 함수를 짧은 함수 여러 개로 나누면 여러 함수를 교차하면서 코드를 읽어야하는 불편함이 생긴다. 오늘날 IDE 가 많은 부분을 해소하고 있긴 하지만, 가장 좋은 것은 짧게 나뉜 함수의 이름을 명확히 지어서 본문 코드를 보지 않아도 되는 것이다.
만약 리팩터링하고자 하는 긴 함수가 매개변수와 임시 변수를 많이 사용한다면?
오히려 리팩터링하고 나서 코드가 더 난해해질 수 있다. 이 때는 임시 변수를 질의 함수로 만들고 매개변수를 객체화하는 작업부터 하도록 하자.
4. 긴 매개변수 목록
매개 변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많다. ( 나 역시도 매개변수를 3개 이상 갖는 함수를 만드는 것을 꺼리는 편이다 )
매개변수를 질의 함수로 만들거나, 객체를 통째로 넘기는 방식 등을 활용해서 이를 해결할 수 있다.
5. 전역 데이터
전역 데이터의 문제는 코드베이스 어디에서든 건드릴 수 있고 값을 누가 바꾸었는지 찾는 메커니즘을 찾기 어렵다는 점이다. 가장 대표적인 해결책은 변수 캡슐화로, 데이터를 함수로 감싸는 것만으로도 데이터를 수정하는 부분을 쉽게 찾을 수 있고 접근을 통제할 수 있다.
6. 가변 데이터
데이터를 변경하니, 예상치 못한 버그가 발생할 때가 있다. 이런 이유에서 "함수형 프로그래밍"에서는 데이터는 절대 변하지 않고 데이터를 변경하려면 반드시 변경하려는 값에 해당하는 복사본을 만들어서 반환한다는 개념을 근간에 두고 있다. 그럼에도 불구하고, 변수 값을 바꿀 수 있는 언어를 사용하는 프로그래머가 더 많다.
가변 데이터에 의한 코드 불안정성 해소를 위해 변수를 쪼개거나, 문장 슬라이드, 함수 쪼개기 등을 통해 갱신 코드를 분리하는 방식의 리팩터링을 적용해볼 수 있다.
7. 뒤엉킨 변경 (Divergent Change)
뒤엉킨 변경은 SRP가 제대로 지켜지지 않을 때 주로 나타난다. 예를 들어, 금융 상품이 추가될 때마다 또 다른 함수 네 개를 바꿔야하는 모듈이 있다면 뒤엉킨 변경이 발생했을 가능성이 높다. 물론 개발 초기에 맥락 사이의 경계를 뚜렷이 나누기가 어렵고 기능이 변경됨에 따라 경계 역시도 끊임없이 움직이기 때문에 이것이 나타날 수 있다.
8. 산탄총 수술 (Shotgun Surgery)
이 냄새는 코드를 변경할 때마다 자잘하게 수정해야하는 클래스가 많을 때 풍긴다. 뒤엉킨 변경이 한 코드에 여러 의존성이 섞여들어간 경우라면, 산탄총 수술은 여러 코드에 로직이 흩뿌려진 경우이다. 산탄총 수술의 경우, 코드 전반에 걸친 수정이 필요한 사항들을 찾기도 어렵게 꼭 수정해야 할 곳을 지나치면 버그로 이어지기도 한다.
9. 기능 편애 (Feature Envy)
보통은 모듈화할 때, 한 모듈 내의 상호작용은 최대한 늘리고, 외부 모듈간의 상호작용은 최소로 줄이는 데 주력한다. 기능 편애는 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용할 일이 더 많을 때 풍기는 냄새다.
10. 데이터 뭉치 (Data Clumps)
데이터 항목들은 어린아이처럼 서로 어울려 노는 것을 좋아한다. 그래서 데이터 항목 서너 개가 여러 곳에서 항상 함께 뭉쳐다니는 모습을 흔히 목격할 수 있다. 이런 데이터들은 따로 보금자리를 마련해줘야한다.
데이터 뭉치인지 판별하려면 값 하나를 삭제해보자. 그랬을 때 나머지 데이터만으로 의미가 없다면 새로운 객체로 환생시켜주는 것이 좋다.
11. 기본형 집착
이 냄새는 문자열을 다루는 코드에서 특히 흔하다. 전화번호를 단순한 문자 집합으로만 표현하기엔 아쉬움이 많다. 최소한 사용자에게 보여줄 때는 일관된 형식으로 출력해주는 기능이라도 갖춰야 한다. 이런 자료형들을 문자열로만 표현하는 악취는 아주 흔해서, 소위 "문자열화된 변수"라는 이름까지 붙었다고 한다.
12. 반복되는 switch 문
순수한 객체 지향을 신봉하는 사람들은 코드에 등장하는 switch문을 모조리 다형성으로 바꿔 없애야 한다고 주장한다. 지금은 다형성이 널리 자리 잡아서 단순히 switch문을 썼다고 해서 자동으로 검토 대상이 되지는 않는다. 그러니 이제는 똑같은 조건부 로직이 여러 곳에 등장하는지에 집중해볼 필요가 있다.
13. 반복문
책의 초판에서는 탐탁지 않은 대상으로 다뤄졌지만, 지금은 일급 함수를 지원하는 언어가 많아졌기 때문에 반복문을 파이프라인으로 바꾸어 개선할 수 있다.
14. 성의 없는 요소
나에게도 해당되는 이야기다. 보통 코드의 구조를 잡을 때 '프로그래밍 요소'를 이용하는 것을 선호하는데, 그 구조가 필요 없을 때도 있다. 실질적으로 메서드가 하나뿐인 클래스가 그 예시이다. 물론 나중에 본문을 더 채울 생각이었지만 어떠한 사정으로 그렇게 하지 못한 결과일 수도 있다. 사정이 어떠하든 이런 프로그램 요소는 고이 보내드리는 게 좋다.
15. 추측성 일반화
이 냄새는 한 마디로 정의할 수 있다. "나중에 필요할거야"
실제로 사용하게 된다면 다행이지만, 그렇지 않다면 쓸데 없는 낭비이다. 당장 걸리적거리는 코드는 눈앞에서 치워버리자.
16. 임시 필드
간혹 특정 상황에서만 값이 설정되는 필드를 가진 클래스도 있다. 보통 객체를 가져올 때 모든 필드가 채워져 있으리라 기대하는 게 보통이라, 이렇게 임시 필드를 갖도록 작성하면 코드를 이해하기 어렵다. 덩그러니 떨어져 있는 필드들을 발견하면 클래스로 추출하기로 제 살 곳을 찾아주는 것이 좋다.
17. 메시지 체인
다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다. 이 경우, 내비게이션 중간 단계를 수정하면 클라이언트 코드도 수정해야하는 어려움이 생긴다.
18. 중재자
객체 지향의 대표 개념 중 하나는 "위임"이다. 그러나 클래스가 제공하는 메서드 중 절반이 다른 클래서 구현을 위임하고 있다면 좋은 신호가 아니다. 이럴 때는 중개자 제거하기를 활용하여 실제로 일을 하는 객체와 직접 소통하게 하자.
19. 내부자 거래
모듈들 사이에서 데이터를 은밀하게 주고받고 있다면 이 냄새에 해당된다. 공통 부분을 정식으로 처리하는 제 3의 모듈을 만들거나 위임 숨기기를 이용하여 다른 모듈이 중간자 역할을 하게 하자.
20. 거대한 클래스
한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어난다. 그리고 클래스에 필드가 너무 많으면 중복 코드가 생기기 쉽다.
가장 간단한 해법은 그 클래스 안에서 자체적으로 중복을 제거하는 것이다.
21. 서로 다른 인터페이스의 대안 클래스들
인터페이스가 다른 경우 함수 선언을 바꾸거나 함수를 옮겨 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어 넣는다.
22. 데이터 클래스
데이터 클래스란, 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말한다. 이런 클래스에는 public 필드가 있다면 레코드 캡슐화하기로 숨기자. 변경하면 안되는 필드는 세터를 제거해서 접근을 원청 봉쇄한다.
23. 상속 포기
예전에는 자식 클래스가 부모 클래스의 일부만 필요한 경우, 같은 계층에서 서브 클래스를 하나 새로 만들어서, 물려받지 않을 필드를 모두 서브 클래스로 넘기는 방식을 택했다.
하지만 마틴 파울러는 이 방식을 항상 수행해야 하는 건 아니라고 말한다. 일부 동작을 재활용하기 위한 목적으로 상속을 이용할 수 있고 실무 관점에서 아주 유용하다. 냄새가 나긴하지만 참을 만한 정도.
상속 포기 냄새는 서브클래스가 부모의 동작은 필요로하지만, 인터페이스는 따르고 싶지 않을 때 심하게 난다. 구현을 따르지 않는 것은 이해할 수 있지만 인터페이스를 따르지 않는 것은 상당히 무례하다. 이 때는 서브 클래스를 위임으로 바꾸기 등을 활용해 상속 메커니즘에서 벗어나자.
24. 주석
주석은 악취가 아닌 향기를 입힌다. 문제는 주석을 탈취제처럼 사용하는 데 있다. 주석이 장황하게 달린 원인이 코드를 잘못 작성했기 때문인 경우가 의외로 많다.
주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩토링해본다.
'Programming > Refactoring' 카테고리의 다른 글
기본적인 리팩터링 (2) (0) | 2025.04.10 |
---|---|
기본적인 리팩터링 (1) (0) | 2025.04.10 |
테스트 구축하기 (0) | 2025.03.20 |
리팩터링: 첫 번째 예시 (0) | 2025.02.26 |
리팩터링 2판 공부를 시작하며 (0) | 2025.02.24 |