Refactoring Study - 리팩터링이 필요한 경우

리팩터링 스터디 도중 리팩터링 2판를 읽고 요약한 내용입니다. 자세한 내용은 책을 구매하여 확인 부탁드립니다.

언제 리팩터링을 해야 하나

앞으로 후술될 문제점의 해결책은 책 뒷부분에서 자세히 다룰 것이므로 대략적인 해결 방안과 책에서 제시한 리팩터링 기법의 이름만 적어 놓도록 하겠습니다.

기이한 이름

  • 상황
    • 함수 / 모듈 / 변수 / 클래스 등의 이름만 보고, 어떤 일을 하는지 알기 힘들 때 나는 경우 입니다.
    • 코드는 단순 명료하게 작성 되어야 합니다.
    • 명료한 코드를 만드는데 가장 중요한 위치를 차지하는것이 올바른 이름 짓는 것 입니다.
    • 마땅한 이름이 생각나지 않는다면, 설계에 더 근본적인 문제가 숨어 있을 가능성이 높습니다.
  • 해결책
    • 이름을 알맞게 바꾸는 작업을 진행 합니다.
      • 함수 선언 바꾸기
      • 변수 이름 바꾸기
      • 필드 이름 바꾸기

중복 코드

  • 상황
    • 똑같은 코드 구조가 여러곳에서 반복 되는 경우입니다.
    • 코드 구조가 중복되면, 각각을 볼 때 마다 차이점은 없는지 주의깊게 보아야 하는 부담이 생깁니다.
    • 하나를 변경 할 때에, 비슷한 구조의 다른 코드들도 모두 변경 해 주어야 합니다.
  • 해결책
    • 한 클래스에 딸린 두 메서드가 같은 표현식을 사용한다면
      • 함수 추출하기
    • 코드가 비슷하긴 한데, 완전히 똑같지는 않다면
      • 문장 슬라이드 하기
    • 같은 부모로 부터 파생된 서브클래스들에 코드가 중복되어 있다면
      • 메서드 올리기

긴 함수

  • 상황
    • 하나의 함수가 지나치게 긴 영역을 차지 하는 경우에 해당됩니다.
    • 함수가 길다는 것은, 해당 함수가 너무 많은 일을 하고 있는것은 아닌지 의심 해 보아야 합니다.
  • 상황에 대한 팁
    • 주석을 달아야 할 만한 부분을 모두 함수로 분리합니다.
    • 코드가 단 한줄이라도 따로 설명할 필요가 있다면 함수로 추출하는것이 좋습니다.
  • 해결책
    • 함수를 짧게, 좋은 코드 덩어리로 만들기
      • 함수 추출하기
    • 매개변수, 임시변수가 너무 많아지는것 방지하기
      • 임시 변수를 질의 함수로 바꾸기
      • 매개변수 객체 만들기
      • 객체 통째로 넘기기
    • 여전히 임시 변수, 매개 변수가 너무 많은 경우
      • 함수를 명령으로 바꾸기
    • 조건문, 반복문을 리팩터링 하기
      • 조건문 분해하기
      • 조건문을 다형성으로 바꾸기
      • 반복문 쪼개기

긴 매개변수 목록

  • 상황
    • 함수에 필요한 것들을 전무 매개변수로 전달하는데, 이 매개변수 목록이 너무 많은 경우에 해당됩니다.
    • 전역 데이터를 적게 사용하기 위해, 매개변수를 많이 전달하는 경우가 잦아 져 해당 상황이 발생 할 수 있습니다.
  • 해결책
    • 다른 매개변수에서 값을 얻어올 수 있을때
      • 매개변수를 질의 함수로 바꾸기
    • 사용 중인 데이터 구조에서 값들을 뽑아 각각을 별개의 매개변수로 전달하는 경우라면
      • 객체 통째로 넘기기
    • 항상 함께 전달되는 변수가 있다면
      • 매개변수 객체 만들기
    • 함수의 동작 방식을 정하는 플래그 역할의 매개변수가 있다면
      • 플래그 인수 제거하기
    • 여러 개의 함수가 특정 매개변수의 값을 공통으로 사용하고 있다면
      • 여러 함수를 클래스로 묶기

전역 데이터

  • 상황
    • 전역 데이터를 사용하는 경우에 해당됩니다.
    • 전역변수는 코드베이스 어디에서든 건드릴 수 있고, 누가 값을 바꾸었는지 찾아 낼 메커니즘이 없는 것이 문제의 원인입니다.
    • 싱글톤 클래스를 활용할 때에도 문제가 생길 수 있습니다.
    • 특히, 전역 변수가 mutable 한 경우에 더 문제가 큽니다.
  • 해결책
    • 다른 코드에서 전역 변수를 변경 할 수 있는 경우
      • 함수 캡슐화 하기

가변 데이터

  • 상황
    • 데이터의 값을 변경 할 수 있는 경우에 해당 됩니다.
    • 데이터의 값이 변경됨으로써 원하지 않는 결과가 나올 수 있음을 유의하고 개발을 진행해야 합니다.
    • 함수형 프로그래밍에서 처럼, 변수를 변경하는것은 불가능하고, 데이터를 변경하려면 데이터의 복사본을 변경하여 반환 하는 것이 제일 안전합니다.
  • 해결책
    • 정해놓은 함수를 거쳐야만 값을 수정 할 수 있도록 하기
      • 변수 캡슐화 하기
    • 하나의 변수에 용도가 다른 값들을 저장한다면 → 용도별로 독립 변수에 저장
      • 변수 쪼개기
    • 갱신 로직은 다른코드와 떨어뜨려놓기
      • 문장 슬라이드 하기
      • 함수 추출하기
    • API를 만들 때
      • 질의 함수와 변경 함수 분리하기
    • 대부분의 경우
      • setter 제거하기
      • 파생 변수를 질의 함수로 바꾸기
    • 변수의 유효 범위를 제한하기
      • 여러 함수를 클래스로 묶기
      • 여러 함수를 변환 함수로 묶기
    • 내부 필드에 데이터를 담고 있는 변수라면 → 내부 필드를 직접 수정 하지 않고, 구조체를 통째로 교체하기
      • 참조를 값으로 바꾸기

뒤엉킨 변경

  • 상황
    • 단일 책임의 원칙이 제대로 지켜지지 않는 경우에 해당됩니다.
    • 하나의 모듈이 서로다른 이유들로 인해 여러가지 방식으로 변경되는 일이 많을 때 발생합니다.
    • 즉, 한 코드에 여러 맥락이 섞인 경우에 발생합니다.
  • 해결책
    • 순차적으로 실행되는것이 자연스러운 맥락일 경우
      • 단계 쪼개기
    • 전체 처리 과정 곳곳에서 각기다른 맥락의 함수를 호출하는 경우가 잦을 때
      • 함수 옮기기
    • 여러 맥락의 일에 관여하는 함수가 있을 때
      • 함수 추출하기
      • 클래스 추출하기

산탄총 수술

  • 상황
    • 코드를 변경 할 때마다 자잘하게 수정해야 하는 클래스가 많은 경우에 해당됩니다.
    • 변경 할 부분이 코드 전반에 퍼져 있는 경우에 버그를 양산하기 쉽습니다.
    • 여러 코드에 맥락이 흩뿌려진 경우에 발생합니다.
  • 해결책
    • 변경되는 대상들을 묶어놓기
      • 함수 옮기기
      • 필드 옮기기
    • 비슷한 데이터를 다루는 함수가 많다면
      • 여러 함수를 클래스로 묶기
    • 데이터 구조를 변환하거나, 보강하는 함수에는
      • 여러 함수를 변환 함수로 묶기
    • 묶은 함수들의 출력 결과를 묶어 다음 단계의 로직으로 전달 할 수 있다면
      • 단계 쪼개기
    • 어설프게 분리된 로직 합치기
      • 함수 인라인 하기
      • 클래스 인라인 하기

기능 편애

  • 상황
    • 어떤 함수가 자기가 속한 모듈의 함수나, 데이터 보다 다른 모듈의 함수나 데이터와 상호작용 할 일이 더 많은 경우에 해당됩니다.
    • 실제로는, 영역 안에서 이루어 지는 상호작용은 최대로, 영역 사이에 이루어 지는 상호작용은 최소화 하여야 합니다.
    • 외부 객체의 getter / setter 등을 많이 호출 하는 경우도 이에 해당됩니다.
  • 해결책
    • 특정 함수가 특정 데이터와 가까이 있고 싶어하는 의중이 뚜렷히 들어 난다면
      • 함수 옮기기
    • 함수의 일부에서만 특정 데이터와 가까이 있고 싶어 한다면
      • 함수 추출하기
      • 함수 옮기기
    • 함수가 사용하는 모듈이 다양하여 어디로 옮길 지 명확하게 들어나지 않는 경우
      • 함수 추출하기 → 추출된 함수들을 각각 적합한 모듈로 옮기기

데이터 뭉치

  • 상황
    • 항상 뭉쳐다니는 데이터 뭉치가 존재하는 경우에 해당합니다.
    • 몰려다니는 데이터 뭉치를 분리 해 주어야 합니다.
  • 상황 팁
    • 데이터 뭉치로 의심되는 뭉치에서, 값 하나를 삭제 했을 때 나머지 데이터가 의미가 없다면 해당 데이터들은 데이터 뭉치입니다.
    • 데이터 뭉치를 하나의 클래스로 묶어놓고, 그 클래스에 옮기면 좋은 동작은 없는 지 살펴보는것이 좋습니다.
  • 해결책
    • 필드 형태의 데이터 뭉치를 찾아 객체로 묶기
      • 클래스 추출하기
    • 메서드 시그니처에 있는 뭉치 해결하기
      • 매개변수 객체 만들기
      • 객체 통째로 넘기기

기본형 집착

  • 상황
    • 알맞지 않은 데이터 타입을 사용하는 경우에 해당합니다.
    • 화폐 데이터를 단순 숫자 형태로 사용하거나, 좌표 데이터를 단순 숫자로 사용하는 경우가 해당됩니다.
    • 전화번호를 단순 문자열로 다루는 경우도 이와 마찬가지 입니다.
  • 해결책
    • 알맞은 데이터 타입을 생성해주기
      • 기본형을 객체로 바꾸기
    • 조건부 동작을 제어하는 타입 코드로 사용 된 경우
      • 타입 코드를 서브 클래스로 바꾸기
      • 조건부 로직을 다형성으로 바꾸기
    • 자주 몰려다니는 기본형 그룹이 있다면
      • 클래스 추출하기
      • 매개변수 객체 만들기

반복되는 switch문

  • 상황
    • switch문이 너무나 많이 사용되는 경우에 해당됩니다.
    • 예전에는, 모두 다형성으로 switch문을 없애는 것이 강력히 권고되었지만, switch문 자체에 기능들이 추가되며 강제되고 있지는 않습니다.
    • 중복된 switch문이 많다면, 하나의 switch문을 수정 할 때 나머지 switch문도 모두 동일하게 수정 해 주어야 오류 없이 잘 동작합니다.
  • 해결책
    • 중복된 switch문이 많이 존재하는 경우
      • 조건부 로직을 다형성으로 바꾸기

반복문

  • 상황
    • 반복문이 사용되고 있는 경우에 해당합니다.
    • 없애는 것이 최선입니다.
    • 파이프라인으로 반복문을 처리하면, 각 원소들이 어떻게 처리되는지 파악하기 쉽습니다.
  • 해결책
    • 일급 함수를 지원하는 언어라면
      • 반복문을 파이프라인으로 바꾸기

성의 없는 요소

  • 상황
    • 의미가 없는 프로그래밍 요소 ( method, class, interface … ) 등이 존재하는 경우가 해당됩니다.
  • 해결책
    • 제거 할 수 있다면 제거
      • 함수 인라인하기
      • 클래스 인라인하기
      • 상속을 사용 했다면
        • 계층 합치기

추측성 일반화

  • 상황
    • ‘나중에 필요할 거야’ 라는 생각으로 당장은 필요 없는 모든 종류의 hooking point / 특이 케이스 로직을 작성 해둔 경우에 해당합니다.
    • 지우는게 상책입니다.
  • 해결책
    • 하는일이 거의 없는 추상클래스가 있다면
      • 계층 합치기
    • 쓸데없이 위임하는 코드
      • 함수 인라인하기
      • 클래스 인라인하기
    • 본문에서 사용되지 않는 매개변수 / 한번도 사용한 적 없는 매개변수가 존재
      • 함수 선언 바꾸기
    • 테스트 코드 말고는 사용하는곳이 없다면
      • 죽은 코드 제거하기

임시 필드

  • 상황
    • 특정 상황에서만 값이 설정되는 필드를 가진 클래스가 존재 하는 경우에 해당됩니다.
    • 객체를 가져 올 때, 모든 필드가 채워 져 있는것이 다른 프로그래머들과 소통하기 좋습니다.
    • 비어있는 필드는 최대한 없애는 것이 좋습니다.
  • 해결책
    • 덩그러니 떨어져 있는 필드가 있다면
      • 클래스 추출하기
      • 함수 옮기기 → 새 클래스에 옮겨줌
    • 임시 필드가 유효한지 검사하는 로직이 있다면
      • 특이 케이스 추가하기 → 이후 임시필드 제거

메세지 체인

  • 상황
    • 클라이언트가 한 객체를 통해 객체를 얻고, 얻은 객체를 통해 다른 객체를 얻고, 얻은 객체를 통해 또 다른 객체를 얻는 경우가 존재 하는 경우에 해당됩니다.
    • 클라이언트가 객체 네비게이션 구조에 종속 되었음을 의미합니다.
    1
    2
    3
    4
    5
      // 이런 코드가 존재하는 경우 
      managerName = aPerson.department.manager.name
    
      // 체인의 존재를 숨기는 것을 제안하고 있습니다. 
      managerName = aPerson.getManagerName() 
    
  • 해결책
    • 메세지 체인 없애기
      • 최종 경과 객체가 어떻게 쓰이는지 살펴 본 뒤 → 위임 숨기기
    • 객체를 사용하는 코드 일부를 추출 / 옮기기
      • 함수 추출하기
      • 함수 옮기기

중개자

  • 상황
    • 한 클래스 내에 위임 객체가 너무 많은 경우에 해당됩니다.
  • 해결책
    • 실제로 일을 하는 객체와 직접 소통하게 변경하기
      • 중개자 제거하기
    • 위임 메서드 제거 후 남는 일이 별로 없다면
      • 함수 인라인 하기

내부자 거래

  • 상황
    • 모듈 간의 데이터를 주고 받는 것이 숨겨 진 경우에 해당 됩니다.
  • 해결책
    • 사적으로 처리하는 부분을 줄이기
      • 함수 옮기기
      • 필드 옮기기
    • 여러 모듈이 같은 관심사를 공유한다면
      • 공통 부분을 처리하는 제3의 모듈 만들기
      • 위임 숨기기 → 다른 모듈이 중간자 역할을 하게 만들기
    • 상속 구조에서, 자식 클래스가 부모 클래스에 대해 너무 많은 권한을 갖고 있어 분리 할 필요성이 있다면
      • 서브 클래스를 위임으로 바꾸기
      • 슈퍼 클래스를 위임으로 바꾸기

거대한 클래스

  • 상황
    • 한 클래스가 너무 많은 일을 하게되어 필드, 메서드가 비대해진 경우에 해당됩니다.
    • 코드량이 너무 많은 클래스도 이에 해당합니다.
    • 클라이언트들이 거대 클래스를 어떻게 이용하는지 패턴을 파악하여 쪼개는 단서를 얻을 수 있습니다.
  • 해결책
    • 필드 일부를 묶을 수 있을 때
      • 클래스 추출하기 ( 접두어, 접미어가 같은 필드들을 주로 찾으면 좋습니다 )
    • 분리할 컴포넌트가 원래 클래스와 상속 관계로 이루어 질 수 있을 때
      • 슈퍼 클래스 추출하기
      • 타입 코드를 서브 클래스로 바꾸기
    • 코드량이 너무 많을 때
      • 함수 추출하기
    • 유용한 기능 그룹을 찾았다면 → 분리
      • 클래스 추출하기
      • 슈퍼 클래스 추출하기
      • 타입 코드를 서브 클래스로 바꾸기

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

  • 상황
    • 서로 다른 인터페이스를 구현하고 있는 클래스를, 하나로 합칠 수 있는 경우에 해당됩니다.
  • 해결책
    • 인터페이스가 같아 질 때 까지 필요한 동작들을 클래스 안으로 밀어 넣기
      • 함수 선언 바꾸기를 통해 method signature 일치시키기
      • 함수 옮기기를 이용하여 인터페이스 일치시키기
      • 중복 코드가 생기면 슈퍼클래스 추출하기

데이터 클래스

  • 상황
    • data만 다루는 클래스에, public field가 존재하는 경우에 해당됩니다.
    • data class를 다루고 있는 곳의 코드를, data class 내부로 옮길 수 있는지 확인 해보는 것이 좋습니다.
  • 해결책
    • property 캡슐화
      • 레코드 캡슐화하기
      • setter 제거하기
    • data class를 다루고 있는 곳의 코드를 data class 내부로 옮기기
      • 전체를 옮기기 힘들다면 함수 추출하기 이후 옮길 수 있는 부분만 옮기기

상속 포기

  • 상황
    • 부모 클래스의 property / method 중 상속 받고 싶지 않은 필드가 존재 하는 경우에 해당됩니다.
    • 하지만, 항상 공통된 부분만 남기는 것이 권장되지는 않습니다.
    • 부모의 동작은 필요로 하지만, 인터페이스는 따르고 싶지 않을 때 수정을 하는것이 강력하게 권고됩니다.
  • 해결책
    • 부모 클래스에 필요한 공통된 부분만 남기기
      • 메서드 내리기, 필드 내리기를 활용하여 물려 받지 않을 부모 코드를 모조리 새로만든 서브클래스로 넘기기
    • 자식 클래스에서 인터페이스를 따르고 싶어하지 않을 때 → 상속 메커니즘 탈피
      • 서브 클래스를 위임으로 바꾸기
      • 슈퍼 클래스를 위임으로 바꾸기

주석

  • 상황
    • 주석을 탈취제 처럼 사용하는 경우에 해당됩니다.
    • 주석은 향기를 입히는데, 잘못 작성된 코드에 향기를 입히는 경우 리팩터링의 대상이 됩니다.
    • 주석이 필요없는 코드로 리팩터링 한 뒤에 주석을 남기는 것이 좋습니다.
    • 무엇을 할 지 모를때, 현재 진행 및 미래에도 어떻게 변경될지 모르는 코드를 작성 할 때엔 주석을 남기는 것이 좋습니다.
  • 해결책
    • 특정 코드 블록이 하는 일에 주석을 남기고 싶을 때
      • 함수 추출하기
    • 이미 추출된 함수임에도 여전히 설명이 필요할 때
      • 함수 선언 바꾸기
    • 시스템이 동작하기 위한 선행 조건을 명시하고 싶을 때
      • assertion 추가하기