본문 바로가기
better code

리팩터링 - 마틴 파울러

by marble25 2023. 7. 5.

인상깊게 보았던 부분은 볼드 + 색깔을 변경해 두었다.

Chapter 2: 리팩터링 원칙

  • 리팩터링 하기 전과 후의 코드가 동일하게 동작해야 한다.
  • 소프트웨어를 개발할 때 목적을 분명히 하자. ‘기능 추가’인지, ‘리팩터링’인지.
  • 리팩터링의 궁극적인 목적은 개발 속도를 높임에 있다.

Chapter 3: 코드에서 나는 악취

  • 주석을 달아야 할 부분은 무조건 함수로 만들자. 함수를 짧게 만들어 보자.
  • 매개변수 목록을 줄이자. 매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많다.
  • 전역 데이터를 주의하자. 전역 데이터는 코드베이스 어디서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아내기 어렵다.
  • 프로그램을 모듈화할 때에는 코드를 여러 영역으로 나눈 후 영역 안의 상호작용은 늘리고 영역 사이 상호작용은 줄이자.
  • 당장 걸리적거리는 코드는 눈앞에서 치우자.
  • 주석을 남겨야겠다는 생각이 들면, 주석이 필요 없는 코드로 리팩토링해보자.

Chapter 4: 테스트 구축하기

  • TDD(테스트 주도 개발): 테스트 코드 작성 → 테스트 코드를 통과하도록 코딩 → 리팩토링 을 반복하는 개발론을 의미한다.
  • 테스트 코드에서 개별 테스트 코드별로 픽스쳐를 새로 만들자. 이렇게 하면 모든 테스트를 독립적으로 구성할 수 있다.
  • Happy path를 벗어나는 경계 조건에서 일어나는 일에 대한 테스트도 함께 작성하자.
  • 테스트는 모든 버그를 걸러주지는 못하더라도, 리팩터링할 수 있는 보호막은 되어준다.

Chapter 6: 기본적인 리팩터링

  1. 함수 추출
    • 코드를 보고 무슨 일을 하는지 잘 모르겠으면 그 부분을 함수로 추출하자.
    • 함수 이름에는 ‘어떻게’가 아닌, ‘무엇’을 하는 함수인지 잘 드러나야 한다.
  2. 함수 인라인하기
    • 지나치게 함수화 되었다면, 이를 다시 함수 → 인라인 코드로 변경하자.
  3. 변수 추출하기
    • 표현식이 너무 복잡할 때에는 지역변수를 활용해서 쪼개보자.
  4. 변수 인라인하기
    • 지나치게 변수화되었다면, 다시 변수 → 인라인 코드로 변경하자.
  5. 함수 선언 바꾸기
    • 이름 바꾸기와 매개 변수 추가를 모두 할 때에는 이름 바꾸고, 테스트하고, 매개변수 추가하고, 테스트하는 식으로 진행하자.
  6. 변수 캡슐화하기
    • 데이터는 함수보다 제어하기 힘들다. 데이터는 참조하는 모든 부분을 바꿔야 정상 동작하기 때문이다. 따라서 데이터를 옮길 때에는 캡슐화를 먼저 하는것이 도움이 된다. 데이터 캡슐화는 데이터 변경 전 검증이나 변경 후 추가 로직을 끼워넣기 좋다.
    • 불변 데이터는 가변 데이터보다 캡슐화할 이유가 적다. 불변 데이터는 그냥 복제하면 되기 때문이다.
  7. 변수 이름 바꾸기
    • 함수 호출 한 번으로 끝나지 않고 값이 영속되는 필드라면 이름에 더 신경써야 한다.
  8. 매개변수 객체 만들기
    • 데이터 항목 여러 개가 뭉쳐 다닌다면 데이터 구조 하나로 모아주자. 매개변수 수가 줄고, 새 데이터 구조가 문제 영역을 훨씬 간결하게 표현할 수도 있다.
  9. 여러 함수를 클래스로 묶기
    • getter 함수를 이용, 값을 계산하는 로직을 함수로 넣어주자.
  10. 여러 함수를 변환 함수로 묶기
    • 변환 함수는 원본 데이터를 입력받아서 필요한 정보를 모두 도출한 후, 각각을 출력 데이터 필드에 넣어 반환한다.
  11. 단계 쪼개기
    • 동작을 연이은 두 단계로 쪼개고, 중간 데이터 구조를 만들어 그 둘을 연결해 보자. 예컨대, 입력값을 다루기 편한 형태로 가공한 후 실제 로직으로 들어가는 등이 있을 수 있다.

Chapter 7: 캡슐화

  1. 레코드 캡슐화하기
    • 가변 데이터를 저장할 때, 객체를 사용하면 사용자는 저장된 값과 계산된 값을 구분할 필요가 없다.
    • 읽기를 제공할 때 크게 3가지를 생각할 수 있다.
      • API 형식: 필요한 필드를 입력 인자로 모두 받아서 해당 위치의 값을 리턴한다. → 패턴이 다양할 경우 작성할 코드가 많아진다.
      • 실데이터 형식: 실제 데이터를 리턴한다. 원본에 영향을 끼치지 않도록 깊은 복사한 객체를 리턴한다. → 복제 비용이 발생한다.
      • 재귀적 레코드 캡슐화: nested된 모든 layer를 클래스로 만들어 모두 제어한다. → 일이 상당히 커진다.
  2. 컬렉션 캡슐화하기
    • 레코드 캡슐화와 유사하다. setter 메소드는 제거하는 것이 좋다. add와 remove를 따로 메소드로 만들자.
  3. 기본형을 객체로 바꾸기
    • 상태를 나타내는 enum에서 많이 사용될 것 같다.
      • ex) Pending, Running, Stopped, Failed
    • 이런 기본형 사이의 우선순위 정의, 값 검증 등을 할 수 있어서 좋다.
  4. 임시 변수를 질의 함수로 바꾸기
    • 변수 대신 함수로 만들어놓으면 비슷한 계산을 수행하는 다른 함수에서도 사용할 수 있다.
    • 클래스 내에서 적용했을 때 효과가 크다.
  5. 클래스 추출하기
    • 일부 데이터와 메소드를 묶을 수 있다면 어서 분리하자.
  6. 클래스 인라인하기
    • 클래스 추출하기의 역연산이다. 두 클래스의 기능을 지금과 다르게 분배하고 싶을 때도 인라인 후 다시 추출하는 과정이 빠를 수 있다.
  7. 위임 숨기기
    • chaining을 길게 가져가야 하는 경우 위임 메소드를 만들어 위임 객체의 존재를 숨긴다면 delegate chaining이 바뀌더라도 위임 메소드만 바꿔주면 된다.
  8. 중개자 제거하기
    • 위임 숨기기의 역연산. 위임 객체 중간 객체를 사용하고 싶을 때마다 위임 메소드를 만드는 것은 성가실 수 있다.
  9. 알고리즘 교체하기
    • 다른 메소드를 먼저 적용하여 알고리즘을 간소화해보자.

Chapter 8: 기능 이동

  1. 함수 옮기기
    • 어떤 함수가 자신이 속한 모듈 A의 요소보다 다른 모듈 B의 요소들을 더 많이 참조한다면 모듈 B로 옮겨줘야 한다.
    • 중첩함수를 사용하다 보면 숨겨진 데이터끼리 상호 의존하기 쉬우니 중첩 함수는 되도록 만들지 말자.
  2. 필드 옮기기
    • 현재 데이터 구조가 적절치 않으면 곧바로 수정하자.
    • 필드가 캡슐화되어 있지 않다면 캡슐화를 먼저 적용하자. 클래스의 메소드는 데이터를 옮기는 작업을 쉽게 해준다.
  3. 문장을 함수로 옮기기
    • 문장들을 함수로 옮기려면 그 문장들이 호출되는 함수의 일부분이라는 확신이 있어야 한다. 한 몸은 아니지만 여전히 같이 호출되어야 한다면 그들을 또 하나의 함수로 추출하자.
  4. 문장을 호출한 곳으로 옮기기
  5. 인라인 코드를 함수 호출로 바꾸기
  6. 문장 슬라이드하기
    • 관련된 코드가 모여 있으면 이해하기 더 쉽다. 선언 코드를 슬라이드해서 처음 사용하는 곳까지 끌어내려보자. 겉보기 동작이 바뀌는지 항상 조심하라.
    • 부수효과가 있는 코드를 슬라이드하거나 건너뛰어야 한다면 훨씬 신중하자.
  7. 반복문 쪼개기
    • 반복문 내에 다른 의미를 가진 코드가 존재한다면 반복문을 쪼개자. 반복문을 두 번 실행해야 하므로 불편해할 수 있지만, 리팩터링과 최적화를 구분하라!
  8. 반복문을 파이프라인으로 바꾸기
  9. 죽은 코드 제거하기
    • 코드가 더 이상 사용되지 않으면 지우자. 다시 필요할 날이 올까 걱정하지 말라. 버전 관리 시스템이 있다!

Chapter 9: 데이터 조직화

  1. 변수 쪼개기
    • 가변 변수의 경우 대입이 두 번 이상 이루어진다면 여러 역할을 수행한다는 신호다. 역할이 둘 이상인 변수는 쪼개야 한다. 코드를 읽는 이에게 큰 혼란을 줄 수 있다.
    • 입력 매개변수를 수정해야 할 때도 마찬가지이다.
  2. 필드 이름 바꾸기
  3. 파생 변수를 질의 함수로 바꾸기
    • 값을 쉽게 계산해낼 수 있는 변수들을 모두 제거하자. 예외가 있다면, 소스 데이터가 불변이거나 파생 데이터를 잠시 쓰고 버리는 상황이라면 굳이 필요 없다.
  4. 참조를 값으로 바꾸기
    • 값 객체는 자유롭게 활용하기 좋은데, 불변이기 때문이다. 분산 시스템이나 동시성 시스템에서 유용하게 사용할 수 있다.
  5. 값을 참조로 바꾸기
    • 같은 데이터를 물리적으로 복제해서 사용할 때 문제는 데이터를 갱신할 때이다. 모든 사용처를 빠짐 없이 갱신해야 하기 때문이다. 이럴 때에는 singleton 패턴을 사용하여 하나의 객체만 리턴할 수 있도록 하고, 값을 참조로 바꾸자.
  6. 매직 리터럴 바꾸기
    • 상수를 정의하고 숫자 대신 상수를 사용하도록 하는 것이 대부분 좋다. 단, 모두가 의미를 이해할 수 있거나, 함수 하나에서만 쓰이고 함수가 맥락 정보를 충분히 제공하는 경우 굳이 상수로 바꾸지 않아도 된다.

Chapter 10: 조건부 로직 간소화

  1. 조건문 분해하기
    • 조건식과 조건식에 딸린 조건절을 함수로 추출한다.
  2. 조건식 통합하기
    • 복잡한 조건식을 함수로 추출하면 코드의 의도가 훨씬 분명하게 드러나는 경우가 많다. 대신, 진짜 독립적인 검사들이라고 생각되면 이 리팩터링을 해서는 안된다.
  3. 중첩 조건문을 보호 구문으로 바꾸기
    • if else 조건문을 사용하는 경우는 크게 두 가지가 있다.
      • if 경로와 else 경로 모두 정상 경로라면 if else를 그대로 사용
      • 보호 구문: if문에서 조건 검사 후 비정상적이면 함수에서 빠져나오는 형태. 보통 return으로 함수에서 빠져나온다. 보호 구문은 때에 따라 조건을 역으로 바꿔야 하는 경우도 있다.
  4. 조건부 로직을 다형성으로 바꾸기
    • 복잡한 조건문의 경우 각각의 경우를 여러 개의 클래스로 나누어서 처리할 수 있다. 이때, 각각의 클래스는 같은 이름의 메소드를 가지되, 메소드 내의 동작을 다르게 구성한다.
  5. 특이 케이스 추가하기
    • null과 같은 특이 케이스에 해당하는 클래스를 만든다. 이 클래스는 기본 클래스의 특수한 형태이다.
  6. assertion 추가하기
    • 반드시 참이어야 하는 조건을 assertion으로 추가한다. 단위 테스트보다 직관적이다. 데이터를 읽어와서 검사하는 작업은 assertion이 아닌, 프로그램의 로직으로 예외 처리하자.
  7. 제어 플래그를 탈출문으로 바꾸기
    • 제어 플래그를 꼭 필요할 때에만 사용하자.

Chapter 11: API 리팩터링

  1. 질의 함수와 변경 함수 분리하기
    • 겉보기 부수 효과가 있는 함수와 없는 함수는 명확히 구분하는 것이 좋다. ‘질의 함수는 모두 부수 효과가 없어야 한다.’
    • 겉보기 부수 효과는 캐싱과 같이 외부에서 봤을 때에는 동일하게 동작하는 부수 효과는 제외한다.
  2. 함수 매개변수화하기
    • 두 함수의 로직이 아주 비슷하고 리터럴 값만 다르면, 다른 값만 매개변수로 받아 처리하는 함수로 합쳐서 중복을 없앨 수 있다. 함수의 유용성이 커진다.
  3. 플래그 인수 제거하기
    • 플래그 인수는 함수의 로직을 결정짓는 플래그로 작용하는 인수를 말한다. 특히, boolean 플래그 인수는 호출하는 쪽에서 봤을 때 이해하기 어렵다.
    • 플래그 인수를 제거할 경우 코드가 깔끔해짐과 동시에 프로그래밍 도구에도 도움을 준다. 프로그래밍 툴이 특별 로직 호출과 일반 로직 호출의 차이를 더 잘 이해하게 해준다.
  4. 객체 통째로 넘기기
    • 하나의 레코드에서 값을 가져와서 넘기는 경우, 그 대신 레코드를 통째로 넘긴다.
    • 다만, 함수와 레코드의 의존성을 원치 않으면 이 리팩터링을 수행하지 않는다.
    • 한편, 객체 중 항상 똑같은 일부를 사용하는 코드가 많으면 그 기능만 묶어서 클래스로 추출하는게 좋을 수 있다.
  5. 매개변수를 질의 함수로 바꾸기
    • 호출되는 함수가 스스로 쉽게 결정할 수 있는 값을 매개변수로 넘기는 것 또한 중복이다.
    • 단, 원치 않는 의존성이 생기는 경우는 피하자.
  6. 질의 함수를 매개변수로 바꾸기
    • 길고 반복적인 매개변수 목록을 만드는 것과 함수끼리 많은 것을 공유하여 수많은 결합을 만들어내는것 사이에서 균형을 찾자.
    • 이 리팩터링을 통해 똑같은 값을 건네면 항상 똑같은 값이 나올 것이라 기대할 수 있다.
    • 이 리팩터링은 호출자가 복잡해지는 단점이 있다.
  7. 세터 제거하기
    • initial한 상태에서만 객체 변화가 있고 그 후에는 없는 경우 세터를 명시적으로 제거하자.
  8. 생성자를 팩터리 함수로 바꾸기
    • 생성자의 몇 가지 제약을 탈피하고자 할 때 사용해 보자.
  9. 함수를 명령으로 바꾸기
    • command 패턴은 평범한 함수 메커니즘보다 훨씬 유연하게 함수를 제어할 수 있다. undo 같은 연산도 제공할 수 있고, 상속과 훅을 이용해서 사용자 맞춤형으로 만들 수 있다.
    • command 패턴은 유연성을 제공하는 대신 복잡성을 키운다.
  10. 명령을 함수로 바꾸기
    • 명령 객체는 큰 연산 하나를 여러 개의 작은 메소드로 나누고, 나눠진 메소드끼리 정보를 공유할 수 있는 장점이 있다.
    • 하지만 로직이 크게 복잡하지 않으면 명령 객체보다는 일반 함수를 사용하자.
  11. 수정된 값 반환하기
    • 함수 내부에서 값을 수정했다면, 값을 반환해보자.
  12. 오류 코드를 예외로 바꾸기
    • 일반적인 오류 코드와는 다르게 예외는 독자적인 흐름이 있어 추가적인 처리를 할 필요 없게 해준다.
  13. 예외를 사전확인으로 바꾸기
    • 예외는 말 그대로 예외적으로 동작할 때만 쓰여야 한다.
    • 문제 조건을 함수 호출 전에 검사할 수 있으면, 예외를 검사하는 대신 호출하는 곳에서 조건을 검사하도록 하자.

Chapter 12: 상속 다루기

  1. 메서드 올리기
    • 무언가 중복되었다는 것은 한 쪽의 변경이 다른 쪽에는 반영되지 않을 수 있다는 위험을 수반한다. 메서드를 슈퍼 클래스로 올려보자
  2. 필드 올리기
  3. 생성자 본문 올리기
    • 생성자는 할 수 있는 일과 호출 순서에 제약이 있기 때문에 일반적인 함수와는 다르게 접근해야 한다.
  4. 메서드 내리기
    • 슈퍼클래스에 불필요한 조건부 로직이 있다면 다형성으로 바꿔서 처리해보자
  5. 필드 내리기
  6. 타입 코드를 서브클래스로 바꾸기
  7. 서브 클래스 제거하기
  8. 슈퍼클래스 추출하기
    • 비슷한 일을 수행하는 두 클래스가 있으면 상속 메커니즘을 활용, 비슷한 부분을 슈퍼 클래스로 옮겨담자. 이 과정은 12.1과 12.2, 12.3 등을 포함할 수 있다.
  9. 계층 합치기
  10. 서브클래스를 위임으로 바꾸기
    • 상속은 한 번만 사용할 수 있다. 여러 가지 기준으로 분리된 서브 클래스는 불가늫아다.
    • 상속은 클래스의 관계를 긴밀하게 결합한다. 부모의 변경은 자식에게 크리티컬할 수 있다.
    • delegate(위임)은 상속보다 결합도가 훨씬 약하다.
  11. 슈퍼클래스를 위임으로 바꾸기

후기

작성자가 지독하게 원칙을 중시하는 사람 같다. 모든 과정마다 테스트를 하는 것을 넘어서 리팩토링 과정에서 일부러 테스트에 실패하는 코드를 만들고, 테스트를 만족시킬 수 있도록 수정하는 것을 보면서 혀를 내두를 정도다. 하지만 지나치게 많이 하지는 않더라도 적절하게 활용한다면 좋은 전략인 것 같다. 리팩토링에서의 가장 큰 문제는 같이 적용되어야 하는 부분을 빼먹어서 기존 기능에 버그를 발생시키는 것 같다. 물론 테스트 코드가 있찌만 테스트의 커버리지를 넘어가는 경우에는 큰 문제가 될 수 있다.

리팩토링 전략별로 배경과 과정, 예시를 제시한 방식은 확실히 도움이 되었다. 리팩토링 패턴을 명료하게 정리하여 혼동이 적었다. 앞으로 내가 잘 적용하지 않고 있던 전략들을 적용한다면 코드 리뷰에서도 커멘트를 적게 받을 수 있을 것 같고, 훨씬 깔끔한 코드를 작성할 수 있을 것 같다.

'better code' 카테고리의 다른 글

헤드퍼스트 디자인패턴  (0) 2023.07.19
객체지향의 사실과 오해  (0) 2023.07.02
[TIL] copy-on-write 방법론 - js에서 퍼포먼스 테스트  (0) 2023.04.22
Clean Code (5/n)  (0) 2022.02.21
Clean Code (4/n)  (0) 2022.02.16