Skip to main content

3. 코드에서 나는 악취

리팩터링을 작동 방법을 알았다면 적용 시점에 대해서도 알아야합니다.


3.1 기이한 이름#

코드는 단순하고 명료하게 작성하는 것이 중요합니다. 그 중 하나가 바로 이름입니다.

  • 함수 선언 바꾸기
  • 변수 이름 바꾸기
  • 필드 이름 바꾸기

이름이 명확하지 않다면 설계에 근본적인 문제가 있을 확률이 높고, 혼란스러운 이름을 잘 정리하다 보면 코드가 훨씬 간결해질 때가 많습니다.

3.2 중복 코드#

똑같은 코드 구조가 여러 곳에서 반복되면 하나로 통합해서 더 나은 방법으로 만들어야 합니다.

  • 함수 추출하기
    • 한 클래스에 딸린 두 메서드가 똑같은 표현식을 사용하는 경우
  • 문장 슬라이드하기
    • 비슷한 부분을 한 곳에 모아 함수 추출할 때 사용
  • 메서드 올리기
    • 같은 부모 클래스로 파생된 자식 클래스에 코드 중복이 있는 경우, 각자 호출이 되지 않도록 적용합니다.

3.3 긴 함수#

함수를 짧게 구성하면 다음의 장점을 가집니다.

  • 관리하기 편리해 집니다.
  • 간접 호출의 효과를 볼 수 있습니다.
  • 코드를 이해하고 공유하기 쉽습니다.
  • 선택하기 쉬워집니다.

또한 예전과 달리 프로세스 안에서 함수 호출 비용이 거의 없습니다.

다음의 과정으로 할 수 있습니다

  • 함수 추출하기
    • 함수를 짧게 만드는 작업의 99%를 차지합니다.
    • 함수 본문에서 따로 묶어서 빼내면 좋은 코드 덩아리를 새로운 함수로 만듭니다.
  • 임시변수를 질의 함수로 변경, 매개변수 객체 만들기, 객체를 통째로 넘기기
    • 매개 변수가 너무 많아지면 난해해지기 때문에 사용합니다.
  • 함수를 명령으로 바꾸기
    • 임시 변수와 매개 변수가 너무 많은 경우, 추가적으로 사용할 수 있습니다.
  • 조건문을 분해하기
    • 거대한 switch 문을 구성하는 case 문마다 함수를 추출하고, 함수 하나로 변경합니다.
  • 조건문을 다형성으로 바꾸기
    • switch문을 여러개 하는 것도 좋습니다.
  • 반복문 쪼개기
    • 반복문도 안의 코드를 추출해서 독립된 함수로 만들어서 적합한 이름과 성격을 가지게 합니다.

3.4 긴 매개변수 목록#

매개변수 목록이 길어지면 이해하기 어려워집니다.

  • 매개변수를 질의 함수로 바꾸기
    • 매개변수에서 값을 얻어올 수 있는 매개변수를 얻을 수 있음.
  • 객체 통째로 넘기기
    • 사용 중인 데이터 구조에서 값들을 뽑아 각각의 별개의 매개변수로 전달합니다.
  • 매개변수 객체 만들기
    • 원본 객체를 그대로 넘겨서 짧게 합니다.
  • 플래그 인수 제거하기
    • 함수의 동작 방식을 정하는 플래그 역할의 매개변수를 지웁니다.
  • 여러 함수를 클래스로 묶기
    • 여러개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 유용합니다.

3.5 전역 데이터#

전역데이터는 어디에서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아낼 매커니즘이 없기 때문에 문제가 됩니다.

  • 변수 캡슐화하기
    • 다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 진행합니다.
    • 데이터를 함수로 감싸서 수정하는 부분을 쉽게 찾고 접근을 통제합니다
    • 더 나아가 함수들을 클래스나 모듈에 집어넣고 안에서만 수정이 되도록 하고, 접근 범위를 최소화합니다.

전역 데이터를 캡슐화해야지, 변화에 대처할 수 있습니다.


3.6 가변 데이터#

데이터를 변경했더니 예상치 못한 결과나 골치 아픈 버그가 발생하는 문제가 있습니다. 특히 이러한 문제는 원인을 발생하기 어려울 때가 있습니다.

이를 해결하기 위해서 함수형 프로그래밍에서의 데이터는 절대 변하지 않고, 데이터를 변경하려면 반드시 변경하려는 값에 해당하느 복사본을 만들어서 반환한다는 개념을 가지고 있습니다. 다만 함수형 언어가 프로그래밍에서 차지하는 비중은 적고, 변수 값을 바꿀 수 있는 언어를 사용하는 프로그래머가 더 많습니다.

하지만 불변성이 주는 장점을 포기할 필요는 없습니다.

  • 변수 캡슐화하기
    • 정해놓은 함수를 지정해, 수정하도록 하면 값이 어떻게 바뀌는지 알거나 코드를 개선하기 쉽습니다.
  • 변수 쪼개기
    • 변수에 용도가 다른 값들을 저장해 값을 갱신하는 경우
    • 용도별로 독립 변수에 저장하여 값 갱신이 문제를 일으키는 부분을 없앱니다.
  • 문장 슬라이드하기, 함수 추출하기
    • 갱신 코드를 분리하여 부작용을 줄입니다.
  • 질의 함수와 변경 함수 분리하기
    • API를 만들때 진행합니다.
    • 부작용이 있는 코드를 호출할 수 없게합니다.
  • 세터 제거하기
    • 세터를 호출하는 곳을 제한하면 변수의 유효 범위를 줄일 수 있습니다.
  • 파생 변수를 질의함수로 바꾸기
    • 값을 여러곳에서 설정할 수 있는 경우는 여러 혼동과 버그를 만들 수 있습니다.
  • 여러 함수를 클래스로 묶기, 여러 함수를 변환 함수로 묶기
    • 변수의 유효 범위가 넓어지면 위험이 커집니다.
    • 변수를 갱신하는 코드들의 유효범위를 클래스나 변환으로 제한합니다.
  • 참조를 값으로 바꾸기
    • 구조체처럼 내부 필드에 데이터를 담고 있는 변수의 경우 적용합니다.
    • 때로는 내부 필드를 수정하는 것 보다는 구조체를 통째로 교체하는 편이 좋습니다.

3.7 뒤엉킨 변경#

일반적으로 뒤엉킨 변경은 단일 책임 원칙(SRP, Single Responsibility Principle)이 제대로 지켜지지 않을 때 발생합니다.

  • 단계 쪼개기
    • 반약에 순차적으로 실행되는 게 자연스러운 경우
    • 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 식으로 단계를 분리합니다.
  • 함수 옮기기
    • 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높은 경우
    • 각 맥락에 해당하는 적당한 모듈들을 만들어 관련 함수를 모읍니다.
  • 함수 추출하기, 클래스 추출하기
    • 맥락의 일에 관련하는 함수나 클래스인 경우, 진행합니다.

3.8 산탄총 수술#

뒤엉킨 변경과 비슷하면서 정반대입니다. 일반적으로 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍깁니다.

  • 함수 옮기기, 필드 옮기기
    • 한 모듈에 묶어서 처리합니다.
  • 여러 함수를 클래스로 묶기
    • 비슷한 데이터를 다루는 함수가 많은 경우
  • 여러 함수를 변환 함수로 묶기
    • 데이터 구조를 변환하거나 보강하는 함수들이 많은 경우
  • 단계 쪼개기
    • 묵은 함수들의 출력 결를 묶어서 다음 단계의 로직으로 전달합니다.
  • 함수 인라인하기, 클래스 인라인 하기
    • 어설프게 분리된 로직을 모으기
    • 메서드나 클래스가 커지면 추출하기를 통해서 분리할 수도 있습니다.

3.9 기능 편애#

프로그램을 모듈화할 때는 코드를 여러 영역으로 나눈 뒤, 영역 안에서 이뤄지는 상호작용은 최대한 늘리고 영역 사이에서 이뤄지는 상호작용을 최소로 줄입니다.

  • 함수 옮기기, 함수 추출하기
    • 데이터와 함수가 가까이 있어야한다고 판단되면 이를 모아줍니다.
    • 일부 함수에서만 그 기능이 필요하다고 판단하면, 독립함수로 빼서 원하는 모듈로 보내줍니다.
    • 옮길 곳이 명확하지 않는 경우, 가장 많은 데이터를 포함한 모듈로 이동합니다.

이를 해결하는 다양한 패턴들이 존재합니다.

  • 전략 패턴, 방문자 패턴, 자기 위임
    • 이러한 패턴의 핵심은 함께 변경할 대상을 한 곳에 모으는 것입니다.

3.10 데이터 뭉치#

데이터 항목은 서로 뭉쳐놓은 것이 좋습니다.

  • 클래스 추출하기
    • 먼저 필드 형태의 데이터 뭉치를 하나의 객체로 묶습니다.
  • 매개변수 객체 만들기, 객체 통째로 넘기기
    • 배개변수 수를 줄입니다.

데이터의 필드 하나를 없앴을 때, 나머지 데이터만으로 의미가 없다면 객체로 변경해야하는 데이터입니다.


3.11 기본형 집착#

대부분의 플래그래밍 언어는 다양한 기본형(정수, 보동소수점 수, 문자열 등)의 다양한 기본형을 제공합니다. 다만 이를 오사용하면 안됩니다.

  • 기본형을 객체로 바꾸기
  • 타입 코드를 서브 클래스로 바꾸기, 조건부 로직을 다형성으로 바꾸기
  • 클래스 추출하기, 매개변수 객체 만들기

3.12 반복되는 switch 문#

조건부 로직을 다형성으로 바꾸는 것도 좋은 방법입니다. 반복이 많은 경우, 또한 여러 조건절이 추가되는 경우 모두 수정해야하기 때문에 이를 다형성으로 바꾸는 것이 좋습니다.


3.13 반복문#

반복문을 파이프라인으로 바꾸기를 적용해서 시대에 걸맞지 않은 반복문을 제거할 수 있습니다. 다만, 항상 이를 적용하는 것을 좋지 않습니다.


3.14 성의 없는 요소#

잘못된 프로그램 요소는 제거합니다.

프로그램 요소 : 함수, 클래스, 인터페이스 등 코드 구조를 잡는 데 활용되는 요소

원래 풍성했던 클래스가 리팩터링을 거치면서 역할이 줄어들 수 있으며 이는 나쁘지 않습니다.

  • 함수 인라인하기, 클래스 인라인하기
  • 계층 합치기
    • 상속을 사용한 경우

3.15 추측성 일반화#

추측성 일반화는 나중에 필요할거라는 생각으로 만들어 놓는 경우 발생합니다. 이는 결국에 코드가 더러워지는 효과가 있으므로 당장 걸리적거리는 코드는 삭제하는 것이 좋습니다.

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

3.16 임시 필드#

간혹 특정 상황에서만 값이 설정되는 필드를 가진 클래스가 있는데, 이때 객체를 들고오는 경우 모든 필드가 채워져있다고 기대하게 사용합니다. 그러나 이러한 생각이 이슈를 만들게 됩니다.

  • 클래스 추출하기
    • 떨어진 필드를 모다줍니다.
  • 함수 옮기기
    • 임시 필드들과 관련된 코드를 모조리 새 클래스에 모아 넣습니다.
  • 특이 케이스 추가하기
    • 필드가 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거합니다.

3.17 메시지 체인#

메세지 체인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 것입니다.

  • 위임 숨기기
    • 내비게이션 중간 단계를 수정해야하는 클라이언트 코드를 수정하는 이슈를 해결할 수 있습니다.
    • 체인을 구성한느 모든 객체에 적용가능하지만 이 경우, 모든 객체가 중개자가 되는 문제가 있으므로 결과 객체를 보고 사용하는 것이 좋습니다.
  • 함수 추출하기, 함수 옮기기
    • 결과 객체를 사용하는 코드 일부를 따로 빼내서 체인을 숨깁니다.
// 대표적 예시
String manageName = aPerson.getDepartment().getManager().getName();

3.18 중개자#

객체의 대표적인 기능 중 하나는 외부로부터 세부 사항을 숨겨주는 캡슈뢓입니다. 이러한 캡슐화를 구성하는 과정에서 위임(delegation)이 주로 발생합니다.

그러나 이를 잘못사용하고 지나치면 문제가 됩니다. 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하는 경우, 중개자 제거하기를 통해서 실제로 일을 하는 객체와 직접 소통하도록 해야합니다.

추가적으로 위임 메서드를 제거한 후 남는 일이 거의없다면 호출하는 쪽으로 인라인합니다.


3.19 내부자 거래#

모듈 사이의 데이터 거래가 많으면 결합도(coupling)이 높아지는 문제가 있습니다. 물론 필요하지만 최대한 양을 줄이고 투명하게 처리하는 것이 중요합니다.

  • 함수 옮기기, 필드 옮기기
    • 은밀히 데이터를 주고 받는 모듈을 옮겨서 사적으로 처리하는 부분을 줄입니다.
  • 위임 숨기기, 새로운 모듈 생성
    • 여러 모듈이 같은 관심사를 공유하는 경우, 다른 모듈이 중간자 역할을 하거나 새로 생성합니다.
  • 서브클래스를 위임으로 바꾸기, 슈퍼클래스를 위임으로 바꾸기
    • 상속 구조에서 부모 클래스에서 벗어나는 경우에 사용합니다.

3.20 거대한 클래스#

한 클래스가 너무 많은 일을 하면 필드 수가 늘어나고, 중복 코드가 생기기 쉽습니다.

  • 클래스 추출하기
    • 같은 컴포넌트에 모으는 것이 합당해 보이는 필드를을 선택해서 분리합니다.
  • 슈퍼클래스 추출하기, 타입 코드를 서브클래스로 바꾸기
    • 분리한 컴포넌트를 원래 클래스와 상속 클래스로 만드는 것이 좋은 경우에 사용합니다.

이렇게 하게 되면 여러 코드를 짧게 구성해서 유지보수성이나 사용자 측면에서 장점을 가지게 됩니다.


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

클래스를 사용할 때의 장점은 언제든 다른 클래스로 교체할 수 있다는 장점입니다.

  • 함수 선언 바꾸기
    • 메서드시그니처를 일치시킵니다.
  • 함수 옮기기
    • 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어넣습니다.
  • 슈퍼클래스 추출하기
    • 대안 클래스들 사이에 중복 코드가 생긴 경우, 적용합니다.

3.22 데이터 클래스#

데이터 클래스는 데이터 필드와 getter/setter로만 구성된 클래스를 의미합니다.

  • 레코드 캡슐화하기
    • 클래스에 public 필드가 있는 경우, private 으로 수정합니다.
  • 세터 제거하기
    • 변경하면 안되는 필드는 세터 제거하기로 접근을 봉쇄합니다.
  • 함수 옮기기, 함수 추출하기
    • 데이터 클래스의 게터나 세터를 사용하는 메서드를 찾아 가능한 한 곳으로 모아둡니다.
    • 이가 어렵다면, 옮길 수 잇는 부분만 별도 메서드로 뽑아냅니다.
  • 단계 쪼개기
    • 데이터 클래스와 클라이언트 코드를 최대한 분리시킵니다.
    • 불변 필드는 캡슐화 필요성이 적으며, 필드 자체를 공개해도 크게 문제가 없습니다.

3.23 상속 포기#

서브 클래스는 부모로부터 메서드와 데이터를 물려받습니다. 하지만 부모의 유산을 원하지 않는 경우가 존재합니다. 이는 계층구조를 잘못설계해서 그렇습니다.

사실 상속은 대부분의 상황에서 안좋습니다.

위 경우에는 같은 계층에 서브 클래스를 만들고 메서드 내리기, 필드 내리기를 통해서 물려받지 않을 부모 코드를 모조리 새로 만든 서브클래스로 넘깁니다. 이를 통해 부모에는 공통된 부분만 남습니다.

이보다는 서브클래스를 위임으로 바구기, 슈퍼클래스를 위임으로 바꾸기를 통해서 상속 메커니즘을 벗어나는 것이 중요합니다.


3.24 주석#

주석이 길게 달린 원인은 대부분 코드를 잘못 작성했기 때문인 경우가 많습니다.

  • 함수 추출하기
    • 특정 코드 블록이 하는 일에 주석을 남기고 싶은 경우.
  • 함수 선언 바꾸기
    • 추출되어 있으나 설명이 필요하다면 함수 이름을 바꿉니다.
  • 어서션 추가하기
    • 시스템이 동작하기 위한 선행조건을 명시하고 싶은 경우.

주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링을 해봅니다.

뭘하는 지 모를 때라면 주석을 다는 것이 좋고, 작성 이유를 설명하는 용도는 괜찮습니다.

Last updated on