Chapter 03. 코드에서 나는 악취

냄새나면 당장 갈아라

  • 리팩터링을 언제 시작하고 언제 그만할지 판단하는것은 매우 중요 하다.
  • 리팩터링이 필요한 코드는 일정한 패턴이 있어 이것으로 리팩터링의 시작을 알 수 있다.
  • 리팩터링의 종료는 개인의 직관과 경험으로 (만족?) 감을 키운다.

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 뒤엉킨 변경 (Divergent Change)


  • 소스를 수정 할 때는 고쳐야 할 딱 한군데만 고치기를 바란다. 이렇게 할 수 없다면 뒤엉킨 변경과 산탄총 수술중 하나의 악취가 풍긴다.
  • 단일책임원칙이 제대로 지켜지지 않을때 뒤엉킨 변경이 나타난다.
  • 하나의 모듈이 서로 다른 이유들로 인해 여러가지 방식으로 변경될때 나타난다.
  • 데이터를 특정한 데이터 구조에 담아 전달 하도록 단계 쪼개기를 한다
  • 처리 과정을 맥락 별로 분리 하기 위해 함수 옮기기로 맥락 별로 적당한 모듈을 만들어 함수를 모은다.
  • 이 떄 여러 맥락에 관여하는 함수가 있다면 함수 추출하기로 진행하고 클래스하면 클래스 추출하기를 진행 한다.
  • 한코드에 섞여 있는 맥락들을 분리 한다.

3.8 산탄총 수술 (Shotgun Surgery)


  • 뒤엉킨 변경과 비슷하면서도 정반대 이다.
  • 코드를 변경할 떄마다 자잘하게 수정해야 하는게 많을 때 냄새를 풍긴다.
  • 수정해야 할 대상들을 함수 옮기기필드 옮기기로 모으면 좋다.
  • 비슷한 데이터를 다루는 함수가 많다면 여러 함수를 클래스로 묶기를 적용하고 데이터 구조를 변환하거나 보강하는 함수들은 여러 함수를 변환 함수로 묶기를 한다.
  • 출력 결과를 묶어 다음단계로 전달할 수 있담녀 단계 쪼개기를 적용 한다.
  • 어설프게 분리된 로직을 함수 인라인하기클래스 인라인하기로 합치는 것도 괜찮다.
  • 여러 코드에 흩뿌려진 맥락들을 맥락별로 모은다.

3.9 기능 편애 (Feature Envy)


  • 프로그램을 모듈화 할때는 여러 영역으로 나눈 뒤 영역안에서의 상호작용은 최대한 늘리고 영역 끼리의 상호작용은 최소화 한다.
  • 기능 편애는 반대로 자신의 영역안에서의 함수가 다른 영역의 데이터와 함수와 상호작용을 더 많이 할때 풍긴다.
  • 함수 추출하기로 함수를 여러조각으로 나눈후 각각 적합한 모듈로 옮기면 쉽게 해결되는 경우도 많다.

3.10 데이터 뭉치 (Data Clumps)


  • 몰려다니는 데이터뭉치는 보금자리를 따로 마련해줘야 한다.
  • 필드 형태의 데이터 뭉치를 찾아 클래스 추출하기로 하나의 객체로 묶는다.
  • 매개변수 객체 만들기객체 통째로 넘기기로 매개변수 수를 줄인다.
  • 데이터 뭉치가 앞에서 새로 만든ㄴ 객체 필드 중 일부만 사용하여도 상관없다.
  • 데이터 뭉치에서 값 하나를 삭제 하여 나머지 데이터만으로 의미가 없다면 객체로 만들기 적합하다.

3.11 기본형 집착 (Primitive Obsession)


  • 기본형을 객체로 바꾸기를 적용하여 의미있는 자료형들을 생산할 수 있게 한다.
  • 기본형으로 표현된 코드가 조건부 동작을 제어한든 타입코드로 쓰인다면 타입 코드를 서브클래스로 바꾸기조건부 로직을 다형성으로 바꾸기 를 차례로 적용한다.
  • 자주 함께 몰려 다니는 기본형 그룹도 클래스 추출하기매개변수 객체 만들기를 이용하여 데이터 뭉치에서 객체로 환생 시킨다.

3.12 반복되는 switch 문 (Repeated Switches)


  • 똑같은 조건부 로직이 여러곳에서 반복해 등장하는 코드를 집중하여 찾아 보자.
  • 중복된 switch문은 조건절이 추가 될 때 다른 switch문들도 수정해야 하기 때문이다.
  • 이를 다형성으로 변경하자.

3.13 반복문 (Loops)


  • 반복문을 파이프라인으로 바꾸기를 적용하여 반복문을 제거 할 수 있다.
  • filter, map 같은 파이프라인 연산을 사용.

3.14 성의 없는 요소 (Lazy Element)


  • 본문 코드 그대로 쓰는것과 다름 없는 함수
  • 실질적인 메서드가 하나뿐인 클래스
  • 함수 인라인하기클래스 인라인 하기로 처리 한다
  • 상속을 했다면 계층 합치기를 적용한다.

3.15 추측성 일반화 (Speculative Generality)


  • 나중에 필요할 거야.
  • 당장은 필요 없는 후킹포인트 및 특이 케이스 처리 로직을 작성 해둔 코드에서 풍긴다.
  • 이해하거나 관리하기 어려워진 코드 탄생
  • 당장 사용하지 않아 걸리적 거리는 코드는 치워 버리자.
  • 하는 일이 없는 추상클래스는 계층 합치기로 제거. 쓸데 없이 위임하는 코드는 함수 인라인 하기 클래스 인라인하기로 삭제.
  • 본문에서 사용되지 않는 매개변수를 함수 선언 바꾸기로 삭제
  • 테스트코드 말고는 사용하는 곳이 없는 함수나 클래스에서 볼 수 있는데 테스트 케이스를 삭제 한뒤 죽은 코드 제거하기로 삭제

3.16 임시 필드 (Temporary Field)


  • 특정 상황에서만 채워지는 플드를 가진 클래스가 있다면 코드를 이해하기 어렵다.
  • 클래스 추출하기함수 옮기기로 임시 필드들과 관련된 코드를 새 클래스에 몰아 넣는다.
  • 특이 케이스 추가하기로 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거 할 수 있다.

3.17 메시지 체인 (Message Chains)


  • 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드
  • 위임 숨기기로 해결한다..
  • 함수 추출하기로 결과 객체를 사용하는 코드 일부를 따로 빼내고 함수 옮기기로 체인을 숨길 수 있다.

3.18 중개자 (Middle Man)


  • 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있다면 문제가 된다.
  • 중개자 제거하기를 활용하여 실제로 일하는 객체와 직접 소통 한다.

3.19 내부자 거래 (Insider Trading)


  • 모듈사이에 두꺼운 벽을 세웠지만 은밀하게 데이터를 주고 받는 모듈이 있다면 함수 옮기기필드 옮기기로 떼어 놓는다.
  • 여러 모듈이 공통으로 사용하는 부분이 있다면 제3의 모듈을 만들거나 위임 숨기기를 이용하여 다른 모듈의 중간자 역할을 하게 한다.
  • 상속에서도 자식 클래스가 부모 클래스를 필요 이상으로 접근 한다면 서브클래스를 위임으로 바꾸기슈퍼클래스를 위임으로 바꾸기를 활용한다.

3.20 거대한 클래스 (Large Class)


  • 한 클래스가 너무 많은 일을 하려다 보면 필드가 늘어나고 중복 코드도 늘어 난다.
  • 클래스 추출하기로 필드를 일부 따로 묶는다.
  • 한 클래스 안에 접두어와 접미어가 같은 필드들이 함께 추출할 후보
  • 상속관계가 어울린다면 슈퍼클래스 추출하기타입 코드를 서브클래스로 바꾸기를 하는게 더 쉬울수 있다.
  • 가장 간단한 방법은 클래스 안에서 자체적으로 중복 코드를 제거하는 것이다.
  • 또한 클라이언트들이 특정 기능 그룹만 주요 사용한다면 해당 기능들이 개별 클래스로 추출될 후보이다.

3.21 서로 다른 인터페이스의 대안 클래스들


  • 클래스를 사용할 때의 장점은 언제든 다른클래스로 교체 할 수 있다는 것. 단, 인터페이스가 같아야 한다.
  • 함수 선언 바꾸기를 통해 메서드의 시그니처를 일치 시키고 함수 옮기기를 이용하여 인터페이스가 같아 질때까지 클래스에 넣는다.
  • 만약 중복 코드가 생기면 슈퍼 클래스 추출하기를 적용할지 고려 해본다.

3.22 데이터 클래스 (Data Class)


  • 데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스
  • 이런 클래스에 public필드가 있다면 레코드 캡슐화하기로 숨기자
  • 변경하면 안되는 필드는 세터 제거하기로 접근을 못하게 한다
  • 다른 클래스에서 데이터 클래스의 세터/게터를 사용하는 메서드를 찾아 함수 옮기기함수 추출하기 를 이용하여 별도 메서드로 뽑아 낸다.
  • 단계 쪼개기 의 결과와 같은 불변데이터는 게터를 통하지 않고 필드 자체를 공개 해도 된다.

3.23 상속 포기 (Refused Bequest)


  • 자식클래스가 부모의 동작은 필요하지만 인터페이스는 따르고 싶지 않을때 냄새가 심하게 난다.
  • 서브클래스를 위임으로 바꾸기슈퍼클래스를 위임으로 바꾸기를 활용하자

3.24 주석 (Comments)


  • 특정 코드 블록이 하는일에 주석을 남기고 싶다면 함수 추출하기를 적용
  • 이미 추출되어진 함수에는 함수 선언 바꾸기로 함수 이름 변경
  • 선행 조건을 명시 하고 싶다면 어서션 추가하기
  • 주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링 해본다.
  • 뭫ㄹ 할지 모를때라면 주석을 달아 놓는게 좋다
  • 진행 사항뿐 아니라 확실하지 않은 부분에도 주석을 남긴다
  • 건망증이 심한 프로그래머에게 도움이 된다

    나야 나!

Comments