[소프트웨어 공학]리팩터링 3장

bluejoy·2022년 2월 12일
0

소프트웨어 공학

목록 보기
3/4

전 단원에서는 리팩터링의 전반적인 원칙에 대해 배웠다. 이번에는 리팩터링을 언제 시작하고 언제 그만할지를 판단하는 법을 배워볼 것이다.
리팩터링할 시점을 설명하는데에 냄새란 표현을 사용한다. 냄새가 나는 시작 시점을 찾는 법을 알아본다. 그리고 종료할 시점보다는 리팩터링하면 해결할 문제의 징후를 표현하겠다.
(이게 진짜 무슨 소리냐)

3.1 리팩터링의 정의

코드를 명료하게 표현하는데 가장 중요한 요소 하나는 이름이다. 함수, 모듈, 변수, 클래스 등은 그 이름만 보고도 무슨 일을 하고 어떻게 사용할지 명확히 알도록 이름을 지어야 한다.

그러나 이름 짓기는 정말 어렵다. 그렇기에 우리가 가장 많이 사용하는 리팩터링 함수 선언 바꾸기, 변수 이름 바꾸기, 필드 이름 바꾸기처럼 이름을 바꾸는 리팩터링들이다.

마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 있다. 그러므로 혼란스러운 이름을 잘 정리해서 코드를 간결하게 만들자.

코멘트

이름 짓는건 정말 어렵다. 영어 공부를 좀 해야지 어휘 폭이 넓어질 듯.

3.2 중복 코드

똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합해 더 나은 프로그램을 만들 수 있다.

3.3 긴 함수

짧은 함수의 중요성

짧은 함수들로 이루어진 프로그램은 간접 호출(indirection)의 효과, 즉 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점을 가진다.

예전에는 서브루틴을 호출하는 비용이 컸기 때문에 짧은 함수를 꺼렸다. 그렇지만 지금은 ㄱㅊ.

코드를 읽는 사람 입장에서는 함수가 하는 일을 파악하기 위해 왔다 갔다 해야하므로 여전히 부담이 되지만 함수 선언부와 호출부를 빠르게 이동하거나 동시에 보여주는 개발 환경을 활용하면 이 부담이 줄어든다. 하지만 짧은 함수로 구성된 코드를 쉽게 만드는 가장 확실한 방법은 좋은 이름이다. 함수 이름을 잘 지어두면 본문 코드를 볼 이유가 사라진다.

적극적으로 함수를 쪼개야 한다. 주석을 달아야 할 만한 부분은 무조건 함수로 만든다. 그 함수 본문에는 원래 주석으로 설명하려던 코드를 담고, 함수 이름은 동작 방식이 아닌 의도(intention)이 드러나게 짓는다.

원래 코드보다 길어지더라도 함수로 뽑아야 한다. 단, 함수 이름에 코드의 목적을 드러내야 한다. 무엇을 하는지를 코드가 잘 설명해주지 못할 수록 함수로 만드는 게 유리하다.

짧은 함수 만들기

함수를 짧게 만드는 작업으 99%는 함수 추출하기가 차지한다. 함수 본문에서 따로 묶어 빼면 좋은 코드 덩어리를 찾아 새로운 함수로 만드는 것이다.

함수가 매개변수와 임시 변수를 많이 사용한다면 추출 작업에 방해가 된다. (테스트에도 방해가 되겠죠!) 그러므로 임시 변수를 질의 함수로 바꾸기로 임시 변수의 수를, 매개변수 객체 만들기객체 통째로 넘기기로 매개변수의 수를 줄이자.

이 리팩터링을 적용해도 여전히 임시 변수와 매개변수가 너무 많다면 더 큰 수술이라 할 수 있는 함수를 명령으로 바꾸기를 고려해보자.

추출할 코드 찾기

좋은 방법은 주석을 참고하는 것이다. 주석은 코드만으로 목적을 이해하기 어려운 부분에 달려 있는 경우가 많다. 이런 주석을 찾으면 주석이 설명하는 코드와 함께 함수로 빼내고, 함수 이름은 주석 내용을 토대로 짓는다.

코드가 단 한줄이어도 따로 설명할 필요가 있다면 함수로 추출하는게 좋다.

조건문이나 반복문도 추출 대상의 실마리를 제공한다. 조건문은 조건문 분해하기로 대응한다. 거대한 switch문을 구성하는 case문마다 함수 추출하기를 적용해서 각 case의 본문을 함수 호출문 하나로 바꾼다. 같은 조건을 기준으로 나뉘는 switch문이 여러 개라면 조건문을 다형성으로 바꾸기를 적용한다.

반복문도 그 안의 코드와 함께 추출해서 독립된 함수로 만든다. 추출한 반복문 코드에 적합한 이름이 떠오르지 않는다면 성격이 다른 두 가지 작업을 섞여 있기 때문일 수 있다. 이럴 때는 반복문 쪼개기를 적용해서 작업을 분리한다.

코멘트

뼈가 되고 살이 되는 조언인듯.

3.4 긴 매개변수 목록

과거

예전에는 함수에 필요한 것을 모조리 매개변수로 전달하라고 배웠다. 그래야 전역 데이터가 늘어나는 사태를 막을 수 있기 때문이다.
그러나 매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많았다.

개선하기

  • 종종 다른 매개변수에서 값을 얻어올 수 있는 매개변수가 있을 수 있는데, 이런 매개 변수는 매개 변수를 질의 함수로 바꾸기로 제가할 수 있다. (그러면 i/o 작업이나 통신 작업은 당연히 예외겠지?!))
  • 사용 중인 데이터 구조에서 값을을 뽑아 각각을 별개의 매개변수로 전달하는 코드라면 객체 통째로 넘기기를 적용해서 원본 데이터 구조를 그대로 전달한다.
  • 항상 함꼐 전달되는 매개변수들은 매개변수 객체 만들기로 하나로 묶어버린다.
  • 함수의 동작 방식을 정하는 플래그 역활의 매개 변수는 플래그 인수 제거하기로 없애준다.
  • 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때는 여러 함수를 클래스로 묶기를 이용해 공통 값들을 클래스의 필드로 정의한다. 함수형 프로그래밍이었다면 일련의 부분 적용 함수(partially applied function)들을 생성한다고 말했을 것이다.

코멘트

부분 적용 함수 : https://velog.io/@kwonh/ES6-%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98-%EC%BB%A4%EB%A7%81-%EB%B6%80%EB%B6%84%EC%A0%81%EC%9A%A9%ED%95%A8%EC%88%98 이게 무슨 소리지?

3.5 전역 데이터

나쁜 전역 데이터

전역 데이터를 주의해야 한다. 전역 데이터는 코드 베이스 어디에서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아낼 메커니즘이 없다는게 문제다.
문제가 생기면, 그 원인이 되는 코드를 찾아내기도 힘들다.

방지하기

이를 방지하기 위해 우리가 사용하는 대표적인 리팩터링은 변수 캡슐화하기다. 다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 이 기법을 가장 먼저 적용하자.
이런 데이터를 함수로 감싸는 것만으로도 데이터를 수정하는 부분을 쉽게 찾을 수 있고, 접근을 통제할 수 있다. 더 나아가 접근자 함수를 클래스나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위를 최소로 줄이는 것도 좋다.

더나아가

전역 데이터가 가변이라면 특히 다루기 까다롭다. 프로그램이 구동된 후에는 값이 바뀌지 않는다고 보장할 수 있는 전역 데이터는 그나마 안전한 편이다. (고정 상수 값 변수 사용 가능?!) 전역 데이터가 아주 조금만 있더라도 캡슐화 하자 소프트웨어가 진화하는데에 따르는 변화에 대처할 수 있다.

코멘트

그러면 다량의 비밀키? ex) env파일의 정보 같은 경우에는 전역으로 관리하지말고 한번 불러서 사용해야하나?

3.6 가변 데이터

함수형 프로그래밍에서의 불변 데이터

데이터를 변경하면 예기치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다. 이런 이유로 함수형 프로그래밍에서는 데이터는 절대 변하지 않고, 데이터를 변경하려면 반드시 (원래 데이터는 그대로 둔 채) 변경하려는 값에 해당하는 복사본을 만들어서 반환한다는 개념을 기본으로 삼고 있다.
그러나 함수형 언어가 프로그래밍에서 차지하는 비중은 여전히 적고, 변수 값을 바꿀 수 있는 언어를 사용하는 프로그래머가 더 많다.

나머지 언어에서의 불변 데이터

불변성이 주는 장점을 포기하지 않고 무분별한 데이터 수정에 따른 위험을 줄이는 방법은 얼마든지 있다.

  • 변수 캡슐화하기를 적용하여 정해놓은 함수를 거쳐야만 값을 수정할 수 있도록 하면 값이 어떻게 수정되는지 감시하거나 코드를 개선하기 쉽다.
  • 하나의 변수에 용도가 다른 값들을 저장하느라 값을 갱신하는 경우라면 변수 쪼개기를 이용하여 용도별로 독립 변수에 저장하게 하여 값 갱신이 문제를 일으킬 여지를 없앤다.
  • 갱신 로직은 다른 코드와 떨어트려 놓자. 문장 슬라이드하기함수 추출하기를 이용해 무언가를 갱신하는 코드로부터 부작용이 없는 코드를 분리하자.
  • API를 만들 때는 질의 함수와 변경 함수 분리하기를 활용해서 꼭 필요한 경우가 아니라면 붑작용이 있는 코드를 호출 못하게 한다.
  • 가능한 세터 제거하기도 적용한다. (접근자랑은 분위기가 다르네) 세터를 호출하는 클라이언트를 찾는 것만으로도 변수의 유효범위를 줄이는데 도움될 때가 있다.

가변 데이터를 바꾸기

  • 값을 다른 곳에서 설정할 수 있는 가변 데이터는 좋지 않다. 파생 변수를 질의 함수로 바꾸기로 코드 전체를 적절히 바꿔주자.
  • 변수의 유효 범위가 좁다면 가변 데이터여도 문제가 적다. 그래도 여러 함수를 클래스를 묶기여러 함수를 변환 함수로 묶기를 활용해 코드의 유효 범위를 (클래스나 변환으로) 제한하자.
  • 구조체처럼 내부 필드에 데이터를 담고 있다면 (js obejct나 python dict?) 참조를 값으로 바꾸기를 적용하여, 내부 필드를 직접 수정하지 말고 구조체를 통째로 교체하는 편이 낫다.

코멘트

구조체랑 object나 dict가 다르게 작동하나? 전부 첫 주소만 보내는거 아닌가욤

3.7 뒤엉킨 변경

소프트웨어의 구조를 변경하기 쉬운 형태로 조직해야한다. 코드를 수정할 때는 시스템에서 고쳐야할 딱 한 군데를 찾아서 그 부분만 수정할 수 있기를 바란다.이렇게 할 수 없다면 뒤엉킨 변경산탄총 수술 중 하나가 풍긴다.

뒤엉킨 변경이 나타날 때

뒤엉킨 변경은 단일 책임 원칙(SRP)가 제대로 지켜지지 않을 때 나타난다. 즉, 하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경되는 일이 많을 때 발생한다. 개발 초기에는 맥락 사이의 경계를 명확히 나누기가 어렵고, 소프트웨어 시스템의 기능이 변경되면서 이 경계도 끊임없이 움직이기에 나중에야 이 악취가 느껴질 수도 있다.

해결하기

  • 순차적으로 실행되는게 자연스러운 맥락이라면 (db에서 데이터 꺼내서 처리하기), 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 식으로 단계를 분리한다(단계 쪼개기).
  • 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높다면, 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모은다(함수 옮기기)
  • 여려 맥락의 일에 관여하는 함수가 있다면 옮기기 전에 함수 추출하기부터 수행한다. 모듈이 클래스라면 클래스 추출하기.

3.8 산탄총 수술

산탄총 수술과 뒤엉킨 변경

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

/뒤엉킨 변경산탄총 수술
원인맥락을 잘 구분하지 못함맥락을 잘 구분하지 못함
해결(원리)맥락을 명확히 구분맥락을 명확히 구분
발생 과정(현상)한 코드에 섞여 들어감여러 코드에 흩뿌려짐
해법(실제 행동)맥락별로 분리맥락별로 모음

원인

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

해결법

  • 함께 변경되는 대상들을 함수 옮기기필드 옮기기로 모두 한 모듈에 묶어두면 좋다.
  • 비슷한 데이터를 다루는 함수가 많다면 여러 함수를 클래스로 묶기를 적용한다.
  • 데이터 구조를 변환하거나 보강하는 함수들에는 여러 함수를 변환 함수로 묶기를 적용한다.
  • 이렇게 묶은 함수들의 출력 결과를 묶어서 다음 단계의 로직으로 전달할 수 있다면 단계 쪼개기를 적용한다.
  • 어설프게 분리한 로직을 함수 인라인하기클래스 인라인하기 같은 인라인 팩터링으로 하나로 합치는 것도 좋은 방법이다.

코드가 비대해지더라도 나중에 추출하기 리팩터링으로 더 좋은 형태로 분리할 수 있다. 작은 함수와 클래스에 집착해야하지만, 코드를 재구성하는 중간 과정에서는 큰 덩어리로 뭉쳐져도 괜찮다.

코멘트

뒤에 기술들을 좀 봐야지 이해가 될듯~

3.9 기능 편애

프로그램을 모듈화하면 코드를 여러 영역으로 나누고, 영역 내의 상호작용은 최대한 늘리고, 영역 사이의 상호작용은 최소로 줄이는 데 주력한다.

기능 편애란?

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

해결법

  • 함수가 데이터와 가까이 있고 싶어 한다면 데이터 근처로 옮기자(함수 옮기기).
  • 함수의 일부에서만 기능을 편애한다면 그 부분만 독립 함수로 빼내(함수 추출하기) 원하는 모듈로 보내준다(함수 옮기기).

어디로 옮겨야 할지 모를 때

함수가 사양하는 모듈이 다양하다면 가장 많은 데이터를 포함한 모듈로 옮기자.
함수 추출하기로 함수를 여러 조각으로 나눠 각각을 적합한 모듈로 옮겨도 된다.

복잡한 패턴의 경우

전략 패턴(Strategy pattern)방문자 패턴(Visitor Pattern), 자기 위임(Self-Delegation)도 여기 속한다.
이들은 모두 뒤엉킨 변경 냄새를 없앨 때 활용하는 패턴들로, 가장 기본이 되는 원칙은 '함께 변경될 대상을 한데 모으는 것'이다.
데이터와 이를 활용하는 동작은 함께 변경해야 할 때가 많지만, 예외가 있다. 그럴 때는 같은 데이터를 다루는 코드를 한 곳에서 변경할 수 있도록 옮긴다.

코멘트

전략 패턴 https://victorydntmd.tistory.com/292
방문자 패턴 https://dailyheumsi.tistory.com/216
자기 위임은 없네...

3.10 데이터 뭉치

데이터 항목들은 어울리는걸 좋아한다. 물려다니는 데이터 뭉치는 따로 묶어줘야한다.

방법

  • 가장 먼저 필드 형태의 데이터 뭉치를 찾아서 클래스 추출하기로 하나의 객체로 묶는다.
  • 이후 메서드 시그니처 (이게 뭔데) 있는 데이터 뭉치를 매개 변수 객체 만들기객체 통째로 넘기기를 적용해서 매개변수 수를 줄여본다.

클래스 권장

방금 설명에서 간단한 레코드 구조가 아닌 클래스로 만들기를 권고 했다. 새로운 클래스를 만들고 그 클래스로 옮기기 좋은 동작을 살펴보자. 이렇게 데이터 뭉치를 생산성에 기여하는 정식 멤버로 만들자.

3.11 기본형 집착

커스텀 타입

대부분의 프로그래밍 언어는 기본형을 제공한다. 라이브러리를 통해 날짜나 환율 같은 간단한 객체를 추가로 제공하기도 한다. 한편 프로그래머 중에는 자신에게 주어진 문제에 딱 맞는 기초 타입(화폐, 좌표, 구간 등)을 직접 정의하기를 꺼려하는 사람이 많다. 이 냄새는 문자열을 다루는 코드에서 특히 흔하다.

해결법

  • 기본형을 객체로 바꾸기를 적용하면 기본형만이 사용되는 코드 덩어리를 의미 있는 자료형들로 이루어지게 탈바꿈 시킬 수 있다.
  • 기본형으로 표현된 코드가 조건부 동작을 제어하는 타입 코드(type code)로 쓰였다면 타입 코드를 서브클래스로 바꾸기조건부 로직을 다형성으로 바꾸기를 차례로 적용한다.
  • 자주 함께 몰려다니는 기본형 그룹도 데이터 뭉치이다. 클래스 추출하기매개변수 객체 만들기를 이용해 고쳐주자.

코멘트

기본형을 객체로 만들기. 정말 중요할듯.

3.12 반복되는 switch문

모든 조건부 로직 제거?

몇 사람들은 코드의 switch문 및 모든 조건부 로직(if문)을 조건부 로직을 다형성으로 바꾸기로 없애야 한다고 한다. 그러나 그럴 필요는 없다. 지금은 똑같은 조건부 로직(switch/case문이나 길게 나열된 if/else문)이 여려 곳에서 반복해 등장하는 코드를 고치자.

중복된 조건부 로직의 문제

중복된 switch문이 문제가 되는 이유는 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아서 함께 수정해야하기 때문이다. 이럴 때 다형성은 반복된 switch문을 하나로 만들어 관리가 편하게 해준다.

3.13 반복문

과거에는 반복문을 제거하려 해도 대안이 없었다. 그러나 지금은 일급 함수(first-class function)를 지원하는 언어가 많아졌기 때문에 반복문을 파이프라인으로 바꾸기를 적용해서 시대에 맞지 않는 반복문을 제거할 수 있다. 필터(filter)맵(map)같은 파이프라인 연산을 사용하면 코드에서 각 원소들이 어떻게 처리되는지 쉽게 파악 가능하다.

코멘트

파이프라인 패턴이 pipes and filters 아키텍쳐 패턴인가? https://johngrib.github.io/wiki/pattern/pipeline/

3.14 성의 없는 요소

우리는 코드의 구조를 잡을 때 프로그램 요소(프로그래밍 언어가 제공하는 함수, 클래스, 인터페이스 등 코드 구조를 잡는데 활용되는 요소)를 이용하는 걸 좋아한다. 그러나 그 구조가 리팩터링으로 역활이 줄거나, 사정으로 필요 없어지면 단호하게 쳐내야한다.

해결법

  • 함수 인라인하기클래스 인라인하기로 처리한다.
  • 상속을 사용했다면 계층 합치기를 적용한다.

코멘트

필요 없는 코드를 쳐내라는 거죠? 프로그램 요소의 설명이 좀 이상한 것 같은데 언어가 제공하는게 아니라 기본 코드를 이야기 해야하는거 아닐까요?

3.15 추측성 일반화

이 냄새는 모든 종류의 후킹(hooking) 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다. 그 결과는 물론 이해하기나 관리하기 어려워진 코드이다. 미래를 대비하는 것은 좋지만 실제 사용하지 않는다면 지우자.

해결법

  • 하는 일이 거의 없는 추상 클래스는 계층 합치기로 제거한다.
  • 쓸데없이 위임하는 코드는 함수 인라인하기클래스 인라인하기로 삭제한다.
  • 본문에서 사용되지 않는 매개 변수는 함수 선언 바꾸기로 없앤다. (나중에 필요할꺼라도 추가했지만 안쓰는 매개 변수도 쳐낸다.)
  • 테스트 코드 말고는 사용한 곳이 없는 함수나 클래스에서도 흔하다. 테스트 케이스부터 삭제한 뒤에 죽은 코드 제거하기로 날리자.

3.16 임시 필드

임시 필드 나쁘다

특정 상황에서만 값이 설정되는 필드를 가진 클래스도 있다. 하지만 객체를 가져올 때는 모든 값이 채워져 있을거라 기대하기 때문에 임시 필드를 가지게 작성하면 이해하기 어렵다.

해결법

  • 임시 필드를 발견하면 클래스 추출하기로 분리해주자.
  • 그런 다음 함수 옮기기로 임시 필드들과 관련된 코드를 새 클래스로 몰라 넣자.
  • 또한 임시 필드가 유효한지 확인한 후 동작하는 조건부 로직은 특이 케이스 추가하기로 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거할 수 있다.

코멘트

서브 클래스를 만들어서 2가지 조건부 동작을 다형성으로 처리하라는 거겠지

3.17 메시지 체인

메시지 체인이란

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

해결법

위임 숨기기로 해결한다. 이 리팩터링은 메시지 체인의 다양한 연결점에 모두 적용할 수 있지만 중간 객체들이 모두 중개자가 돼버리기 쉽다.
그러니 최종 결과 객체의 쓰임을 살펴보고 함수 추출하기로 결과 객체를 사용하는 코드 일부를 빼네 함수 옮기기로 체인을 숨길 수 있는지 살펴보자.

코멘트

객체 내비게이션 구조는 찾아도 안나온다.

managerName = aPerson.department.manager.name;
// 부서 객체와 관리자 객체 모두의 존재를 숨김
managerName = aPerson.managerName;
// 이 매니저 name 결과 객체를 사용하는 코드를 함수로 옮겨 체인을 숨기자 

3.18 중개자

캡슐화

객체의 대표적인 기능 하나로, 외부로부터 세부사항을 숨겨주는 캡슐화(encapsulation)가 있다. 캡슐화하는 과정에서는 위임(delegation)이 자주 활용된다. 하지만 지나치면 문제가 된다. 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하면 문제가 된다.

해결법

  • 이럴 때는 중개자 제거하기를 활용해 실제로 일하는 객체와 직접 소통하게 하자.
  • 위임 메서드를 제거한 후 남는 일이 거의 없다면 호출하는 쪽으로 인라인화 하자(함수 인라인하기)

3.19 내부자 거래

모듈 사이의 거래

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

해결법

  • 은밀히 데이터를 주고 받는 모듈들이 있다면 함수 옮기기필드 옮기기 기법으로 떼어 놓아서 사적으로 처리하는 부분을 줄인다.
  • 여러 모듈이 같은 관심사를 공유한다면 공통 부분을 정식으로 처리하는 제 3의 모듈을 새로 만들거나 위임 숨기기를 이용하여 다른 모듈이 중간자 역활을 하게 만들자.
  • 상속 구조에서는 부모 자식 사이에 결탁이 생길 떄가 있다. 자식 클래스가 일정 이상으로 부모 클래스에 결탁한다면 서브 클래스를 위임으로 바꾸기슈퍼 클래스를 위임으로 바꾸기를 활용해 떠나보내 주자.

3.20 거대한 클래스

거대한 클래스의 발생

한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어난다. 그리고 클래스에 필드가 너무 많으면 중복 코드가 생기기 쉽다.

해결법

  • 클래스 추출하기로 필드들 일부를 따로 묶는다. 같은 컴포넌트에 모아두는 것이 합당해 보이는 필드들을 선택하면 된다. 더 일반적으로는, 한 클래스 안에서 접두어나 접미어가 같은 필드들이 함께 추출할 후보들이다.
  • 분리할 컴포넌트를 원래 클래스와 상속 관계로 만드는 게 좋다면 (클래스 추출하기 보다는) 슈퍼 클래스 추출하기나 (실질적으로 서브 클래스 추출하기에 해당하는) 타입 코드를 서브클래스로 바꾸기를 적용하는게 쉬울 것이다.
  • 코드량이 너무 많은 클래스의 중복 코드는 작은 메서드로 뽑아내 해결하자.
  • 클라이언트의 거대 클래스 이용 패턴을 파악해 쪼갤 단서를 얻을 수 있다. 특정 기능 그룹만을 자주 사용한다면 그것들을 개별 클래스로 추출하자. 클래스 추출하기, 슈퍼 클래스 추출하기, 타입 코드를 서브클래스로 바꾸기 등을 활용해서 여러 클래스로 분리한다.

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

클래스의 교체

클래스를 사용할 때의 큰 장점은 필요에 따라 언제든 다른 클래스로 교체 가능하다는 것이다. 단 교체하려면 인터페이스가 같아야 한다. 따라서 함수 선언 바꾸기로 메서드 시그니처를 일치시킨다. 이로 부족하면 함수 옮기기를 이용하여 인터페이스가 같아질 때까지 필요한 동작을 클래스 안으로 밀어 넣는다. 그러다 대안 클래스들 사이에 중복 코드가 생기면 슈퍼클래스 추출하기를 적용할지 고민해본다.

코멘트

어댑터 패턴이 생각나는 문단

3.22 데이터 클래스

데이터 클래스란

데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말한다. 그저 데이터 저장 용도로만 쓰이다 보니 다른 클래스가 너무 깊이까지 함부로 다룰 때가 많다.

해결법

  • 이런 클래스에 public 필드가 있다면 얼른 레코드 캡슐화하기로 숨기자. 변경하면 안되는 필드는 세터 제거하기로 접근을 원천 봉쇄한다.
  • 다른 클래스에서 데이터 클래스의 게터나 세터를 사용하는 메서드를 찾아서 함수 옮기기로 그 메서드를 데이터 클래스로 옮길 수 있을지 살펴보자. 메서드를 통째로 옮기기 힘들다면 함수 추출하기를 이용해서 옮길 수 있는 부분만 별로 메서드로 뽑아낸다.

예외

다른 함수를 호출해 얻은 결과 레코드 (데이터 객체)로는 동작 코드를 넣을 이유가 없다. 대표적으로 단계 쪼개기의 결과로 나온 중간 데이터 구조가 있다. 이런 데이터 구조는 불면이다. 불변 필드는 굳이 캡슐화 필요가 없으며, 게터 없이 필드 자체를 공개해도 된다.

3.23 상속 포기

서브클래스

서브클래스는 부모로부터 메서드와 데이터를 물려 받지만 그 중에 필요 없는 것이 있다면 어떡해야할까?

계층 구조에서의 해결법

예전에는 계층 구조를 잘못 설계 했다고 봤다. 이 관점에서의 해결 방법은 메서드 내리기필드 내리기를 활용해 물려받지 않을 부모 코드를 모조리 새로 만든 서브 클래스로 넘긴다. 그러면 부모에는 공통된 부분만 남는다. 항상 이럴 필요가 없다.

상속 매커니즘에서의 해결법

서브 클래스가 부모의 동작은 필요로하지만 인터페이스는 따르고 싶지 않아 할때는 서브 클래스를 위임으로 바꾸기슈퍼 클래스를 위임으로 바꾸기를 활용해 상속 메커니즘에서 아예 벗어나 보자.

3.24 주석

주석

주석을 달면 안된다고 말하는 것은 아니다. 그러나 주석이 많다면 이 장에서 소개한 온갖 악취를 풍기는 코드가 나오기 쉽다.

주석 없애기

  • 특정 코드 블록이 하는 일에 주석을 남기고 싶다면 함수 추출하기를 적용하자.
  • 이미 추출된 함수임에도 설명히 필요하다면 함수 선언 바꾸기로 이름을 바꾸자.
  • 시스템이 동작하기 위한 선행 조건을 명시하고 싶다면 어서션 추가하기를 쓰자.

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

주석이 필요할 때

뭘 할지 모를때라면 주석을 달아두면 좋다. 현재 진행 상황뿐만 아니라 확실하지 안는 부분에 주석에 남긴다. 코드를 이렇게 쓴 이유를 설명하는 용도로 달 수도 있다.

코멘트

어느 상황에서 어느 리팩터링을 적용해야할지에 대한 문단이다. 무척 중요하다. 뒤의 각각의 기법에 대해 읽어보고 다시 한번 훝어보면 좋을 것 같다.

profile
개발자 지망생입니다.

0개의 댓글