리팩터링을 언제해야 하는가에 대해 명확하게 정립된 규칙은 없다. 하지만 리팩터링이 절실한 코드들의 특징은 존재한다. 켄트 백은 이를 냄새(악취)라고 정의한다.

기이한 이름

코드는 단순하고 명료하게 작성되어야 한다. 특히 이름은 보기만 해도 어떤 작업을 하는 코드인지 명확히 알 수 있어야 한다. 이러한 노력은 곧 코드를 이해하기 쉽게 만든다.

만약 마땅한 이름이 떠오르지 않는다면 설계에 근본적인 문제가 있을 수 있다. 이름을 잘 정리하다 보면 코드가 간결해지는 경우도 있다.

중복 코드

똑같은 코드 구조가 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있다.

리팩터링 설명
함수 추출하기
  • 간단한 중복 코드의 경우
  • 중복 코드에서 추출된 메서드를 호출
  • 문장 슬라이드하기
  • 완전히 똑같지 않고 비슷한 경우
  • 비슷한 부분을 한 곳에 모아 함수 추출하기를 더 쉽게 적용
  • 메서드 올리기
  • 자식 클래스간 중복 코드는 부모로 옮김
  • 긴 함수

    리팩터링 설명
    짧은 함수
  • 함수는 길수록 이해하기 어려움
  • 간접 호출의 효과, 코드를 이해하고, 공유하고, 선택하기 쉬워짐
  • 좋은 이름
  • 주석을 달아야 할 만한 부분은 함수로 만듦
  • 함수 본문에는 주석으로 설명하려던 코드를 담음.
  • 함수 이름은 동작 방식이 아닌 의도가 드러나게 지음.
  • 긴 매개변수 목록

    매개변수 또한 길어지면 이해하기 어려워 진다.

    리팩터링 설명
    매개변수를 질의 함수로 바꾸기
  • 다른 매개변수에서 값을 얻어올 수 있는 매개변수 제거
  • 객체 통째로 넘기기
  • 원본 데이터 구조로 전달
  • 매개변수 객체 만들기
  • 항상 함께 전달되는 매개변수 묶기
  • 플래그 인수 제거
  • 함수 동작방식을 제어하는 플래그 제거
  • 여러 함수를 클래스로 묶기
  • 공통 값들을 클래스의 필드로 정의
  • 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 유용
  • 전역 데이터

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

    리팩터링 설명
    변수 캡슐화하기
  • 함수로 데이터를 감쌈
  • 데이터 수정되는 부분을 쉽게 찾을 수 있음
  • 접근 통제가 가능
  • 가변 데이터

    데이터를 변경하면 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다.

    리팩터링 설명
    변수 캡슐화하기
  • 정해놓은 함수를 거쳐야만 값 변경 가능하게 설정
  • 값이 어떻게 수정되는지 감시 가능
  • 코드를 개선하기 용이
  • 변수 쪼개기
  • 용도가 다른 값은 용도 별로 독립 변수에 저장
  • 갱신 로직은 다른 코드와 떨어뜨려 놓는 것이 좋음
  • 문장 슬라이드하기와 함수 쪼개기 이용
  • 무언가를 갱신하는 코드로부터 부작용이 없는 코드를 분리
  • 질의 함수와 변경 함수 분리하기
  • 부작용이 있는 코드를 호출하지 못하게 함
  • 세터 제거하기
  • 변수의 유효범위 줄이는 데 도움이 됨
  • 파생 변수를 질의 함수로 바꾸기
  • 값을 다른 곳에서 설정할 수 있는 가변 데이터는 악취가 심함
  • 여러 함수를 클래스 / 변환 함수로 묶기
  • 유효범위가 넓은 변수는 문제를 일으킬 위험이 큼
  • 변수를 갱신하는 코드들의 유효범위를 제한
  • 참조를 값으로 바꾸기
  • 구조체처럼 내부 필드에 데이터를 담고 있는 변수의 경우
  • 내부 필드를 직접 수정하지 말고 구조체를 통째로 교체하는 편이 나음
  • 뒤엉킨 변경

    소프트웨어의 구조는 변경하기 쉬운 형태로 조직되어야 한다. 그렇지 않다면 뒤엉킨 변경이나 산탄총 수술이 풍기기 마련이다.

    뒤엉킨 변경은 SRP(단일 책임 원칙, Single Responsibility Principle)이 만족하지 않을 때 발생한다. 즉, 하나의 모듈이 서로 다른 이유들로 인해 변경되는 일이 많을 때 발생한다.

    비즈니스 로직과 데이터베이스 연동은 서로 다른 맥락에서 이뤄지므로 독립된 모듈로 구성해야 마땅하다. 하지만 개발 초기에는 이러한 경계를 나누는 것이 쉽지 않으며, 개발 과정에서 이 경계 또한 끊임없이 변경된다.

    리팩터링 설명
    단계 쪼개기
  • 비즈니스 로직에서 데이터를 가져와서 처리하는 것과 같은 순차적인 맥락의 경우
  • 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 식으로 단계를 분리
  • 함수 옮기기
  • 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높은 경우
  • 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모음
  • 함수 추출하기
  • 함수 옮기기를 할 때 여러 맥락의 일을 관여하는 함수가 있는 경우 수행
  • 클래스 추출하기
  • 모듈이 클래스인 경우 클래스 추출하기를 통해 맥락별로 분리
  • 산탄총 수술

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

    이 냄새는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍긴다. 변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽다.

    리팩터링 설명
    함수 옮기기 / 필드 옮기기
  • 변경되는 대상들을 한 모듈로 묶음
  • 여러 함수를 클래스로 묶기
  • 비슷한 데이터를 다루는 함수가 많은 경우
  • 여러 함수를 변환 함수로 묶기
  • 데이터 구조를 변환하거나 보강하는 함수들의 경우
  • 단계 쪼개기
  • 이렇게 묶은 함수들의 출력 결과를 다음 단계의 로직으로 전달할 경우
  • 함수 인라인하기 / 클래스 인라인하기
  • 어설프게 분리된 로직으로 인한 산탄총 수술에 대처하는 좋은 방법
  • 메서드나 클래스가 비대해지지만 나중에 추출하기 리팩터링으로 개선
  • 기능 편애

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

    리팩터링 설명
    함수 옮기기
  • 연관되는 함수와 데이터를 가까이 함
  • 함수 추출하기
  • 함수의 일부에서만 기능을 편애하는 경우
  • 그 부분과 독립 함수로 빼냄
  • 함수 옮기기로 원하는 모듈로 이동
  • 어디로 옮길지가 명확하게 드러나지 않는다면 가장 많은 데이터를 포함한 모듈로 이동하자

    데이터 뭉치

    데이터 항목들이 항상 함께 뭉쳐 다니는 데이터 뭉치는 보금자리를 따로 마련해줘야 마땅하다.

    리팩터링 설명
    클래스 추출하기
  • 필드 형태의 데이터 뭉치를 하나의 객체로 묶음
  • 매개변수 객체 만들기 / 객체 통째로 넘기기
  • 메서드 시그니처에 있는 데이터 뭉치의 경우
  • 이 리팩터링을 적용하여 매개변수 수를 줄임
  • 클래스를 이용하면 좋은 향기를 흩뿌릴 기회가 생긴다.

    기본형 집착

    프로그래밍 언어에서 제공하는 기본형에 집착하고, 주어진 문제에 맞는 기초 타입을 직접 정의하기 꺼리는 사람이 많다.

    • 기본형을 객체로 바꾸기
      • 기본 자료형을 감싸기만 한 것처럼 보임
      • 필요한 경우 특별한 동작을 더할 수 있음
    • 기본형으로 표현된 코드가 조건부 동작을 제어하는 타입 코드로 쓰이는 경우
      • 타입 코드를 서브클래스로 바꾸기
      • 조건부 로직을 다형성으로 바꾸기
      • 위 리팩터링을 순차 적용한다

    반복되는 switch문

    조건부 로직을 다형성으로 바꾸기를 통해 가능한 한 switch문은 없애주는 편이 좋다. 조건절을 추가할 떄마다 다른 switch문들도 모두 찾아서 함께 수정해줘야 하기 떄문이다.

    반복분

    지금은 일급 함수(first-class function)을 지원하는 언어가 많다. 반복문을 파이프라인으로 바꾸기를 적용하여 시대에 걸맞지 않은 반복문을 제거하자. 필터나 맵 같은 파이프라인 연산을 사용하면 코드에서 각 원소들이 어떻게 처리되는지 쉽게 파악 가능하다.

    성의 없는 요소

    실질적으로 메서드가 하나뿐인 클래스와 같은 부실한 모듈의 경우 제거 작업을 진행한다. 함수 인라인하기클래스 인라인하기로 처리한다. 상속을 사용한 경우 계층 합치기를 사용한다.

    추측성 일반화

    당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다.

    • 하는 일이 거의 없는 추상 클래스
      • 계층 합치기
    • 쓸데없이 위임하는 코드
      • 함수 인라인하기나 클래스 인라인하기로 삭제
    • 본문에서 사용되지 않는 매개변수
      • 함수 선언 바꾸기로 삭제
    • 테스트 코드 말고는 사용하는 곳이 없는 함수나 클래스
      • 죽은 코드 제거하기

    임시 필드

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

    1. 덩그러니 떨어져 있는 필드를 발견하면 클래스 추출하기로 모은다.
    2. 임시 필드들과 관련된 코드를 함수 옮기기를 이용해 앞에서 만든 클래스에 넣는다.
    3. 임시 필드들이 유효한지 확인한 후 동작하는 조건부 로직은 특이 케이스 추가하기로 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거한다.

    참고문헌