[리팩토링] Chapter 3. 코드에서 나는 악취

업데이트:

코드에서 나는 악취

냄새 나면 당장 갈아라

코드에서 악취가 난다는 기준은 아래와 같이 정리할수 있다.

3.1 기이한 이름

함수, 모듈, 변수, 클래스 등은 그 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알수 있도록 신경 써서 이름을 지어야 한다.

이름에 관련한 리팩터링은 아래와 같다.

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

3.2 중복 코드

중복 코드 관련 리팩터링은 아래와 같다.

  • 함수 추출하기
  • 문장 슬라이드
  • 메서드 올리기 : 같은 부모로부터 파생된 서브 클래스들에 코드가 중복되어 있다면 중복된 코드를 부모의 메서드로 정의하는 것

3.3 긴 함수

함수가 길수록 사람들은 함수를 이해하기 힘들다.

긴 함수 관련 리팩터링 작업은 아래와 같다.

  • 함수 추출하기 : 99% 는 이 작업을 수행한다.
  • 조건문 분해하기 > case 문 마다 함수 추출하기 > 조건부 로직을 다향성으로 바꾸기
  • 반복문 쪼개기

3.4 긴 매개 변수 목록

매개변수 목록이 길어질수록 코드를 이해하기 어렵다.

이와 관련 리팩터링 작업은 아래와 같다.

  • 매개 변수를 질의 함수로 바꾸기
  • 객체 통째로 넘기기
  • 매개변수 객체 만들기
  • 여려 함수를 클래스로 묶기

3.5 전역 데이터

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

이와 관련한 리팩터링 작업은 아래와 같다.

  • 변수 캡슐화 하기 : 데이터를 수정하는 부분을 쉽게 찾을 수 있고 접근을 통제할수 있게 된다.

3.6 가변 데이터

데이터를 변경했더니 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다. 코드의 다른 곳에서는 다른 값을 기대한다는 사실을 인식하지 못한 채 수정해버리면 프로그램이 오작동을 한다.

이와 관련한 리팩터링 작업은 아래와 같다.

  • 변수 캡슐화하기
  • 질의 함수와 변경 함수 분리하기
  • 세터 제거하기
  • 참조를 값으로 바꾸기 : 내부 필드를 직접 수정하지 말고 구조체를 통째로 교체

3.7 뒤엉킨 변경

코드를 수정할 때는 시스템에서 고쳐야 할 딱 한 군데를 찾아서 그 부분만 수정할 수 있기를 바란다. 이렇게 할 수 없다면 뒤엉킨 변경산탄총 수술 중 하나가 풍긴다.

뒤엉킨 변경은 단일 책임 원칙이 제대로 지켜지지 않을 때 나타난다.

이와 관련한 리팩터링 작업은 아래와 같다.

  • 단계 쪼개기
  • 함수 옮기기
  • 함수 추출하기
  • 클래스 추출하기

3.8 산탄총 수술

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

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

이와 관련한 리팩터링 작업은 아래와 같다.

  • 함수 옮기기, 필드 옮기기 로 한 모듈에 묶어둔다.
  • 여러 함수를 클래스로 묶기
  • 어설프게 분리된 로직을 함수 인라인하기, 클래스 인라인하기 를 통해 하나로 묶는다.

3.9 기능 편애

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

  • 함수를 상호 작용할 모듈로 이동시키면 된다(함수 옮기기)

3.10 데이터 뭉치

데이터 항목은 서로 뭉치는 경향이 있다. 이렇게 몰려다니는 데이터 뭉치는 보금자리를 따로 마련해줘야 한다.

  • 데이터 뭉치를 찾아서 클래스 추출하기
  • 매개변수 객체 만들기객체 통째로 넘기기

3.11 기본형 집착

자신에게 주어진 문제에 딱 맞는 기초 타입(화폐, 좌표, 구간 등)을 직접 정의하기를 몹시 꺼리는 사람이 많다. 예를 들어 전화번호를 단순히 문자 집합으로만 표현하기에는 아쉬움이 많다. 최소한 사용자에게 보여줄 때는 일관된 형식으로 출력해주는 기능이라도 갖추어야 한다.

  • 기본형을 객체로 바꾸기
  • 타입 코드를 서브클래스로 바꾸기
  • 조건부 로직을 다형성으로 바꾸기

3.12 반복되는 switch 문

switch 문은 조건부 로직을 다형성으로 바꾸기 로 최대한 리팩토링을 해야한다.

3.13 반복무

반복문을 파이프라인으로 바꿔서 리팩토링을 해보자

  • filter, map, ..

3.14 성의 없는 요소

본문 코드를 그대로 쓰는 것과 다름 없는 함수나 실질적으로 메서드가 하나뿐인 클래스들을 리팩토링을 통해 제거하자

  • 함수 인라인하기
  • 클래스 인라인하기
  • 계층 합치기

3.15 추측성 일반화

나중에 필요할 거야 라는 생각으로 당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다. 실제로 사용하게 되면 다향이지만, 그렇지 않는다면 쓸데없는 낭비일 뿐이다. 당장 걸리적거리는 코드는 눈 앞에서 치워버리자.

  • 하는 일이 거의 없는 추상 클래스는 계층합치기 로 제거
  • 함수 인라인, 클래스 인라인
  • 사용되지 않는 매개변수는 함수 선언 바꾸기 로 제거

3.16 임시 필드

간혹 특정 상황에서만 값이 설정되는 클래스도 있다. 하지만 객체를 가져올 때는 당연히 모든 필드가 채워져 있으리라 기대하는 게 보통이라, 이렇게 임시 필드를 갖도록 작성하면 코드를 이해하기 어렵다.

  • 클래스 추출하기, 함수 옮기기 를 통해 임시 필드들과 관련된 코드를 모조리 새 클래스에 몰아 넣는다.
  • 임시 필드들이 유효한지를 확인한 후 동작하는 조건부 로직이 있을 수 있는데, 특이 케이스 추가하기 로 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거할수 있다.

3.17 메시지 체인

메시지 체인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다. 이는 클라이언트가 객체 내비게이션 구조에 종속됐음을 의미한다. 그래서 내비게이션 중단 단계를 수정하면 클라이언트 코드도 수정되어야 한다.

  • 위임 숨기기
  • 함수 추출하기 로 결과 객체를 사용하는 코드 일부를 따로 빼낸 다음 함수 옮기기 로 체인을 숨길 수 있는지 살펴보자.

3.18 중개자

객체의 대표적인 기능 하나로, 캡슐화가 있다. 캡슐화하는 과정에서 위임이 자주 활용이 되는데, 하지만 지나치면 문제가 된다. 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있다면 어떤가?

  • 중개자 제거하기 를 활용하여 실제로 일을 하는 객체와 직접 소통하게 하자

3.19 내부자 거래

모듈 사이의 데이터 거래가 많으면 결합도가 높아진다. 일이 돌아가게 하려면 거래가 이뤄질 수 밖에 없지만, 그 양을 최소로 줄이고 모두 투명하게 처리해야 한다.

  • 은밀히 데이터를 주고받는 모듈들이 있다면 함수 옮기기 와 필드 옮기기 기법으로 떼어놓아서 사적으로 처리하는 부분을 줄인다.
  • 여러 모듈이 같은 관심사를 공유한다면 공통 부분을 정식으로 처리하는 제 3의 모듈을 만들거나 위임 숨기기 를 이용한다.

3.20 거대한 클래스

한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어난다.

  • 클래스 추출하기
  • 슈퍼 클래스 추출하기
  • 타입 코드를 서브 클래스로 바꾸기

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

3.22 데이터 클래스

데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말한다.

  • public 필드가 있다면 레코드 캡슐화하기
  • 변경하면 안 되는 필드는 세터 제거하기

3.23 상속 포기

서브 클래스는 부모로부터 메서드와 데이터를 물려받는다. 하지만 부모의 유산을 원치 않거나 필요 없다면 어떻게 해야 할까? 수 많은 유산 중에 관심 있는 몇 개만 받고 끝내려는 경우는 얾마든지 있을 수 있다.

  • 서브 클래스를 위임 으로 바꾸기나 슈퍼클래스를 위임 으로 바꾸기 를 활용해서 상속 메커지즘에서 벗어나보자.

3.24 주석

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

Reference

리팩터링 2판

카테고리:

업데이트:

댓글남기기