본문으로 바로가기

[Refactoring] 3. 코드에서 나는 악취

category Program/Refactoring 2022. 9. 7. 14:33

3장 개요

  • 리팩터링을 언제 시작하고 언제 그만할지를 판단하는 일은 리팩터링의 작동 원리를 아는 것 만큼 중요하다.
  • 켄트백은 리팩터링할 시점을 냄새 라는 표현 사용
  • 리팩터링이 필요한 코드들에는 일정한 패턴이 있음
  • 리팩터링을 언제 멈춰야 하는지는 경험과 직관을 통해 감을 키우자

3.1 기이한 이름 - Mysterious Name

  • 코드는 단순하고 명료하게 작성해야 하고 이를 위한 중요한 요소가 이름이다.
  • 이름 짓는 건 프로그래밍에서도 가장 어려운 두 가지중 하나임 (필칼튼 캐시무효화, 이름짓기)
  • 이름 바꾸는 리팩토링을 가장 많이 사용함 :함수선언 바꾸기, 변수이름 바꾸기, 필드이름 바꾸기
  • 이름만 잘 지어도 문맥을 파악하느라 헤매는 시간을 크게 절약할 수 있다.
  • 마땅한 이름이 떠오르지 않으면 설계에 문제가 있을 가능성이 있음

3.2 중복 코드 - Duplicated Code

  • 똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합하자
  • 코드가 중복되면 수정시 비슷한 코드를 주의깊게 봐야하고 모두 수정해야함!
  • 두 메서드가 똑같은 표현식을 쓸때 함수 추출하기
  • 두 메서드가 비슷하다면 문장슬라이드 후 비슷한 부분을 모아 함수 추출하기 시전
  • 같은 부모로부터 파생된 서브 클래스에 코드 중복이 있으면 따로 호출되지 않도록 메서드 올리기

3.3 긴 함수 - Long Function

  • 오랜기간 잘 활용되는 프로그램은 짧은 함수로 구성되어 있다.
  • 코드를 끝없이 위임하는 방식인 간접호출(indirection)을 통해 코드를 이해하고, 공유하고 선택하기 쉬워진다.
  • 함수가 짧아지면 개발시 왔다갔다 하는 비용이 발생하나 IDE를 통해 부담을 줄일 수 있다.
  • 함수의 이름을 잘 지어 둘 경우 본문 코드를 볼 이유를 없앨 수 있다.
  • 주석이 필요한 부분은 함수로 만들어 코드로 녹이고 함수 이름은 의도(intention)가 드러나게 지어주자
  • 짧은 함수는 99% 함수 추출하기를 통해 만들어진다.
  • 임시 변수와 매개변수가 너무 많아질 경우 아래 방법으로 줄여주자
    • 임시 변수를 질의함수를 바꾸기, 매개변수 객체만들기, 객체 통째로 넘기기
    • 함수를 명령으로 바꾸기
  • 함수 추출 대상 선별
    • 주석 : 주석이 설명하는 코드를 함수로 추출
    • 조건문 : 조건문 분해하기, case문 마다 함수 추출하기 > 조건부로직을 다형성으로 바꾸기
    • 반복문 : 반복문내부 로직을 추출, 여러 작업이 섞여 있다면 반복문 쪼개기 고려

3.4 긴 매개변수 목록 - Long Parameter List

  • 이전에는 전역데이터를 줄이기 위해 함수에 필요한 모든 데이터를 매개변수로 전달했으나 매개변수가 많아지면 함수를 이해하기 힘들어 진다.
  • 다른 매개변수에서 값을 얻어오는 매개변수는 매개변수를 질의 함수로 바꾸기로 제거
  • 특정 오브젝트에서 값을 뽑아 각각 전달한다면 객체 통째로 넘기기
  • 항상 함께 전달되는 값이 있다면 매개변수 객체로 만들기
  • 여러 함수를 클래스로 묶기 를 통해 공통 값들을 클래스의 필드로 정의 > 함수형 프로그래밍의 부분 적용 함수

3.5 전역 데이터 - Global Data

  • 전역변수, 클래스 변수, 싱글톤 같은 전역데이터는 코드 어디서든 수정할 수 있어 버그 찾기가 힘들다
  • 변수 캡슐화하기를 통해 함수로 감싸서 데이터 수정하는 부분을 쉽게 찾고 접근을 통제할 수 있다.

3.6 가변 데이터 - Mutable Data

  • 데이터를 변경할 경우 버그가 발생할 수 있다.
  • 그래서 최근의 함수형 프로그래밍은 원본은 두고 값을 복사해서 연산뒤 반환해주는 패러다임임
  • 변수 캡슐화하기: 정해놓은 함수로만 값을 수정
  • 하나의 변수의 용도가 다른 값들을 저장하느라 값을 갱신하는 경우 변수 쪼개기 를 통해 용도별로 독립 변수에 저장
  • 값을 갱신하는 로직은 문장슬라이드, 함수 추출하기를 통해 다른 코드와 분리
  • 질의함수와 변경함수 분리하기를 통해 부작용이 있는 코드는 호출하지 못하게 하자.
  • 가능한 세터 제거하기를 통해 변수 수정의 유효범위를 줄여주자
  • 다른 곳에서 값을 설정할 수 있는 경우 파생변수를 질의함수로 바꾸기를 통해 접근하도록 바꿔줌
  • 여러함수를 클래스로 묶기, 여러함수를 변환함수로 묶기를 통해 갱신 코드의 유효범위 제한
  • 구조체일 경우는 참조를 값으로 바꾸기를 적용하여 값 갱신시 구조체를 통째로 교체하자.

3.7 뒤엉킨 변경 - Divergent change

  • 코드를 수정할 때는 시스템에서 교쳐야할 딱 한 군데를 찾아서 그 부분만 수정하는것이 좋다. -단일책임원칙(SRP - Single responsibility pricsiple)
  • 단일책임원칙(SRP)이 지켜지지 않고 하나의 모듈이 서로 다른 이유로 인해 여러가지 방식으로 변경되는 일이 많을 때 발생함
  • 단계 쪼개기를 통해 DB 연동과 비즈니스 레이어 분리
  • 전체 과정에서 다른 맥락의 함수 호출 빈도가 높다면 함수 추출하기함수 옮기기, 클래스 추출하기로 적당한 모듈을 만들어서 관련 함수를 모아줌

3.8 산탄총 수술 - Shotgun Surgery

  • 뒤엉킨 변경과 비슷하면서도 정반대의 냄새
  • 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 발생
  • 함께 변경되는 대상을 함수옮기기필드 옮기기를 통해 한 모듈로 묶어줌
  • 비슷한 데이터를 다루는 함수가 많다면 여러 함수를 클래스로 묶기 적용
  • 데이터구조를 변환하거나 보강하는 함수들은 여러 함수를 변환함수로 묶기 적용
  • 이렇게 묶은 함수들은 출력결과를 묶어서 단계쪼개기를 통해 다음 단계 로직으로 전달
  • 어설프게 분리된 로직은 함수 함수 인라인하기, 클래스 인라인하기다시 합쳐줌
  • 코드를 재구성하는 중간과정에서는 큰 덩어리로 뭉쳐지는건 상관이 없다.

3.9 기능 편애 - Feature Envy

  • 프로그램을 모듈화 할때는 코드의 영역을 나눈 뒤 영역 안에서의 상호작용은 늘리고 영역간의 상호작용은 줄이는데 주력해야한다.
  • 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈과 상호작용을 많이 할때 발생
  • 외부 객체의 getter 메서드 호출 : 함수옮기기 를 통해 데이터 근처로 옮겨줌
  • 함수 일부에서만 기능 편애할 경우 함수추출하기 로 분해한 후 함수옮기기를 통해 원하는 모듈로 이동
  • 함수에서 사용하는 모듈이 많다면 가장 많은 데이터를 포함한 모듈로 이동
  • 변경할 대상을 한데 모으는 것 : 전략패턴, 방문자패턴, 자기위임

3.10 데이터 뭉치 - Data Clumps

  • 항상 몰려다니는 데이터 뭉치는 보금자리를 따로 마련해줘야 한다.
  • 필드형태의 데이터 뭉치는 클래스 추출하기를 통해 하나의 객체로 묶어줌
  • 메서드 시그니처의 데이터 뭉치는매개변수 객체만들기, 객체 통째로 넘기기를 사용해 매개변수 수를 줄임

클래스로 옮기는 것을 권장한다. 기능편애, 데이터 뭉치를 해결하다 나온 클래스들로 옮길 좋은 동작들이 없는지 확인하고 통합하다 보면 유용한 클래스가 나와 생산성 향상에 기여 할 수 있다.

3.11 기본형 집착 - primitive Obsession

  • 기본형에 집착하지 말고 상황에 맞는 기초 타입에 맞게 정의해서 사용하자(화폐,좌표,구간,전화번호 등)
  • 기본형을 객체로 바꾸기를 통해 다양한 기능을 제공하는 객체로 변경
  • 기본형 코드가 조건부 제어하는 타입코드라면 타입코드를 서브클래스로 바꾸기, 조건부 로직을 다형성으로 바꾸기 적용
  • 자주 뭉쳐다니는 기본형 그룹의 데이터 뭉치는 클래스 추출하기, 매개변수 객체 만들기를 통해 통합

3.12 반복되는 switch문 - Repeated Switches

  • switch이나 if/else 문을 조건부 로직을 다형성으로 바꾸기를 통해 코드베이스를 세련되게 바꿔주자

3.13 반복문 - Loops

  • 현재 일급함수(first-calss function)를 지원하는 언어가 많아졌다 - 함수형프로그래밍
  • 반복문을 파이프라인으로 바꾸기를 적용해서 반복문을 제거해 주자.
  • filter, map 등의 파이프라인 연산으로 원소들이 어떻게 처리되는지 쉽게 파악이 가능하다

3.14 성의 없는 요소 - Lazy Element

  • 함수(메서드), 클래스, 인터페이스 등의 프로그램 요소가 필요 없을 경우도 있다. (오버엔지니어링)
  • 본문코드 그대로 쓰는 경우, 메서드가 하나뿐인 클래스 등
  • 함수 인라인하기, 클래스 인라인하기로 통합, 상속을 사용했다면 계층 합치기를 적용

3.15 추측성 일반화 - Speculative Generality

  • 나중에 필요할거야라는 생각으로 당장 필요없는 모든 후킹 포인트와 특이케이스 처리 로직을 작성해 둘 경우 발생
  • 이해와 관리만 어려워지고 실제로 사용되지 않는다면 낭비일 뿐!
  • 하는일이 거의 없는 추상클래스는 계층 합치기로 제거
  • 쓸데 없이 위임하는 코드는 함수 인라인하기클래스 인라인하기로 삭제
  • 본문에서 사용되지 않는 매개변수는 함수 선언바꾸기로 제거
  • 테스트코드에서밖에 사용하지 않는 케이스는 테스트케이스 삭제 후 죽은 코드 제거하기 적용

3.16 임시 필드 - Temporary Field

  • 특정 상황에서만 값이 설정되는 필드
  • 클래스 추출하기를 통해 분리하고 함수 옮기기를 통해 관련 코드를 옮겨줌
  • 임시 필드의 유효성을 체크후 동작하는 조건부 로직은 특이 케이스 추가 하기로 대안 클래스 생성후 제거

3.17 메시지 체인 - message Chains

  • 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드
  • 클라이언트가 객체 네비게이션 구조에 종속되어 중간 단계가 수정되면 클라이언트 코드 수정 필요
  • 위임 숨기기로 해결
  • 최종 객체의 결과 사용처를 함수 추출하기를 통해 빼낸 뒤 함수 옮기기로 숨겨주자

3.18 중개자 - Middle Man

  • 객체의 대표적인 기능으로 외부로부터 세부사항을 숨겨주는 캡슐화(encapsulation)가 있는데 이를 위해 위임(delegation)이 자주 활용된다.
  • 절반이상의 메서드를 위임하는 것 처럼 위임이 과할 경우 중개자 제거하기처리하는 객체를 직접 호출하도록 변경
  • 위임메서드 제거 후 남은 일이 별로 없다면 호출하는 쪽으로 함수인라인 하기 고려

3.19 내부자 거래 - insider Trading

  • 모듈 사이의 데이터 거래가 많으면 결합도(Coupling)가 높아진다.
  • 데이터를 주고받는 모듈들이 있다면 함수 옮기기필드 옮기기로 분리해줌
  • 여러 모듈이 같은 관심사를 공유하면 제3의 모듈을 만들거나 위임 숨기기를 통하여 다른 모듈이 중간자 역할을 하게 만들자
  • 상속 구조에서 자식클래스에서 부모클래스가 공개한 정보 외에도 접근하려 할 경우 서브클래스를 위임으로 바꾸기, 슈퍼클래스를 위임으로 바꾸기를 적용

3.20 거대한 클래스 - Large Class

  • 한 클래스가 너무 많은 일을 하려다 보면 필드 수와 중복코드가 늘어난다.
  • 클래스 추출하기로 필드 일부를 따로 묶어서 분리함. ex) 접두어, 접미어가 같은 경우
  • 상속관계가 더 적절하다면 슈퍼클래스 추출하기, 타입코드를 서브클래스로 바꾸기 적용
  • 이 과정에서 클래스가 너무 많아 지지 않도록 중복 코드 제거도 같이 병행해준다.

3.21 서로 다른 인터페이스의 대안 클래스들 Alternative Classes with Different Interfaces

  • 클래스의 장점은 언제든 다른 클래스로 교체할 수 있다는 점이다.(동일 인터페이스일 경우)
  • 함수 선언 바꾸기를 통해 메서드 시그니처를 일치 시키고 함수옮기기를 통해 인터페이스가 같아질 때까지 클래스에 통합시킴
  • 이후 대안 클래스들 사이에 중복코드가 생기면 슈퍼클래스 추출하기 적용 고려

3.22 데이터 클래스 - Data Class

  • Data Class란 데이터 필드와 getter / setter 로만 이루어진 클래스이다.
  • public 필드는 레코드 캡슐화 하기 적용
  • 변경하면 안되는 필드는 세터 제거하기 적용하여 접근 봉쇄
  • 데이터 클래스를 많이 사용하는 메서드를 함수 추출하기,함수 옮기기를 통해 데이터 클래스 내부로 옮길 수 있을지 검토
  • 불편 필드는 굳이 캡슐화 하지 않고 필드 자체를 공개해도 무방하다.

3.23 상속 포기 - Refused Bequest

  • 서브클래스에서 부모의 모든 정보가 아닌 관심사 몇개만 필요할 경우가 있다.
  • 같은 계층에 서브클래스를 만들고 메서드 내리기,필드내리기를 활용해서 물려받지 않을 부분을 새로만든 서브클래스로 옮겨서 부모에 공통 부분만 남겨주자.
  • 부모 클래스는 항상 추상클래스여야 한다고 말하는 사람도 많다.
  • 예전에는 계층구조를 잘못 설계한 케이스라고 했지만 실무에서 유용하기에 항상 이렇게 변경 할 필요는 없다는 의견이다.
  • 서브클래스가 부모의 동작은 필요로 하지만 인터페이스는 따르고 싶지 않을때 발생
  • 인터페이스를 따르지 않을 경우 서브클래스를 위임으로 바꾸기, 슈퍼클래스를 위임으로 바꾸기 적용해서 상속에서 벗어나자

3.24 주석 - Comments

  • 주석이 장황하게 달린 원인은 코드가 잘못 작성 됐기 때문일 경우가 많음
  • 리팩터링을 통해 냄새를 걷어내면 주석이 필요 없어지는 경우들이 많다.
  • 특정 블록이 하는일에 주석을 남기고 싶다면 함수 추출하기 적용
  • 추출한 함수에 여전히 설명이 필요하다면 함수 선언 바꾸기로 더 의미있는 이름으로 변경
  • 선행조건을 명시하고 싶다면 어서션 추가하기 적용

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

Reference

마틴파울러 - 리팩터링2판