3.1 기이한 이름

  • 코드는 단순하고 명료하게 작성해야 함
  • 함수, 모듈, 변수, 클래스 등은 그 이름만 보고도 각각의 역할을 알 수 있도록 이름 지어야 함
  • 이름 짓기는 프로그래밍에서 가장 어렵기로 손꼽히는 일 중 하나임

3.2 중복 코드

똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합할 수 있음

  • 한 클래스에 딸린 두 메서드가 똑같은 표현식을 사용하는 경우 → 함수 추출하기
  • 코드가 비슷하긴 한데 완전히 똑같지는 않다면 → 문장 슬라이드하기
  • 같은 부모로부터 파생된 서브 클래스들에 코드가 중복되어 있다면 → 메서드 올리기

3.3 긴 함수

  1. 짧은 함수들은 간접 호출의 효과를 낼 수 있다. 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점이 있음
  2. 짧은 함수로 구성된 코드를 이해하기 쉽게 만들기 위해서는 함수 이름을 잘 지어두어야 함 적극적으로 함수를 쪼개고, 함수 이름은 동작 방식이 아닌 ‘의도’가 드러나게 지어야 함

    • 함수 추출하기
    • 임시 변수를 질의 함수로 바꾸기
    • 매개변수 객체 만들기
    • 객체 통째로 넘기기
    • 함수를 명령으로 바꾸기
    • 조건문 분해하기
    • case문마다 함수 추출하기
    • 조건부 로직을 다형성으로 바꾸기
    • 반복문 쪼개기

3.4 긴 매개변수 목록

  • 매개변수를 질의 함수로 바꾸기
  • 객체 통째로 넘기기
  • 매개변수 객체 만들기
  • 플래그 인수 제거하기
  • 여러 함수를 클래스로 묶기

3.5 전역 데이터

  • 전역 변수를 제거할 수 있는 대표적인 방법은 변수 캡슐화
  • 데이터를 함수로 감싸 데이터를 수정하는 부분을 쉽게 찾을 수 있도록 만들고, 접근을 통제할 수 있게 됨
  • 나아가 접근자 함수들을 클래스나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위를 최소로 줄이는 것도 좋음

3.6 가변 데이터

  1. 변수 캡슐화하기

    • 정해놓은 함수를 거쳐야만 값을 수정할 수 있도록 함
  2. 변수 쪼개기

    • 하나의 변수에 용도가 다른 값들을 저장하느라 값을 갱신하는 경우
  3. 문장 슬라이드하기 & 함수 추출하기

    • 무언가를 갱신하는 코드로부터 부작용이 없는 코드를 분리
  4. 질의 함수와 변경 함수 분리하기

    • 부작용이 있는 코드를 호출할 수 없게 만듬
  5. 세터 제거하기

    • 변수의 유효범위를 줄일 수 있음

이 밖에도 파생 변수를 질의 함수로 바꾸기, 여러 함수를 클래스 또는 변환 함수로 묶기, 참조를 값으로 바꾸기 등을 적용할 수 있음


3.7 뒤엉킨 변경

  • 뒤엉킨 변경은 단일 책임 원칙(SRP)이 제대로 지켜지지 않을 때, 즉 하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경되는 일이 많을 때 발생 함
  • 일을 순차적으로 진행하는 ‘단계 쪼개기’와 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모으는 ‘함수 옮기기’를 적용할 수 있음
  • 여러 맥락의 일에 관여하는 함수는 옮기기 전에 ‘함수 추출하기’를 수행하고, 모듈이 클래스라면 ‘클래스 추출하기’를 사용할 수 있음

3.8 산탄총 수술

  • 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많은 경우
  • 이럴 때는 함께 변경되는 대상들을 함수 옮기기와 필드 옮기기로 모두 한 모듈에 묶어두면 좋음
  • 여러 함수를 클래스나 변환 함수로 묶을 수 있고, 단계 쪼개기를 적용할 수 있음
  • 어설프게 분리된 로직을 인라인 함수나 인라인 클래스와 같은 인라인 리팩터링으로 하나로 합치는 것도 좋은 방법

3.9 기능 편애

  • 기능 편애는 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용할 일이 더 많을 때 발생하는 문제
  • 이럴 때는 함수 추출하기와 함수 옮기기를 통해 함수를 데이터와 가까운 곳으로 옮김
  • 함수가 사용하는 모듈이 다양하다면, 가장 많은 데이터를 포함한 모듈로 옮기거나, 함수 추출하기로 함수를 여러 조각으로 나눈 후 각각을 적합한 모듈로 옮길 수 있음
  • 전략 패턴과 방문자 패턴을 적용하여 같은 데이터를 다루는 코드를 한 곳에서 변경할 수 있도록 옮겨준다면, 오버라이드해야 할 소량의 동작 코드를 각각의 클래스로 격리해주므로 수정이 쉬워 짐

3.10 데이터 뭉치

  • 데이터 여러 개가 클래스 두어 개의 필드에서, 혹은 여러 메서드의 시그니처에서 함께 발견되는 경우가 있음
  • 이럴 때는 가장 먼저 필드 형태의 데이터 뭉치를 찾아서 클래스 추출하기로 하나의 객체로 묶고 다음 매개변수 객체 만들기나 객체 통째로 넘기기를 적용해 매개변수 수를 줄여서 상당한 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시킬 수 있음

3.11 기본형 집착

  • 프로그래머 중에는 자신에게 주어진 문제에 딱 맞는 기초 타입을 직접 정의하기를 몹시 꺼리는 사람이 많음
  • 기본형을 객체로 바꾸면 데이터의 타입을 의미 있는 자료형으로 바꿀 수 있음
  • 기본형으로 표현된 코드가 조건부 동작을 제어한다면, 타입 코드를 서브클래스로 바꾸거나 조건부 로직을 다형성으로 바꿀 수 있음
  • 자주 함께 몰려다니는 기본형 그룹은 클래스 추출과 매개변수 객체화를 이용할 수 있음

3.12 반복되는 switch문

  • switch문은 조건부 로직을 다형성으로 바꿔 대체할 수 있음

3.13 반복문

반복문을 파이프라인으로 바꿔 제거할 수 있음 (ex. filter, map 등)


3.14 성의 없는 요소

필요 없는 프로그램 요소라면 인라인 함수나 클래스, 또는 계층 합치기(상속의 경우)를 적용하여 제거

💡 프로그램 요소 프로그래밍 언어가 제공하는 함수(메서드), 클래스, 인터페이스 등 코드 구조를 잡는 데 활용되는 요소


3.15 추측성 일반화

  • ‘나중에 필요할 거야’라는 생각으로 당장은 필요없는 모든 종류의 후킹(hooking) 포인트와 특이 케이스 처리 로직을 작성해둔 경우
  • 계층 합치기, 인라인 함수 및 클래스, 함수 선언 바꾸기로 불필요한 코드를 제거
  • 테스트 코드에만 사용되는 함수나 클래스는 테스트 케이스부터 삭제한 뒤 죽은 코드를 제거

3.16 임시 필드

  • 특정 상황에서만 값이 설정되는 필드를 가진 클래스의 경우, 쓰이지 않는 것처럼 보이는 필드를 이해하기 어려움
  • 클래스 추출하기와 함수 옮기기로 임시 필드들을 정리해 줌
  • 특이 케이스를 추가하여 필드의 유효성 검사를 위한 대안 클래스를 분리해줄 수도 있음

3.17 메시지 체인

  • 메시지 체인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드
  • 클라이언트가 객체 내비게이션 구조에 종속된 상황으로, 내비게이션 중간 단계를 수정하면 클라이언트 코드도 수정해야 함
  • 이는 위임 숨기기로 해결 가능 최종 결과 객체가 어떻게 쓰이는지부터 살펴보고, 함수 추출하기와 함수 옮기기로 체인을 숨겨야 함

3.18 중개자

  • 객체는 캡슐화를 통해 외부로부터 세부사항을 숨겨줄 수 있음
  • 캡슐화의 과정에는 위임이 자주 활용됨
  • 하지만 캡슐화를 남용하는 경우 (ex. 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임한다면) 중개자를 제거하여 실제로 일을 하는 객체와 직접 소통하게 만들어야 함

3.19 내부자 거래

  • 모듈 사이의 데이터 거래가 많아 결합도가 높아지는 문제가 있다면, 함수 옮기기와 필드 옮기기로 떼어놓음
  • 여러 모듈이 같은 관심사를 공유한다면 제3의 모듈을 만들거나 위임 숨기기를 이용하여 다른 모듈이 중간자 역할을 하게 만듦
  • 상속 구조에서 부모 자식 간에 결탁이 생긴다면, 서브 클래스를 위임으로 바꾸거나 슈퍼클래스를 위임으로 바꿀 수 있음

3.20 거대한 클래스

  • 클래스 추출하기로 필드 일부를 따로 묶어서 분리할 컴포넌트를 원래 클래스와 상속 관계로 만든다면, 슈퍼클래스 추출이나 타입 코드를 서브클래스로 바꾸는 방법을 적용 -코드량이 너무 많은 클래스에 대한 해법은 그 클래스 안에서 자체적으로 중복을 제거하는 것이 좋음
  • 클라이언트가 거대 클래스를 이용하는 패턴을 파악하여 클래스 추출, 슈퍼클래스 추출, 타입 코드를 서브클래스로 바꾸기 등을 활용하여 여러 클래스로 분리함

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

  • 클래스를 다른 클래스로 교체할 때는 인터페이스가 같아야 함
  • 함수 선언 바꾸기로 메서드 시그니처를 일치시키거나, 함수 옮기기로 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어넣음
  • 대안 클래스들 사이에 중복이 생기면 슈퍼클래스를 추출하는 방법을 고려함

3.22 데이터 클래스

  • 데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말함
  • 이런 클래스에 public 필드가 있다면 레코드 캡슐화로 숨기고, 변경하면 안 되는 필드는 세터 제거로 접근을 원천 봉쇄
  • 다른 클래스에서 데이터 클래스의 게터/세터를 사용하는 메서드를 찾아서 그 메서드를 데이터 클래스로 옮기거나, 옮길 수 있는 부분만 별도 메서드로 뽑아내야 함
  • 단계 쪼개기의 결과로 나온 중간 데이터 구조의 경우 캡슐화할 필요가 없으므로 필드 자체를 공개해도 됨

3.23 상속 포기

  • 서브클래스가 부모의 메서드나 데이터를 받고 싶지 않을 수도 있음
  • 같은 계층에 서브클래스를 하나 새로 만들고, 메서드 내리기와 필드 내리기를 활용해서 물려받지 않을 부모 코드를 모조리 새로 만든 서브클래스로 넘기면 부모에는 공통된 부분만 남음
  • 상속 포기 문제는 서브클래스가 부모의 동작은 필요로 하지만 인터페이스는 따르고 싶지 않을 경우 발생
  • 이때는 서브클래스나 슈퍼클래스를 위임으로 바꿔 상속 메커니즘에서 벗어나야함

3.24 주석

  • 특정 코드 블록이 하는 일에 주석을 남기고 싶다면 함수를 추출
  • 이미 추출된 함수임에도 여전히 설명이 필요하다면 함수 이름을 바꿈
  • 시스템이 동작하기 위한 선행조건을 명시하고 싶다면 어서션을 추가

참고