기이한 이름 (Mysterious Name)
- 이름만 잘 지어도 나중에 문맥 파악하느라 헤매는 시간을 크게 절약할 수 있다.
- 마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 있을 가능성이 높다.
중복 코드 (Duplicated Code)
- 똑같은 코드 구조가 여러 곳에서 반복될 때 하나로 통합한다.
- ex. 한 클래스에 딸린 두 메서드가 똑같은 표현식을 쓰는 경우 통합하기
긴 함수 (Long Function)
- 짧은 함수로 구성된 코드를 이해하기 쉽게 만드는 확실한 이름은 ‘좋은 이름’이다.
- 이를 위해서는 적극적으로 함수를 쪼개야 한다.
무엇을 하는지
코드가 잘 설명해주지 못할수록 함수로 만드는 것이 유리하다.
긴 매개변수 목록 (Long Parameter List)
- 사용 중인 데이터 구조에서 값들을 뽑아 객체로 묶어 전달하면 목록을 줄일 수 있다.
- 클래스는 특히 여러 개의 함수가 특정 매개변수를 공통으로 사용할 때 유용하다.
- 여러 함수를 클래스로 묶어, 공통 값들을 클래스 필드로 정의하면 된다.
- 함수형 프로그래밍이라면
partially applied function
들을 생성한다.
전역 데이터 (Global Data)
- 다른 코드애 영향을 줄 가능성이 있는 데이터를 볼 때마다 변수 캡슐화를 사용하자.
- 이런 데이터를 함수로 감싸는 것만으로도 데이터 수정 부분을 쉽게 찾을 수 있다.
- 더 나아가 접근자 함수들을 클래스나 모듈에 넣고 그 안에서만 사용할 수 있도록 해도 좋다.
가변 데이터 (Mutable Data)
- 데이터를 변경하면 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다.
- 따라서 함수형 프로그래밍에서는 불변 데이터를 기본으로, 수정하려면 반드시 복사본으로 반환한다.
- 불가피하다면 변수의 유효범위를 최소화하자.
- 일반적으로 참조를 값으로 바꿔, 내부 필드를 직접 수정하지 않고 구조체를 통째로 교체하는 게 낫다.
뒤엉킨 변경 (Divergent Change)
- 단일 책임 원칙(SRP)이 지켜지지 않을 때 발생한다.
- SRP는 단일 모듈의 변경 이유는 오직 하나여야 한다는 원칙이다.
- 하나의 모듈이 서로 다른 이유들로 인해 여러 방식으로 변경되는 일이 많을 때 발생한다.
- 여러 맥락의 일에 관여하는 함수가 있다면 함수를 추출해보자.
산탄총 수술 (Shotgun Surgery)
- 변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 지나치기 쉽다.
- 따라서 변경되는 대상(함수, 필드…)들을 모두 한 모듈에 묶어두면 좋다.
- 작은 함수와 클래스에 지나칠 정도로 집착할 필요가 없다.
기능 편애 (Feature Envy)
- 어떤 함수가 자기가 속한 모듈의 함수/데이터보다 다른 모듈과 상호작용할 일이 많을 때 발생하는 문제다.
- 그 부분을 해당 데이터 근처로 함수를 옮기면 된다.
- 함수 일부에서만 기능이 편애된다면, 그 부분만 독립 함수로 빼서 원하는 모듈로 옮긴다.
데이터 뭉치 (Data Clumps)
- 필드 형태의 데이터 뭉치를 찾아 클래스로 추출해 하나의 객체로 묶는다.
- 메서드 시그니처에 있는 데이터 뭉치는 매개변수를 객체로 묶어 간결하게 줄여본다.
- 데이터 뭉치인지 판별하는 방법은 값 하나를 삭제해보면 된다.
- 이때 나머지 데이터만으로는 의미가 없다면 객체로써 의미가 있는 뭉치이다.
기본형 집착 (Primitive Obsession)
- 주어진 문제에 딱 맞는 기초 타입을 직접 정의하는 것을 꺼리는 사람들이 많다.
- 하지만 화폐, 좌표, 구간 같은 타입들은 그 단위를 생각해서 처리하자.
반복되는 switch문
- 모든 switch문을 다형성으로 바꾸자는 의견도 있다.
- 중복된 switch문은 조건절을 하나 추가할 때마다 다른 switch문도 찾아서 바꿔야 한다.
- 따라서 조건 case가 너무 많이 나온다면 다형성을 기반으로 수정해보자.
반복문
- 복잡한 반복문을 파이프라인으로 바꾸자.
- filter, map…
성의 없는 요소 (Lazy Element)
- 본문 코드를 그대로 쓰는 함수나 실질 메서드가 하나 뿐인 클래스 등은 고이 보내주자.
- 나중에 더 추가할 생각이었으나 현재는 그렇지 않으면, 어떤 사정이건 고이 보내주자.
추측성 일반화 (Speculative Generality)
- ‘나중에 필요할 거야’라는 생각으로 당장은 필요 없는 모든 종류의 코드이다.
- 사용하는 곳이 없는 함수나 클래스에서 흔히 볼 수 있는 문제다.
- 테스트 케이스부터 삭제한 뒤에 제거해야 한다.
임시 필드 (Temporary Field)
- 특정 상황에서만 값이 설정되는 필드가 있을 때 볼 수 있는 문제다.
- 임시 필드와 관련된 코드는 클래스 추출 및 함수 옮기기를 통해 옮기거나 제거하자.
메시지 체인 (Message Chains)
managerName = aPerson.department.manager.name;
// 아래처럼 사용해서 체인을 숨길 수 있다.
reportAutoGenerator.report(aPerson);
- 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다.
- 이를 리팩터링하기 위해서는 최종 결과 객체가 어떻게 쓰이는지부터 살펴보자.
- 이후 결과 객체를 사용하는 코드 일부를 따로 빼서 체인을 숨길 수 있는지 살펴보자.
흠! 개인적으로 optional chaining이 나와서 체이닝해도 괜찮다는 생각임
중개자 (Middle Man)
- 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있다면 문제다.
- 중개자를 제거해서 실제로 일을 하는 객체와 직접 소통하게 해야 한다.
- 위임 메서드 제거 후 남는 일이 거의 없다면 호출하는 쪽으로 인라인하면 된다.
내부자 거래 (Insider Trading)
- 상속 구조에서 자식이 부모 클래스가 공개하는 것 이상으로 알려고 할 때 문제가 된다.
- 필드가 너무 많은 것뿐만 아니라 코드량이 너무 많은 클래스도 혼동을 일으킬 여지가 크다.
- 가장 간단한 해법은 클래스 안에서 자체적으로 중복을 제거하는 것이다.
서로 다른 인터페이스의 대안 클래스들
- Alternative Classes with Different Interfaces
- 클래스를 사용할 때의 가장 큰 장점은 필요에 따라 언제든 다른 클래스로 교체할 수 있다는 점이다.
- 그러나 교체하려면 인터페이스가 같아야 한다.
- 따라서 메서드 시그니처를 일치시키고, 필요 동작들을 클래스 안으로 넣고, 중복이 생기면 슈퍼클래스를 추출한다.
데이터 클래스 (Data Class)
- 데이터 필드와 게터/세터 메서드로만 구성된 클래스다.
- 데이터 저장 용도로만 쓰이다 보니 다른 클래스가 너무 깊이 함부로 다룰 때가 많다.
- 이런 클래스에 public 필드가 있다면 꼭 레코드 캡슐화하자.
- 변경하면 안 되는 필드는 세터를 제거해서 접근을 원천 봉쇄하자.
상속 포기 (Refused Bequest)
- 서브클래스는 부모로부터 메서드와 데이터를 물려받는데, 부모의 유산이 필요 없다면?
- 같은 계층에 서브클래스를 새로 만들고 물려받지 않을 부모 코드를 모두 그 서브클래스로 넘긴다.
- 그러면 부모에는 공통된 부분만 남게 된다.
- 서브클래스가 부모 동작은 필요하지만 인터페이스는 따르고 싶지 않을 때?
- 이때는 서브클래스를 위임으로 바꾸거나
- 슈퍼클래스를 위임으로 바꿔서 아예 상속에서 벗어나자.
주석
- 주석은 악취가 아닌 향기인데, 문제는 탈취제처럼 사용하는 데 있다.
- 주석을 남겨야겠다면 가장 먼저 주석이 필요 없는 코드로 리팩터링해본다.
- 뭘 할지 모를 때라면 주석을 달아두면 좋다.