본문 바로가기

Untagged

리팩터링 2판 요약 - 챕터 3

728x90
728x90

코드에서 나는 악취

냄새나면 당장 갈아라. -육아 원칙

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

데이터를 변경했을 때 그 데이터를 참조하는 코드의 오작동으로 예상치 못한 버그가 발생할 수 있다. 드문 조건에서만 나타나면 원인을 찾기 매우 어렵다.

 

"변수 캡슐화 하기"를 통해 정해놓은 함수를 거치도록 하면 변경을 추적하기 쉽다.

 

한 변수에 서로 다른 용도의 값을 저장해야 하는 경우 "변수 쪼개기"로 분리한다.

 

값을 갱신하는경신하는 로직은 다른 코드와 분리하는 것이 좋다. "문장 슬라이드하기"와 "함수 추출하기"를 이용해 무언가를 갱신하는 코드에서 부작용이 없는 코드를 분리한다.

 

API를 만들 때는 "질의 함수와 변경 함수 분리하기"로 필요한 경우에만 변경이 일어날 수 있도록 한다.

 

값을 다른 곳에서 수정할 수 있는 가변 데이터는 야근을 부른다. "파생 변수를 질의 함수 바꾸기"를 통해 개선하도록 하자.

 

"여러 함수를 클래스로 묶기"나 "여러 함수를 변환 함수로 묶기"를 활용해 변수를 갱신하는 코드의 유효 범위를 클래스 수준으로 제한한다. 구조체처럼 내부 필드에 데이터를 담고 있는 변수는 "참조를 값으로 바꾸기"를 사용하여 필드를 수정하지 않고 구조체를 통째로 바꾸는 것이 좋다.

3.7 뒤엉킨 변경 - Divergant Change

뒤엉킨 변경은 단일 책임 원칙을 위반했을 때 발생한다.

 

데이터베이스를 하나 추가할 때마다 변경해야 할 함수가 3개 발생하거나, 금융 상품이 하나 주가되면 변경해야 할 함수가 4개와 같은 경우에 뒤엉킨 변경이 발생했다고 한다.

 

데이터베이스에서 데이터를 가져와 상품 로직에서 처리하는 일처럼 순차적으로 실행되는 게 자연스러운 경우, 필요한 데이터 전달하기 전에 특정 자료구조에 담아 전달하는 식으로 단계를 쪼갠다.(단계 쪼개기)

 

전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 경우는 각 맥락에 적당한 모듈을 구성해 관련 함수를 모아 처리 과정을 맥락별로 구별할 수 있다.(함수 옮기기)

 

여러 맥락에 관여하는 함수가 있다면 "함수 추출하기"를 쓸 수 있다.

 

모듈이 클래스라면 "클래스 추출하기"에서 어떻게 하는지 알아볼 수 있다.

3.8 산탄총 수술 - Shotgun Surgery

산탄총 수술은 코드를 변경할 때마다 수정해야 하는 클래스가 많을 때 발생한다. 변경할 부분이 많다면 찾기도 어렵고 수정하지 못하고 빠트리는 일이 생길 수 있다.

 

"함수 옮기기"와 "필드 옮기기"로 모두 한 모듈에 묶어두면 좋다.

 

비슷한 데이터를 다루는 함수가 많다면 "여러 함수를 클래스로 묶기"를 적용한다.

 

자료구조를 변환하거나 보강(JS에서 객체에 속성을 추가하거나 삭제하는 그것을 의미함)하는 함수는 "여러 함수를 변환 함수로 묶기"를 적용한다.

 

이렇게 묶은 함수들의 결과를 묶어서 다음 단계로 전달할 수 있다면 "단계 쪼개기"를 적용한다.

 

어설프게 분리된 로직은 "함수 인라인하기", "클래스 인라인하기"로 하나로 합쳐 산탄총 수술에 대처할 수 있다. 메서드나 클래스가 당장은  비대해지지만, 나중에 추출 기법으로 더 좋게 분리할 수 있다. 코드를 재구성하는 중간 과정에서는 큰 덩어리로 뭉쳐지는 것을 개의치 않는다.

3.9 기능 편애 - Feature Envy

프로그램을 모듈화 할 때는 코드를 여러 영역으로 나눠, 영역 안에서 이뤄지는 상호작용은 최대화, 영역 사이에서 이뤄지는 상호작용은 최소로 줄여야 한다.

 

기능 편애는 어떤 함수가 자기가 속한 모듈 안에서의 상호작용 보다 다른 모듈의 함수나 데이터와 상호작용이 많을 때를 칭한다.

 

이럴 때는 해당 함수를 상호작용이 많이 일어나는 곳으로 옮겨주면 된다. 함수의 특정 부분만 많이 쓰이는 경우, 그 부분을 분리해서 옮기면 된다.

 

함수에서 사용하는 모듈이 많아 명확하지 않을 경우, 데이터를 가장 많이 가지고 있는 모듈로 옮겨준다.

 

전략 패턴과 방문자 패턴이 이와 반대의 효과를 발휘하는데, 뒤엉킨 변경을 해결하는데 쓰인다.

3.10 데이터 뭉치 - Data Clumps

데이터들은 서너개가 뭉쳐 다니는 경향이 있는데(이하 데이터 뭉치), 이를 "클래스 추출하기"로 하나의 객체로 묶는다. 그리고 "매개변수 객체 만들기"나 "객체 통째로 넘기기"를 통해 함수 호출을 간결하게 만든다.

 

데이터 뭉치인지 판별하는 법은 한 데이터가 없다고 했을 때 나머지를 제대로 사용할 수 없는지 확인하면 된다.

 

이렇게 클래스로 묶게 되면, "기능 편애"를 해결하는 과정에서 함수가 해당 클래스로 통합되어 유용한 클래스로 사용될 수 있다.

3.11 기본형 집착 - Primitive Obsession

전화번호, 화폐나 물리량 따위를 다룰 때는 기본형 데이터만을 사용하는 것이 아닌 클래스로 만들어 관리하도록 한다.

3.12 반복되는 switch - Repeated Switch

책의 1판에서는 switch 사용을 code smell로 규정했지만, 프로그래밍 언어의 발전으로 지금은 무조건적인 문제는 아니다.

 

문제가 되는 경우는 똑같은 조건문이 반복돼서 나타날 때이며, 다형성을 적극적으로 활용해 개선한다.

3.13 반복문

가능하다면 map, reduce같은 함수형 프로그래밍 개념을 적극적으로 사용하도록 한다.(*C#에서는 Linq가 있겠다.)

3.14 성의 없는 요소 - Lazy Element

어떤 이유로 요소(element)라 명명할 구조가 아닐 때, 즉 함수 코드를 그대로 쓰는 것과 별반 차이가 없거나 메서드가 하나만 있는 클래스라면 "함수 인라인하기", "클래스 인라인하기", 상속을 사용했다면 "상속 합치기"를 통해 제거한다.

3.15 추측성 일반화 - Speculative Generality

"나중에 필요할 거야"라는 생각으로 당장은 필요 없지만 만들어 둔 후킹 포인트나 특이 케이스 로직을 만들어 둔 경우 부른다. 당장 걸리적거리는 코드는 치우도록 한다.

 

하는 일이 없는 추상 클래스는 "계층 합치기"로 삭제한다.

 

쓸데없이 위임하는 코드는 "함수 인라인하기" 또는 "클래스 인라인하기"로 지운다.

 

사용되지 않는 매개변수는 "함수 선언 바꾸기"로 지운다.

 

테스트 케이스 외에는 사용하는 곳이 없는 코드가 있을 수 있는데 테스트 케이스 삭제 후 "죽은 코드 제거하기"를 하면 된다.

3.16 임시 필드 - Temporary Field

모든 값이 채워져 있을 거라 기대하는 클래스에서 특정 상황에서만 할당되고 사용하는 임시 필드는 코드 해석에 어려움을 준다. "클래스 추출하기", "함수 옮기기"로 온전히 사용될 클래스를 만들어 준다.

 

임시 필드가 유효한지 검사하는 코드가 있을 수 있다. "특이 케이스 추가하기"로 유효하지 않을 경우를 위한 대체 클래스를 만들고 사용하여 임시 필드를 제거할 수 있다.

3.17 메시지 체인 - Message Chain

간혹 이런 코드가 있을 수 있다.

managerName = aPerson.department.manager.name;

 

aPerson 객체 하위에서 변경이 일어난다면 역시 이를 사용하는 코드도 변경해야만 한다.

 

이런 문제는 "위임 숨기기"로 해결할 수 있지만, 체인을 구성하는 모든 객체에 적용하면 3.18 중개자 문제가 발생할 수 있다. 따라서 최종으로 나온 객체의 역할을 먼저 파악하는 것이 좋다.

 

"함수 추출하기"로 객체를 사용하는 코드를 분리하고 "함수 옮기기"로 체인을 없앨 수 있는지 살펴본다. 체인에 속한 어떤 객체를 여러 클라이언트에서 사용하길 원한다면 전용 메서드를 만든다.

3.18 중개자 - Middle Man

위임(delegation)은 지나치면 문제가 된다. 어떤 클래스 구현의 절반 이상을 다른 클래스에 위임하는 경우가 그렇다.

 

"중개자 제거하기"를 활용해 실제로 일하는 객체와 소통하도록 한다. 위임을 제거한 후 하는 일이 거의 없게 된다면(성의 없는 요소) 인라인 하도록 한다.

3.19 내부자 거래 - Insider Trading

클래스나 모듈 간 응집도가 높으면 좋지 않다.

 

모듈 간 데이터를 긴밀히 교환하는 형태를 발견하면 "함수 옮기기", "필드 옮기기"로 떼어 놓는다.

 

여러 모듈이 같은 것을 원한다면 그 부분을 공통으로 처리하는 모듈을 만들거나 "위임 숨기기"로 다른 모듈이 그 중간 다리 역할을 하게 만든다.

 

부모 자식 상속 구조에서 결합이 생길 수 있는데, "서브클래스를 위임으로 바꾸기", "부모클래스를 위임으로 바꾸기"를 활용한다.

3.20 거대한 클래스 - Large Class

한 클래스가 많은 일을 하면 그에 따라 많은 필드가 생긴다. 그리고 필드가 많으면 중복 코드가 생길 수 있다.

 

"클래스 추출하기"로 서로 어울리는 필드들을 묶자.

 

분리할 컴포넌트가 원래 클래스와 상속 관계를 가지는 게 좋으면 "슈퍼클래스 추출하기", "타입코드를 서브클래스로 바꾸기"를 사용하는 게 더 나을 수 있다.

 

코드량 자체가 많은 클래스는 중복 코드와 헷갈릴 여지가 있다. 자체적으로 중복을 제거하도록 한다.

 

클라이언트에서 특정 부분을 많이 사용하는 경우 "클래스 추출하기", "슈퍼클래스 추출하기", " 타입코드를 서브클래스로 바꾸기" 등을 적용한다.

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

인터페이스를 공유하는 클래스는 필요에 의해 다른 클래스로 변경할 수 있다.

 

여러 클래스가 인터페이스를 공유시키려면 "함수 선언 바꾸기"로 시그니처(이름)를 일치시키고, "함수 옮기기"로 인터페이스가 같아질 때까지 필요한 동작을 클래스에 넣는다.

 

과정에서 중복 코드가 생기면 "슈퍼클래스 추출하기"를 고려할 수 있다.

3.22 데이터 클래스 - Data Class

필드, getter, setter로만 구성된 클래스를 데이터 클래스라 부르는데, public 필드가 있다면 "레코드 캡슐화하기"로 숨긴다.

 

변경하면 안 되는 필드는 "세터 제거하기"를 적용한다.

 

데이터 클래스는 필요한 동작이 엉뚱한 곳에 정의되었다는 신호일 수 있다. 이럴 경우 클라이언트 코드를 데이터 클래스로 옮겨도 큰 효과가 있을 수 있다.

 

"단계 쪼개기"의 결과로 나온 중간 데이터같이 사용 상에서 불변(immutable)하는 데이터 클래스는 public으로 둬도 괜찮다.

3.23 상속 포기 - Refused Bequest

서브 클래스가 슈퍼 클래스의 일부만 원하는 경우 1판에서는 이를 잘못된 구조 설계로 보고 "메서드 내리기", "필드 내리기"를 적용할 것을 권했다.

 

하지만 실무에서는 일부 동작을 재활용하기 위한 상속은 유용하기에, 문제가 큰 경우만 리팩터링 한다.

 

서브 클래스가 부모의 동작은 원하지만 인터페이스를 원하지 않으면 심각한 문제이다. "서브클래스를 위임으로 바꾸기", "슈퍼클래스를 위임으로 바꾸기"를 활용해서 상속에서 벗어나보자.

3.24 주석 - Comments

코드의 특정 부분에 주석이 달려있다면, 처음부터 코드를 잘못 작성했을 경우가 많다.

 

"함수 추출하기"로 해당 부분을 분리한다. 분리했음에도 설명이 필요하다면 이해하기 쉽게 "함수 선언 바꾸기"로 이름을 바꾼다. 함수 동작에 대한 선행 조건이 필요하면 "어서션 추가하기"를 적용할 수 있다.

 

주석을 남기고 싶다는 생각이 들면, 주석이 필요없도록 리팩터링 해보자.

 

주석 자체를 금지하는 것은 아니다. 확실하지 않은 부분, 코드를 이렇게 쓴 이유를 설명하면 협업에 큰 도움이 된다.

728x90
728x90

'Untagged' 카테고리의 다른 글

PS 노베이스의 ICPC 도전 가이드  (1) 2024.01.01
리팩터링 2판 요약 - 챕터 6  (1) 2023.12.17
리팩터링 2판 요약 - 챕터 2  (1) 2023.11.12
[numpy] unravel_index  (0) 2022.01.02