본문 바로가기
Book/Refactoring 2nd

3. 코드에서 나는 악취

by Soono991 2023. 1. 8.

이 포스팅은 리팩터링 2판으로 학습한 내용을 토대로 정리한 포스팅입니다.

리팩터링을 언제 시작하고 언제 그만할지를 판단하는 일은 리팩터링의 작동 원리를 아는 것 못지않게 중요합니다.

하지만 이런 일들을 '언제' 해야 하는지에 대해서는 명확하게 정립된 규칙이 없습니다.

마틴 파울러 님도 명확하게 규칙을 제시하지는 않고 아래와 같은 말로 대신했습니다.

냄새나면냄새나면 당장 갈아라.
- 켄트 벡 할머니의 육아 원칙

냄새는 일종의 코드가 풍기는 악취라고 볼 수 있으며, 냄새가 나는 코드로써 24가지 예시를 듭니다.

3.1 기이한 이름 (Mysterious Name)

코드는 단순하고 명료하게 작성해야 하며, 코드를 명료하게 표현하는 데 가장 중요한 요소는 바로 '이름'입니다.

그래서 그 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있도록 엄청나게 신경 써서 이름을 지어야 합니다.

마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높습니다.

우스갯소리로 개발자에게 가장 어려운 것은 바로 변수 이름 짓기라고 할 정도로 이름 짓기는 매우 중요하며 또 어렵습니다.

아예 변수명을 지어주는 사이트까지 존재할 정도입니다.

3.2 중복 코드 (Duplicated Code)

똑같은 코드가 여러 곳에서 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있습니다.

3.3 긴 함수 (Long Function)

간접 호출의 효과, 즉 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점은 함수를 짧게 구성할 때 나오는 것입니다.

3.4 긴 매개변수 목록 (Long Parameter List)

매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많습니다.

사용 중인 데이터 구조에서 값들을 뽑아 각각을 별개의 매개변수로 전달하는 코드라면 객체 통째로 넘기기를 적용해서 원본 데이터 구조를 그대로 전달하여 매개변수를 최소화합니다.

3.5 전역 데이터 (Global Data)

전역 데이터는 코드베이스 어디에서든 건드릴 수 있고 값을 누가 바꿨는지 찾아낼 메커니즘이 없다는 게 문제입니다.

파라켈수스가 말하길 '무엇이든 많이 복용하면 독이 될 수 있다'라고 합니다. 전역 데이터가 조금뿐이라면 감당할 수 있겠지만, 많아지면 걷잡을 수 없게 됩니다.

3.6 가변 데이터 (Mutable Data)

값이 다른 곳에서 설정할 수 있는 가변 데이터가 풍기는 악취는 특히 고약합니다. 혼동과 버그와 야근을 부를 뿐만 아니라, 쓸데없는 코드이기도 합니다.

3.7 뒤엉킨 변경 (Divergent Change)

뒤엉킨 변경은 단일 책임 원칙(SRP)이 제대로 지켜지지 않을 때 나타납니다.

즉, 하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경되는 일이 많을 때 발생합니다.

3.8 산탄총 수술 (Shotgun Surgery)

산탄총 수술은 뒤엉킨 변경과 비슷하면서도 정반대입니다.

이는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍깁니다.

변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽습니다.

3.9 기능 편애 (Feature Envy)

기능 편애는 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용 할 일이 더 많을 때 풍기는 냄새입니다.

3.10 데이터 뭉치 (Data Clumps)

데이터 항목들은 어린아이 같은 면이 있습니다. 서로 어울려 노는 걸 좋아합니다. 그래서 데이터 서너 개가 여러 곳에서 항상 함께 뭉쳐 다니는 모습을 흔히 목격할 수 있습니다. 이렇게 몰려다니는 데이터 뭉치는 보금자리(클래스, 객체)를 따로 마련해줘야 합니다.

3.11 기본형 집착 (Primitive Obsession)

금액을 그냥 숫자형으로 계산하거나, 물리량을 게산할 때도 밀리미터나 인치 같은 단위를 무시하고, 범위도 if (a < upper && a > lower) 처럼 처리하는 코드를 수없이 봤습니다.

또한 전화번호를 단순히 문자열로 표현하기에는 아쉬움이 있습니다.

3.12 반복되는 switch문 (Repeated Switches)

중복된 switch문은 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아서 함께 수정해야 하기 때문에, 이럴 때는 다형성을 이용해 리팩토링 해야 합니다.

3.13 반복문 (Loops)

지금은 일급 함수를 지원하는 언어가 많아졌기 때문에 필터(filter)나 맵(map) 같은 파이프라인 연산을 사용하는 것이 좋습니다.

3.14 성의 없는 요소 (Lazy Element)

실질적으로 메서드가 하나뿐인 클래스나, 리팩터링을 거치면서 역할이 줄어든 클래스도 있을 수 있습니다.

이런 경우에는 함수 인라인, 클래스 인라인, 계층 합치기를 통해 리팩터링 합니다.

3.15 추측성 일반화 (Speculative Generality)

'나중에 필요할 거야'라는 생각으로 당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 경우입니다. YAGNI가 이에 해당합니다.

3.16 임시 필드 (Temporary Field)

특정 상황에서만 값이 설정되는 필드(임시 필드)를 가진 클래스가 있는 경우, 사용자는 쓰이지 않는 것처럼 보이는 필드가 존재하는 이유를 파악하느라 머리를 싸매게 됩니다.

3.17 메시지 체인 (Message Chain)

메시지 체인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말합니다.

이런 코드는 오류가 발생했을 때 정확히 어느 곳에서 발생했는지 파악하기가 어렵습니다.

3.18 중개자 (Middle Man)

클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있는 경우 해당합니다.

이 경우 위임 메서드를 제거하여 실제로 일을 하는 객체와 직접 소통하게 합니다.

3.19 내부자 거래 (Insider Trading)

모듈 사이의 데이터 거래가 많으면 결합도가 높아집니다. 일이 돌아가게 하려면 거래가 이뤄질 수밖에 없지만, 그 양을 최소로 줄이고 모두 투명하게 처리해야 합니다.

3.20 거대한 클래스 (Large Class)

한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어납니다. 그리고 클래스에 필드가 너무 많으면 중복 코드가 생기기 쉽습니다.

3.21 서로 다른 인터페이스의 대안 클래스들 (Alternative Classes with Different Interfaces)

클래스를 사용할 때의 큰 장점은 필요에 따라 언제든 다른 클래스로 교체할 수 있다는 것입니다.

단, 교체하려면 인터페이스가 같아야 합니다. (다형성)

3.22 데이터 클래스 (Data Class)

데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말합니다.

그저 데이터 저장 용도로만 쓰이다 보니 다른 클래스가 너무 깊이까지 함부로 다룰 때가 많습니다.

3.23 상속 포기 (Refused Bequest)

주로 서브클래스가 부모의 동작은 필요로 하지만 인터페이스는 따르고 싶지 않을 때 발생합니다.

3.24 주석 (Comments)

주석을 달면 안 된다는 것은 아니지만, 주석을 탈취제처럼 사용할 때가 문제입니다.

주석이 장황하게 달린 원인이 코드를 잘못 작성했기 때문인 경우가 의외로 많습니다.

주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링 해본다.

 

3장은 리팩터링을 하기 전에 어떤 코드, 상황에서 리팩터링을 하면 좋은지 리팩터링 대상이 될 수 있는 24가지 상황을 예로 들었습니다.

24가지를 전부 외울 필요는 없다고 생각합니다. 하지만 각각의 항목들이 어떤 의미에서 리팩터링 대상이 되는지에 대해서는 알아야 한다고 생각합니다.

'Book > Refactoring 2nd' 카테고리의 다른 글

6~12. 리팩터링  (0) 2023.01.08
4. 테스트 구축하기  (0) 2023.01.08
2. 리팩터링 원칙  (0) 2023.01.08
1. 리팩터링: 첫 번째 예시  (0) 2023.01.08

댓글