Skip to main content

3장 코드에서 나는 악취 上

written by
Mengkki
Mengkki 🏆Front End Engineer

리팩토링을 언제 해야한다고 생각하나? 코드에서 냄새가 날 때다...!

숙련된 개발자의 감만큼 정확한 기준은 없다. 시켜줘...빡숙 개발자

인스턴스 변수는 몇 개가 적당한지, 메서드는 몇줄을 넘어가면 안 좋은지 등은 각자 경험을 통해 감을 키워야 한다.

우선 이번 챕터를 통해 코드가 풍기는 냄새가 무엇인지 찾고, 해법으로 제시한 리팩터링 기법을 찾아 냄새를 없애는 데 도움이 될 지 생각해보도록 하자

3.1 기이한 이름#

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

하지만 이름 짓기는 프로그래밍에서 가장 어려운 두가지 중 하나다. 그 때문에 우리가 가장 많이 사용하는 리팩터링도 함수 선언 바꾸기, 변수 이름 바꾸기, 필드 이름 바꾸기 처럼 이름을 바꾸는 리팩터링들이다.

이름 바꾸기는 단순히 이름을 다르게 표현하는 연습이 아니다. 마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높다. 그래서 혼란스러운 이름을 잘 정리하면 코드가 훨씬 간결해질 때가 많다.

3.2 중복 코드#

똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있다. 코드가 중복되면 각각을 볼때마다 서로 차이점은 없는지 주의 깊게 살펴봐야 하는 부담이 생긴다.

한 클래스에서 두 메서드가 똑같은 표현식을 사용하는 경우가 있다. 이럴 때는 함수 추출하기 를 써서 양쪽 모두 추출한 메서드를 호출하게 바꾸면 된다.

코드가 비슷한데 완전히 똑같지는 않다면, 먼저 문장 슬라이드하기 로 비슷한 부분을 한 곳에 모아 함수 추출하기를 더 쉽게 적용할 수 있는지 살펴본다. 같은 부모로부터 파생된 서브클래스들에 코드가 중복되어 있다면, 각자 따로 호출되지 않도록 메서드 올리기를 적용해 부모로 옮긴다.

3.3 긴 함수#

선생님들의 오랜 경험에 비추어보았을 때, 오랜 기간 잘 활용되는 프로그램들은 하나같이 짧은 함수들로 구성이 되어 있다.

Q. 코드 읽는 사람 입장에서는 짧은 함수 여러개로 이루어져 있으면 읽을때 왔다갔다 해야해서 부담스럽지 않나요?

A. 함수 이름을 잘 지으면 함수의 본문을 읽을 필요가 없어요 ^^

함수 이름을 잘 짓기 위해서는 훨씬 적극적으로 함수를 쪼개야 한다.

주석을 달아야 할 만한 부분은 무조건 함수로 만든다. 함수의 이름은 코드의 목적이 잘 드러나게 짓고, 본문에는 주석으로 설명하려는 부분을 담자

💡 '무엇을 하는지'를 코드가 잘 설명해주지 못할수록 함수로 만드는 게 유리하다.

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

그 덩어리는 어케 찾나요? 일단 이친구들을 의심해보세요!

  • 주석 한줄의 코드더라도 함수로 추출하는 것이 낫다. 함수 이름은 주석 내용 토대로 짓기!
  • 조건문 조건문 분쇄하기나, 거대한 switch문은 case마다 함수 추출하기, 같은 조건을 기준으로 나뉘는 switch문이 여러개라면 조건문을 다형성으로 바꾸기
  • 반복문 반복문 쪼개기

3.4 긴 매개변수 목록#

함수가 배개변수로 받는 것들이 많아진다면 그 자체로 이해하기 어려워질 수 있다. 줄여봅시다~

매개변수를 질의 함수로 바꾸기#

다른 매개변수에서 값을 얻어올 수 있는 매개변수가 있는 경우

객체 통째로 넘기기#

어느 데이터 구조에서 값들을 뽑아 각각을 별개의 매개변수로 쓰고 있는 경우

매개변수 객체 만들기#

항상 함께 전달되는 매개변수가 있는 경우

플래그 인수 제거하기#

함수의 동작 방식을 정하는 플래그 역할의 매개변수가 있는 경우

여러 함수를 클래스로 묶기#

여러 함수가 특정 매개변수들의 값을 공통으로 사용하는 경우

3.5 전역 데이터#

이녀석이 뿜는 악취는 우리가 겪을 수 있는 악취 중 가장 지독한 편에 속한다고 한다....

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

전역 데이터의 대표적인 형태는 전역 변수지만 클래스 변수와 싱클톤에서도 같은 문제가 발생할 수 있다.

이를 방지하기 위해 우리가 사용하는 가장 대표적인 리팩터링은 변수 캡슐화하기 다. 다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 이 기법을 가장 먼저 적용한다.

이런 데이터는 함수로 감싸는 것 만으로도 데이터를 수정하는 부분을 쉽게 찾을 수 있고 접근을 통제할 수 있게 된다.

3.6 가변 데이터#

mutable data 라는 영제에서 유추해낼 수 있듯이...유남생? 이녀석도 냄새가 고약하다.

가변 데이터의 유효범위가 단 몇 줄이라면 가변 데이터라 해도 문제를 일으킬 일이 별로 없다. 하지만 나중에 유효범위가 넓어질 수 있고, 그러면 위험도 덩달아 커진다.

하지만... 무분별한 데이터 수정에 따른 위험을 줄이는 방법은 얼마든지 있다!

변수 캡슐화하기#

정해놓은 함수를 거쳐야만 값을 수정할 수 있도록 한다. 값이 어떻게 수정되는지 감시하거나 코드를 개선하기 쉽다.

변수 쪼개기#

하나의 변수에 용도가 다른 값들을 저장하느라 값을 갱신하는 경우라면 이것을! 용도별로 독립 변수에 저장하게 하여 값 갱신이 문제를 일으킬 여지를 없앤다

문장 슬라이드 하기, 함수 추출하기#

갱신 로직은 다른 코드와 떨어뜨려놓는 것이 좋다. 무언가를 갱신하는 코드로부터 부작용이 없는 코드를 분리한다

질의 함수와 변경 함수 분리하기#

API를 만들때는 이것을 활용해 꼭 필요한 경우가 아니라면 부작용이 있는 코드를 호출할 수 없게 한다.

세터 제거하기#

간혹 세터를 호출하는 클라이언트를 찾는 것만으로도 변수의 유효범위를 줄이는 데 도움될 때가 있다.

여러 함수를 클래스로 묶기, 여러 함수를 변환 함수로 묶기#

변수를 갱신하는 코드들의 유효범위를 클래스 내부로 제한한다.

3.7 뒤엉킨 변경#

우리는 코드를 수정할 때는 시스템에서 고쳐야 할 딱 한군데를 찾아서 그부분만 수정할 수 있기를 바란다.

만약 그렇게 할 수 없다면... 냄새가 나는거다. 뒤엉킨 변경과 산탄총 수술 의 냄새가...

뒤엉킨 변경은 단일 책임 원칙(SRP)이 제대로 지켜지지 않을 때 나타난다. 예를들면

DB가 추가될 때마다 함수 3개를 바꿔야 하고, 금융상품이 추가될때마다 또다른 함수 4개를 바꿔야 하는 모듈이 있다면 뒤엉킨 변경이 발생했다는 뜻이다!

DB와 금융 상품은 다른 맥락에서 이뤄지므로 다른 모듈로 분리해야 편하다.

개발 초기에는 맥락 간의 경계를 나누기 어렵고 기능이 변경되면서 경계도 끊임없이 움직이기 때문에 나중에서야 이 악취를 느끼는 경우도 많다.

단계 쪼개기#

DB에서 데이터를 가져와서 금융 로직을 처리해야 하는 일처럼 순차적으로 실행되는 게 자연스러운 맥락이라면, 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 식으로 단계를 분리하자

함수 옮기기#

전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높다면, 각 맥락에 해당하는 적당한 모듈을 만들어서 관련 함수들을 모은다. 그러면 처리 과정이 맥락별로 구분된다.

함수 추출하기#

여러 맥락의 일에 관여하는 함수가 있다면 함수 옮기기를 하기 전에 함수 추출하기부터 수행한다.

클래스 추출하기#

모듈이 클래스라면 클래스 추출하기가 맥락별 분리 방법을 잘 안내해줄 거래요...

3.8 산탄총 수술#

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

이 냄새는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍긴다.

변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽다.

함수 옮기기, 필드 옮기기#

함께 변경되는 대상들을 모두 한 모듈에 묶어둔다

여러 함수를 클래스로 묶기#

비슷한 데이터를 다루는 함수가 많다면 이걸 적용하자

여러 함수를 변환 함수로 묶기#

데이터 구조를 변환하거나 보강하는 함수들에는 이걸 적용한다

단계 쪼개기#

이렇게 묶은 함수들의 출력 결과를 묶어서 다음 단계의 로직으로 전달할 수 있다면 이걸 적용한다

3.9 기능 편애#

프로그램을 모듈화할 때는 코드를 여러 영역으로 나눈 뒤 한 영역 내에서 이뤄지는 상호작용은 최대로, 영역 간 상호작용은 최소로 줄이는 데 주력한다.

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

함수 옮기기#

우리 함수가 저 모듈과 더 친하게 지내하고 싶어하는 것 같으니 그쪽으로 보내주자... 그 함수의 일부에서만 기능을 편애한다면 그부분만 독립 함수로 떼어낸 다음에 보내주자

하지만 이런 기능 편애를 부추기는 디자인 패턴들도 있다

디자인 패턴 중 전략 패턴 , 방문자 패턴, 자기 위임 등도 뒤엉킨 변경 냄새를 없앨 때 활용하는 패턴인데, 이들의 기본이 되는 원칙은 함께 변경할 대상을 한데 모으는 것이다.

이런 패턴들을 적용하면 오버라이드 해야 할 소량의 동작 코드를 각각의 클래스로 격리해주므로 수정하기 쉬워지지만 간접 호출이 늘어난다

3.10 데이터 뭉치#

데이터 항목 서너 개가 여러 곳에서 항상 뭉쳐 다니는 모습을 볼 수 있다. 이렇게 몰려다니는 데이터 뭉치는 보금자리를 따로 마련해줘야 한다.

데이터 뭉치인지 판별하려면 값 하나를 삭제했을 때 나머지 데이터만으로는 의미가 없다면 객체로 환생하길 갈망하는 데이터 뭉치라는 뜻이다!

클래스 추출하기#

우선 필드 형태의 데이터 뭉치를 찾아서 클래스 추출하기로 하나의 객체로 묶는다.

매개변수 객체 만들기, 객체 통째로 넘기기#

메서드 시그니처에 있는 데이터 뭉치는 매게변수는 이 방법들을 적용해서 매개변수 수를 줄인다. 메서드 호출 코드가 간결해질 것이다.

기능 편애를 없애는 과정에서 새로운 클래스를 만들었다면, 이어서 그 클래스로 옮기면 좋을 동작은 없는지 살펴보자.

이런 연계 과정은 상당한 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시키는 결과를 낳는다.

3.11 기본형 집착#

금액이나 전화번호같은 데이터를 단순히 number, string으로 표현하지 말자. 냄새난다!

기본형을 객체로 바꾸기를 적용해서 우가우가 기본형 자료형을 최신코드로 바꿀수있다.

기본형으로 표현된 코드가 조건부 동작을 제어하는 타입 코드로 쓰였다면 타입 코드를 서브클래스로 바꾸기조건부 로직을 다형성으로 바꾸기 를 차례대로 적용하자.

자주 몰려다니는 기본형 그룹도 데이터 뭉치다. 따라서 클래스 추출하기매개변수 객체 만들기를 이용해서 문명으로 이끌어줘야 한다

3.12 반복되는 switch문#

중복된 switch문이 문제가 되는 이유는 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아서 수정해주어야 하기 때문이다.

이럴 때 조건부 로직을 다형성으로 바꾸기는 반복된 switch문이 내뿜는 사악한 기운을 제압하여 코드베이스를 최신 스타일로 바꿔주는 세련된 무기인 셈이다.