Home [리팩터링] chapter 3 코드에서 나는 악취
Post
Cancel

[리팩터링] chapter 3 코드에서 나는 악취

p.114 이름 바꾸기는 단순히 이름을 다르게 표현하는 연습이 아니다. 마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높다.

p.115 가장 간단한 코드 중복의 예로, 한 클래스에 딸린 두 메서드가 똑같은 표현식을 사용하는 경우가 있다. 이럴 때는 함수 추출하기를 써서 양쪽 모두 추출된 메서드를 호출하게 바꾸면 된다. 코드가 비슷하긴한데 완전히 똑같지는 않다면, 먼저 문장 슬라이드하기로 비슷한 부분은 한 곳에 모아 함수 추출하기를 더 쉽게 적용할 수 있는지 사려본다. 같은 부모로부터 파생된 서브 클래스들에 코드가 중복되어 있다면, 각자 따로 호출되지 않도록 메서드 올리기를 적용해 부모로 옮긴다.

p.115 짧은 함수로 구성된 코드를 이해하기 쉽게 만드는 가장 확실한 방법은 좋은 이름이다. 함수 이름을 잘 지어두면 본문코드를 볼 이유가 사라진다. 그러기 위해서는 훨씬 적극적으로 함수를 쪼개야한다. 우리는 주석을 달아야 할 만한 부분은 무조건 함수로 만든다. 그 함수 본문에는 원래 주석으로 설명하려던 코드가 담기고, 함수의 이름은 동작 방식이 아닌 의도가 드러나게 짓는다. …

p.116 함수가 매개변수와 임시 변수를 많이 사용한다면 추출작업에 방해가 된다. 이런 상황에서 함수를 추출하다보면 추출된 함수에도 매개변수가 너무 많아져서 리팩터링 전보다 난해해 질 수 있다. 그렇다면 임시 변수를 질의함수로 바꾸기로 임시변수의 수를, 매개변수 객체 만들기와 객체 통째로 넘기기로는 매개변수의 수를 줄일 수 있을 것이다.( 심화 학습 필요)… 여전히 임시변수와 매개변수가 너무 많다면 함수를 명령으로 바꾸기를 고려해보자.

주석은 코드만으로는 목적을 이해하기 어려운 부분에 달려있는 경우가 많다. 이런 주석을 찾으면 주석이 설명하는 코드와 함께 함수로 빼내고, 함수 이름은 주석 내용을 토대로 짓는다. 코드가 단 한줄이어도 따로 설명할 필요가 있다면 함수로 추출하는 것이 좋다.

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

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

p.117 다른 매개변수에서 값을 얻어올 수 있는 매개변수가 있을 수 있는데, 이런 매개변수는 매개변수를 질의 함수로 바꾸기로 제거할 수 있다. 사용중인 데이터 구조에서 값을 뽑아 각각을 별개의 매개변수로 전달하는 코드라면 객체 통째로 넘기기를 적용해서 원본 데이터구조를 그대로 전달한다. 항상 함께 전달되는 매개변수들은 매개변수 객체 만들기로 하나로 묶어버린다. 함수의 동작방식을 정하는 플래그 역할의 매개변수는 플래그 인수 제거하기로 없애준다. 클래스는 매개변수 목록을 줄이는데 효과적인 수단이기도 하다. 특히 여러개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 유용하다. 이럴 때는 여러 함수를 클래스로 묶기를 이용하여 공통값들을 클래스의 필드로 정의한다.

p.117 전역데이터는 코드베이스 어디에서든 건드릴 수 있고 값을 누가 바꿨는지 찾아낼 매커니즘이 없다는 것이 문제다. 그래서 버그는 끊임없이 발생하는데 그 원인이 되는 코드를 찾기 굉장히 어렵다. 클래스 변수와 싱글톤에서도 같은 문제가 발생한다. 이를 방지하기 위한 대표적 리팩터링 방법은 변수 캡슐화하기다. 다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 이 기법을 가장 먼저 적용한다. 이런 데이터를 함수로 감싸는 것만으로도 데이터를 수정하는 부분을 쉽게 찾을 수 있고 접근을 통제할 수 있게 된다. 더 나아가 접근자 함수들을 클래스나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위로 최소로 줄이는 것도 좋다.

p.118 데이터를 수정해버리면 프로그램이 오작동하는 경우가 있다. 따라서 함수형 프로그래밍에서는 데이터는 절대 변하지 않고, 데이터를 변경하려면 반드시 원래 데이터는 그대로 둔 채 변경하려는 값에 해당하는 복사본을 만들어서 반환한다

무분별한 데이터 수정에 따른 위험을 줄이는 방법은 얼마든지 있다. 변수 캡슐화하기를 적용하여 정해놓은 함수를 거쳐야만 값을 수정할 수 있도록 하면 값이 어떻게 수정되는지 감시하거나 코드를 개선하기 쉽다. 하나의 변수에 용도가 다른 값들을 저장하느라 값을 갱신하는 경우라면 변수 쪼개기를 이용하여 용도별로 독립 변수에 저장하게 하여 값 갱신이 문제를 일으킬 여지를 없앤다. 갱신로직은 다른 코드와 떨어뜨려 놓는 것이 좋다.

API를 만들 때는 질의 함수와 변경함수 분리하기를 활용해서 꼭 필요한 경우가 아니라면 부작용이 있는 코드를 호출할 수 없게한다. 가능한 한 세터 제거하기도 적용한다. 간혹 세터를 호출하는 클라이언트를 찾는 것만으로도 변수의 유효범위를 줄이는 데 도움될 때가 있다.

값을 다른 곳에서 설정 할 수 있는 가변 데이터는 최악이고 쓸데없는 코드이기도 하다. 이럴 때는 파생변수를 질의함수로 바꾼다.

변수의 유효범위가 단 몇줄이면 가변 데이터라 해도 문제를 일으킬 일이 별로 없다. 하지만 나중에 유효범위가 넓어질 수 있고, 그러면 위험도가 커진다. 따라서 여러 함수를 클래스로 묶기나 여러함수를 변환함수로 묶기를 활용해서 변수를 갱신하는 코드들의 유효범위를 클래스나 변환으로 제한한다. 구조체처럼 내부 필드에 데이터를 담고있는 변수라면 일반적으로 참조를 값으로 바꾸기를 적용하여, 내부필드를 직접 수정하지 말고 구조체를 통째로 교체하는 편이 낫다.

p.119 뒤엉킨 변경은 단일책임원칙SRP이 제대로 지켜지지 않았을 때 나타난다. 즉, 하나의 모듈이 서로 다른 이유들로 인해 여러가지 방식으로 변경되는 일이 많을 때 발생한다. 예컨데 지원해야하 데이터베이스가 추가될 때마다 함수 세개를 바꿔야 하고, 금융 상품이 추가될 때마다 또다른 함수 네개를 바꿔야하는 모듈이 있다면 뒤엉킨 변경이 발생한 것이다. 데이터베이스연동과 금융 상품 처리는 서로 다른 맥락에서 이뤄지므로 독립된 모듈로 분리해야 프로그래밍이 편하다. 데이터베이스에서 데이터를 가져와 금융상품로직에서 처리해야하는 일처럼 순차적으로 실행되는게 자연스러운맥락이라면, 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 방식으로 단계를 분리한다. (단계 쪼개기) 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높다면 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모은다. (함수 옮기기) 그러면 처리과정이 맥락별로 구분된다. 이때 여러 맥락의 일에 관여하는 함수가 있다면 옮기기 전에 함수 추출하기 부터 수행한다.

p.120 산탄총 수술은 코드를 변경할 떄마다 자잘하게 수정해야하는 클래스가 많을때. 이럴 때는 함께 변경되는 대상들을 함수 옮기기와 필드 옮기기로 모두 한 모듈에 묶어두면 좋다. 비슷한 데이터를 다루는 함수가 많다면 여러 함수를 한 클래스로 묶기를 적용한다. 데이터 구조를 변환하거나 보강하는 함수들에는 여러 함수를 변환함수로 묶기를 적용한다. 이렇게 묶은 함수들의 출력결과를 묶어서 다음 단계의 로직으로 전달 할 수 있다면 단위 쪼개기를 적용한다. 어설프게 분리된 로직을 함수 인라인하기나 클래스 인라인하기 같은 인라인 리팩터링으로 하나로 합치는 것도 좋은 방법이다. 메서드나 클래스가 비대하지만, 나중에 추출하기 리팩터링으로 더 좋은 형태로 리팩터링 할 수 있다.

p.121 기능편애는 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용 할 일이 더 많을 때 발생한다. 이럴때는 이 함수가 데이터와 가까이 있도록 옮겨주면 된다. 때로는 함수의 일부에서만 기능을 편애할 수 있다. 이럴때는 그 부분만 독립함수로 빼낸 다음, 원하는 모듈로 보내준다. 함수가 사용하는 모듈이 다양하다면 가장 많은 데이터를 포함한 모듈로 옮긴다. 함수 추출하기로 함수를 여러조각으로 나눈 후 각각을 적합한 모듈로 옮기면 더 쉽게 해결되는 경우도 많다. 이를 거스르는 복잡한 패턴도 있다. ‘디자인 패턴’ 중 전략 패턴과 방문자 패턴이 있다. 켄트 백의 자기 위임도 여기에 속한다. ( 심화 공부 필요)

p.122 데이터 항목들이 여러 곳에서 뭉쳐다니고 클래스 두어개의 필드에서, 혹은 여러 메서드의 시그니처에서 발견될 때. 필드 형태의 데이터 뭉치는 클래스 추출하기로 하나의 객체로 묶는다. 메서드 시그니처의 데이터 뭉치는 매개변수 객체 만들기나 객체 통째로 넘기기를 적용해서 매개변수 수를 줄인다. 데이터 뭉치가 앞에서 새로 만든 객체의 필드 중 일부만 사용하더라도 걱정할 필요 없다. 새 객체로 뽑아낸 필드가 두개 이상이기만 해도 확실히 예전보다 나아진다. 데이터 뭉치인지 판별하려면 값 하나를 삭제해본다. 그랬을때 나머지 데이터만으로는 의미가 없다면 객체로 환생시킬 데이터 뭉치이다. 클래스로 만들어보자. 기능편애를 없애는 과정에서 새로운 클래스를 만들었다면 이어서 그 클래스로 옮기면 좋을 동작은 없는지 살펴본다. 이러한 연계 과정은 종종 상당한 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시키는 결과로 이어진다.

p.123 자신에게 주어진 문제에 기본형 (원시타입)을 사용하는 것보다 딱 맞는 기초타입을 직접 정의하는 것이 좋다. 기본형을 객체로 바꾸기를 적용하자. 기본형으로 표현된 코드가 조건부 동작을 제어하는 타입코드로 쓰였다면 타입코드를 서브클래스로 바꾸기와 조건부 로직을 다형성으로 바꾸기를 차례로 적용한다. (심화 학습 필요) 자주 몰려다니는 기본형 그룹도 데이터 뭉치다. 따라서 클래스 추출하기와 매개변수 객체 만들기를 이용한다.

p.124 똑같은 조건부로직(switch/case문이나 길게 나열된 if/else문)이 여러 곳에서 반복해 등장하는 코드의 문제점. 조건절을 하나 추가할 때 마다 다른 switch문도 모두 찾아서 함께 수정해야하기 때문이다. 이럴 때 다형성을 활용한다. (심화 학습 필요)

p.124 일급함수를 지원하는 언어가 많아졌다. 반복문을 파이프라인으로 바꾸기를 적용해 제거할 수 있다. filter 나 map 같은 파이프라인 연산을 사용하면 코드에서 각 원소들이 어떻게 처리되는지 쉽게 파악할 수 있다.

일급함수 일급 함수를 가진 언어에서는 함수를 다른 함수에 인수로 제공하거나, 함수가 함수를 반환할 수 있으며, 변수에도 할당할 수 있습니다.

p.125 본문코드를 그대로 쓰는 것과 진배없는 함수도 있고, 실질적으로 메서드가 하나뿐인 클래스도 있다. 이런 프로그램 요소는 함수 인라인하기, 클래스 인라인하기로 처리한다. 상속을 사용했다면 계층 합치기를 적용한다.

p.125 ‘나중에 필요할거야’ 라는 생각으로 만든 코드는 치워버리기. 하는 일이 없는 추상클래스는 계층 합치기로 제거한다. 쓸데없이 위임하는 코드는 함수 인라인하기나 클래스 인라인하기로 삭제한다. 본문에 사용되지 않는 매개변수는 함수 선언 바꾸기로 없앤다. 추측성 일반화하는 테스트 케이스부터 삭제하고 죽은 코드 제거하기

p.126 특정 상황에만 값이 설정되는 임시 필드는 클래스 추출하기로 수정한다. 그런 다음 함수 옮기기로 인시 필드들과 관련된 코드를 모조리 새 클래스에 몰아 넣는다. 또한, 임시 필드들이 유효한지를 확인한 후 동작하는 조건부 로직이 있을 수 있는데, 특이 케이스 추가하기로 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어 제거할 수 있다.

p.126 메시지 페인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다. 가령 getSomething() 같은 게터가 꼬리에 꼬리를 물고 이어지거나 임시 변수들이 줄줄이 나열되는 코드가 있다. 이는 클라이언트가 객체 내비게이션 구조에 종속됐음을 의미한다. 그래서 내비게이션 중간 단계를 수정하면 클라이언트 코드도 수정해야한다. 이 문제는 위임숨기기로 해결한다. 이 리팩터링은 메시지 체인의 다양한 연결점에 적용할 수 있다. 원칙적으로 체인을 구성하는 모든 객체에 적용할 수 있지만, 그러다 보면 중간 객체들이 모두 중개자가 돼버리기 쉽다. 그러니 최종 결과 객체가 어떻게 쓰이는지 부터 살펴보는 것이 좋다. 함수 추출하기로 결과 객체를 사용하는 코드 일부를 따로 빼낸 다음 함수 옮기기로 체인을 숨길 수 있는지 살펴보다. 체인을 구성하는 객체 중 특정 하나를 사용하는 클라어인트 중 그 이후의 객체들도 사용하길 원하는 클라이언트가 제법 된다면, 이 요구를 처리해줄 메서드를 추가한다. (심화 학습 필요)

p.127 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있다면 중개자 제거하기를 활용하여 실제로 일하는 객체와 직접 소통하게 한다. 위임 메서드를 제거한 후 남는 일이 거의 없다면 호출하는 쪽으로 인라인한다.

p.128 여러 모듈이 같은 관심사를 공유한다면 공통 부분을 정식으로 처리하는 제 3의 모듈을 새로 만들거나 위임 숨기기를 이용하여 다른 모듈이 중간자 역할을 하게 만든다. 상속 구조에서는 부모 자식 사이에 결탁이 생길 때가 있다. 그러면 서브 클래스를 위임으로 바꾸기나 슈퍼클래스를 위임으로 바꾸기를 활용한다.

p.128 한 클래스가 너무 많은 일을 하려다보면 필드 수가 늘어난다. 그리고 클래스에 필드가 너무 많으면 중복코드가 생기기 쉽다. 이럴 때는 클래스 추출하기로 필드들 일부를 따로 묶는다. 같은 컴포넌트에 모아두는 것이 합당해보이는 필드들을 선택하면 된다. 일반적으로는 한 클래스 안에서 접두어나 접미어가 같은 필드들이 함께 추출할 후보들이다. 이렇게 분리할 컴포넌트를 원래 클래스와 상속 관계로 만드는 게 좋다면 클래스를 추출하기보다 슈퍼 클래스 추출하기나 실질적으로 서브 클래스 추출하기에 해당하는 타입코드 서브 클래스로 바꾸기를 적용하는 편이 더 쉬울 것이다.

필드가 너무 많은 클래스와 마찬가지로 코드량이 너무 많은 클래스도 중복코드와 혼동을 일으킬 여지가 크다. 가장 간단한 해법은 그 클래스 안에서 자체적으로 중복을 제거하는 것이다. 가장 간단한 해법은 그 클래스 안에서 자체적으로 중복을 제거하는 것이다.

클라이언트들이 거대 클래스를 이용하는지 패턴을 파악하여 그 클래스를 어떻게 쪼갤지 단서를 얻을 수도 있다. 먼저 클라이언트들이 거대 클래스의 특정 기능 그룹만 주로 사용하는지 살핀다. 이때 각각의 기능 그룹이 개별 클래스로 추출될 후보다. 유용한 기능 그룹을 찾았다면 클래스 추출하기, 슈퍼 클래스 추출하기, 타입코드를 서브클래스로 바꾸기 등을 활용해서 여러 클래스로 분리한다.

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

p.130 데이터 클래스란 데이터 필드와 게터, 세터 메서드로만 구성된 클래스를 말한다. 그저 데이터 저장 용도로만 쓰이다보니 다른 클래스가 너무 깊이까지 함부로 다룰 때가 많다. 이런 클래스에 public 필드가 있다보면 레코드 캡슐화하기로 숨긴다. 변경하면 안되는 필드는 세터 제거하기로 접근을 원천 봉쇄한다. 메서드를 통째로 옮기기 어렵다면 함수 추출하기를 이용해서 옮길 수 있는 부분만 별도 메서드로 뽑아낸다. 데이터 클래스는 필요한 동작이 엉뚱한 곳에 정의되어 있다는 신호 일 수 있다. 이런 경우라면 클라이언트 코드를 데이터 클래스로 옮기기만 해도 대폭 개선된다.

This post is licensed under CC BY 4.0 by the author.

[JS] this 바인딩

[Next JS] 강의 정리

Comments powered by Disqus.