We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
냄새 나면 당장 갈아라 켄트 벡 할머니의 육아 원칙
이번 장에서는 리팩터링의 타이밍을 결정하는 냄새를 살펴본다.
인스턴스 변수를 삭제하거나 상속 계층을 만든느 방법을 설명하기는 쉽다.
하지만 이런 일을 언제 해야하는 지는 명확하게 정립된 규칙이 없다.
결론부터 말하면 이런 결정은 경험과 직관에 의존한다.
즉, 코드 감각을 키워야 하는 영역이다.
따라서 많이 등장하는 코드 악취, 냄새를 예로 보여주며 냄새가 나는 부분을 파악하는 감을 키워보자.
이후 6~12장의 카탈로그로 악취를 없애는 과정을 준비하자
CleanCode에선 냄새의 연장선으로 좀 더 확장된 개념을 다룬 듯 하여 링크를 같이 첨부한다.
CleanCode 코드 악취
추리 소설이나 로판소설.. "이번 생은 나혼자만 레벨업을 하도록 하겠습니다?" 등등.. 궁금증을 자아낼수록 좋지만, 코드는 아니다.
기인이나 예술적으로 보이고 싶더라도 참고 단순하고 명료하게 작성해야 한다.
이를 가장 직관적으로 먼저 드러내는 것이 이름이다.
하지만 소프트웨어 개발에서도 어렵다고 평가되는 이름 짓기.. 이후 등장하는 함수 선언 바꾸기, 변수 이름 바꾸기, 필드 이름 바꾸기ㅊ럼 이름을 바꾸는 리팩터링들이 있다.
똑같은 코드 구조가 여러 곳에서 반복된다면 냄새가 날 수 있다.
하나로 통합하여 더 나은 프로그램을 만들 수 있다.
가장 간단한 중복 코드의 예로, 한 클래스에 딸린 두 메서드가 똑같은 표현식을 사용하는 경우가 있다.
이럴 때는 함수 추출하기를 사용하여 추출된 메서드를 호출하게 바꾸면 된다.
코드가 비슷하긴 한데 완전히 똑같지는 않다면, 먼저 문장 슬라이드하기로 비슷한 부분을 한 곳에 모아 함수 추출하기를 더 쉽게 적용할 수 있는지 살펴본다.
같은 부모로부터 파생된 서브 클래스들에 코드가 중복되어 있다면, 각자 따로 호출되지 않도록 메서드 올리기를 적용해 부모로 옮긴다.
짧은 함수로 구성된 코드베이스를 얼핏 훑으면 연산하는 부분이 하나도 없어 보인다.
코드가 끝없이 위임하는 방식으로 작성되어 있기 때문이다.
하지만 이런 프로그램을 수십년간 다루다 보면 이 짧은 함수들이 얼마나 중요한지 깨닫게 된다.
간접 호출의 효과, 즉 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점은 함수를 짧게 구성할 때 나오는 것이다.
과거에는 서브루틴을 호출하는 비용이 컸기 때문에 짧은 함수를 꺼렸지만 요즘 언어들은 프로세스 안에서의 함수 호출 비용을 거의 없앴기 때문에 짧은 함수를 만드는 데 부담이 없다.
물론 사람이 보기에 왔다 갔다 해야하기 때문에 여전히 부담이 된다.
그러나 클린코드에서도 말하듯 함수 단위가 작아지면 작아질 수록 이름을 통해 본문을 볼 필요가 없어지게 된다.
이름 자체로 해당 코드가 설명이 된다면, 테스트가 통과 한다면 굳이 본문을 볼 필요가 없기 때문이다.
반대로 함수 길이가 길면 해당 동작과정을 함수 이름으로 나타내기엔 무리가 있다.
이를 위해 Intention 의도가 드러나게 이름을 짓는다.
의도
함수를 짧게 만드는 과정은 99%가 함수 추출하기가 차지한다.
함수가 매개변수와 임시 변수를 많이 사용하면 추출작업에 방해가 된다.
그렇다면 임시 변수를 질의 함수로 바꾸기로 임시 변수의 수를, 매개변수 객체 만들기와 객체 통째로 넘기기로 매개변수의 수르르 줄일 수 있을 것이다.
이 리팩터링을 적용해도 여전히 임시 변수와 매개변수가 너무 많다면 더 큰 수술이라 할 수 있는 함수를 명령으로 바꾸기를 고려해보자
조건문이나 반복문도 추출 대상의 실마리를 제공한다.
조건문은 조건문 분해하기로 대응한다.
거대한 switch문을 구성하는 case문마다 함수 추출하기를 적용해서 각 case의 본문을 함수 호출문 하나로 바꾼다.
같은 조건을 기준으로 나뉘는 switch문이 여러 개 있다면 조건부 로직을 다형성으로 바꾸기를 적용한다.
반복문도 그 안의 코드와 함께 추출해서 독립된 함수로 만든다.
매개변수는 최대 한개 없으면 더 좋다.
이런 매개변수는 매개변수를 질의 함수로 바꾸기로 제거할 수 있다.
사용 중인 데이터 구조에서 값을 뽑아 각각을 별개의 매개변수로 전달하는 코드라면 객체 통째로 넘기기를 적용해서 원본 데이터 구조를 그대로 전달한다.
항상 함께 전달되는 매게변수들은 매개변수 객체 만들기로 하나로 묶어버린다.
함수의 동작 방식을 정하는 플래그 역할의 매개변수는 플래그 인수 제거하기로 없애준다.
클래스는 매개변수를 줄이는 데 효과적인 수단이기도 하다.
특히 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 유용하다.
이럴 때는 여러 함수를 클래스로 묶기를 이용하여 공통 값들을 클래스의 필드로 정의한다.
전역 데이터 사용에 주의해야 한다는 말을 정말 많이 들었다.
이를 함부로 사용하는 프로그래머에겐 지옥에 간다는..?
코드의 악취, 안티 패턴의 최고의 속하는 전역 데이터.
이것이 문제를 발생시키는 항목은 정말 많지만 일단 어디서든 접근 가능하다는 것 만으로도 매우 큰 취약점이다.
이를 방지하기 위해 우리가 사용하는 대표적인 리팩터링은 변수 캡슐화하기다.
다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 이 기법을 가장 먼저 적용한다.
역설적으로 데이터 자체를 private으로 두고 필요에 의해 개방하는 방법이 있을 듯 하다
데이터를 변경했더니 예상하지 못한 버그가 나올 수 있다.
코드의 다른 곳에서는 다른 값을 기대한다는 사실을 인식하지 못한 채 수정해버리면 프로그램이 오작동한다.
특히 이 문제가 아주 드문 조건에만 발생한다면 알아내기가 매우 어렵다..
따라서 불변데이터를 기본으로 사용하기를 추천한다.
변수 캡슐화하기를 적용하여 함수를 거쳐야만 수정할 수 있도록 하거나 변수 쪼개기를 통해 용도별로 독립변수를 저장하게 하여 값 갱신이 문제를 일으킬 여지를 없앤다.
갱신 로직은 다른 코드와 떨어뜨려 놓는 것이 좋다. 그러기 위해 문장 슬라이드하기와 함수 추출하기를 이용해서 무언가를 갱신하는 코드를 분리한다.
가능한 세터 제거하기도 적용한다.
값을 다른 곳에서 설정할 수 있는 가변 데이터가 풍기는 악취는 특히 고약하므로 이럴 때는 파생 변수를 질의 함수로 바꾸기를 적용한다.
소프트웨어는 자고로 소프트해야 마땅하다.
코드를 수정할 때는 시스템에서 고쳐 할 군데를 찾아서 그 부분만 수정하 수 있기를 바라기 때문
SRP
지원해야할 데이터베이스가 추가될 때마다 함수 세 개를 변경해야 한다면 SRP를 위반하고 뒤엉킨 변경이 발생했다는 뜻이다.
데이터베이스에서 데이터를 가져와서 금융 상품 로직에서 처리해야 하는 일처럼 순차적으로 실행되는 게 자연스러운 맥락이라면, 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하는 식으로 구분한다.
전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높다면, 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모은다.
그러면 처리 과정이 맥락별로 구분된다.
이 때 맥락의 일에 관여하는 함수가 있다면 옮기기 전에 함수 추출하기부터 수행한다.
모듈이 클래스라면 클래스 추출하기가 맥락별 분리 방법을 잘 안내해줄 것이다.
이 냄새는 뒤엉킨 변경과 비슷하면서도 정반대다.
이 냄새는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍긴다.
변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽다.
이럴 때는 함수 옮기기와 필드 옮기기로 한 모듈에 묶어버리면 좋다.
비슷한 데이터를 다루는 함수가 많다면 여러 함수를 클래스로 묶기를 적용한다.
데이터 구조를 변환하거나 보강하는 함수들에는 여러 함수를 변환 함수로 묶기를 적용한다.
이렇게 묶은 함수들의 출력 결과를 묶어서 다음 단계의 로직으로 전달할 수 있다면 단계 쪼개기를 적용한다.
어설프게 분리된 로직을 함수 인라인하기나 클래스 인라인하기같은 인라인 리팩터링으로 하나로 합치는 것도 산탄총 수술에 대처하는 좋은 방법이다.
메서드나 클래스가 비대해지지만, 나중에 추출하기 리팩터링으로 더 좋은 형태로 분리할 수 있다.
사실 우리는 작은 함수와 클래스에 지나칠 정도로 집착하지만 코드를 재구성하는 단계에서는 큰 덩어리에 집착하지 않는다.
프로그램을 모듈화할 때는 코드를 여러 영역으로 나눈 뒤 영역안에서 이뤄지는 상호작용은 최대한 늘리고 영역 사이에서 이뤄지는 상호작용은 최소로 줄이는 게 주력한다.
기능 편애는 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용할 일이 더 많을 때 풍기는 냄새다.
한 객체가 게터 메서드 대 여섯개를 호출하도록 작성된 함수같은 경우는 함수가 데이터와 가까이 있고 싶어한다는 의중이 드러나므로 데이터 근처로 옮겨주면 된다. 함수 옮기기
때로는 기능을 편애할 수 있다.
이럴 때는 그 부분만 독립함수로 빼낸 다음 원하는 모듈로 보내준다.
물론 어디로 옮길지가 명확하게 드러나지 않을 때도 있다.
예컨대 함수가 사용하는 모듈이 다양하다면 가장 많은 데이터를 포함한 모듈로 옮긴다.
이런 규칙과 다르게 전략 패턴과 방문자 패턴은 함께 변경할 대상을 한데 모으는 것으로 데이터와 이를 활용하는 동작은 함께 변경해야 할 때가 많지만, 같은 데이터를 다르는 코드를 한 곳에서 변경할 수 있도록 옮긴다.
데이터 항목들은 서너 개가 여러 곳에서 항상 함께 뭉쳐 다니는 모습을 흔히 목격할 수 있다.
클래스 두어 개의 필드에서, 혹은 여러 메서드의 시그니처에서 함께 발견되기도 한다.
가장 먼저 필드 형태의 데이터 뭉치를 찾아서 클래스 추출하기로 하나의 객체로 묶는다.
다음은 메서드 시그니처에 있는 데이터 뭉치 차례이다.
먼저 매개변수 객체 만들기나 객체 통째로 넘기기를 적용해서 매개변수 수를 줄여본다.
그 즉시 메서드의 호출이 간결해질 것이다.
데이터 뭉치인지 확인하는 방법으로 값 하나를 삭제했을 때 나머지 데이터만으로는 의미가 없다면 객체로 환생하길 갈망하는 데이터 뭉치라는 뜻이다.
기능 편애를 없애는 과정에서 새로운 클래스를 만들었다면 그 클래스로 옮기면 좋을 동작은 없는지 확인해본다.
이러한 연계 과정은 상당한 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시키는 결과로 이어지기도 한다.
대부분의 프로그래밍 언어는 정수, 부동소수점 수, 문자열 같은 다양한 기본형을 제공한다.
라이브러리를 통해 날짜 같은 간단한 객체를 추가로 제공하기도 한다.
한편 프로그래머 중에는 자신에게 주어진 문제에 딱 맞는 기초 타입을 직접 정의하기를 몹시 꺼리는 사람이 많다.
그래서 금액을 그냥 숫자로 표현하거나, 날짜를 문자열로 표현하거나, 전화번호를 문자열로 표현하는 식이다.
이 냄새는 문자열을 다루는 코드에서 특히 흔하게 보인다.
기본형을 객체로 바꾸기를 적용하면 기본형만이 거주하는 구석기 동굴을 의미 있는 자료형들이 사는 최신식 코드로 탈바꿈할 수 있다.
많은 사람들이 switch문은 모조리 조건부 로직을 다형성으로 바꾸기로 없애야 할 대상이라고 주장한다.
중복된 switch문이 문제가 되는 이유는 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아서 함께 수정해야 하기 때문이다.
이럴 때 다형성은 반복된 switch문이 내뿜는 사악한 기운을 제압하여 코드베이스를 최신 스타일로 바꿔주는 세련된 무기인 셈이다.
반복문은 프로그래밍 언어가 등장할 때부터 함께한 프로그래밍 요소이다.
이제는 일급 함수를 지원하는 언어가 많아져서 반복문을 파이프라인으로 바꾸기를 적용해서 시대에 걸맞지 않은 반복문을 제거할 수 있게 되었다.
Rx프로그래밍
우리는 코드의 구조를 잡을 때 프로그램 요소를 이용하는 걸 좋아한다.
그래야 그 구조를 변형하거나 재활용할 기회가 생기고, 혹은 단순히 더 의미 있는 이름을 가졌기 때문이다.
그렇지만 그 구조가 필요 없을 때도 있다.
본문 코드를 그대로 쓰는 것과 진배없는 함수도 있고, 실질적인 메서드가 하나인 클래스도 있다.
아마 범용성이나 미래를 예측하여 제작하였지만 사용되지 않아 남겨졌을 것이다.
이런 요소는 제거하는 것이 좋다
이 제거 작업은 함수 인라인하기나 클래스 인라인하기로 처리하며, 상속을 사용했다면 계층 합치기를 적용한다.
추측성 일반화는 우리가 민감하게 반응하는 냄새로 "나중에 필요할 거야"라는 생각으로 당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다.
미래에 대비해 작성한 부분을 실제로 사용하게 되면 다행이지만, 그렇지 않는다면 쓸데없이 낭비일 뿐이다.
당장 걸리적거리는 코드는 눈앞에서 치워버려라
하는 일이 거의 없는 추상 클래스는 계층 합치기로 제거한다.
쓸데없이 위임하는 코드는 함수 인라인하기나 클래스 인라인하기로 삭제한다.
본문에서 사용되지 않는 매개변수는 함수 선언 바꾸기로 없앤다.
추측성 일반화는 테스트 코드 말고는 사용하는 곳이 없는 함수나 클래스에서 흔히 볼 수 있다.
이런 코드를 발견하면 테스트 케이스부터 삭제한 뒤에 죽은 코드 제거하기로 날려버린다.
간혹 특정 상황에서만 값이 설정되는 필드를 가진 클래스도 있다.
하지만 객체를 가져올 때는 당연히 모든 필드가 채워져 있으리라 기대하는 게 보통이다.
이렇게 임시 필드를 갖도록 작성하면 코드를 이해하기 어렵다.
그래서 사용자는 쓰이지 않는 것처럼 보이는 필드가 존재하는 이유를 파악하느라 머리를 싸메게 된다.
이렇게 덩그러니 떨어져 있는 필드르을 발견하면 클래스 추출하기로 제 살 곳을 찾아준다.
그런 다음 함수 옮기기로 임시 필드들과 관련된 코드를 모조리 새 클래스에 몰아넣는다.
또한, 임시 필드들이 유요한지 확인한 후 동작하는 조건부 로직이 있을 수 있는데, 특이 케이스 추가하기로 필드들이 유요하지 않을 때를 위한 대안 클래스를 만들어서 제거할 수 있다.
메세지 체인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다.
기차충돌
게터의 꼬리를 물고 임시 변수들이 줄줄이 나열되는 코드가 있는데, 이는 클라이언트가 객체 내비게이션에 종속됐음을 의미한다.
그래서 내비게이션 중간 단계를 수정하면 클라이언트도 수정해야 한다.
SRP위반
이 문제는 위임 숨기기로 해결한다.
이 리팩터링은 메시지 체인의 다양한 연결점에 적용할 수 있다.
원칙적으로 체인을 구성하는 모든 객체에 적용할 수 있지만, 그러다 보면 중간 객체들이 모두 중재자가 돼버리기 쉽다.
그러니 최종 결과 객체가 어떻게 쓰이는지부터 살펴보는 게 좋다.
직접 해당 객체에 접근하는 것이 아닌 해당 객체의 존재를 숨김으로 중재자가 되는 것이다.
객체의 대표적인 기능 중 하나로, 외부로부터 세부사항을 숨겨주는 캡슐화가 있다.
캡슐화의 과정에선 위임이 자주 활용되는데, 예를 들어 우리가 팀장에게 미팅을 요청한다고 하자면, 팀장은 자신의 일정을 확인한 후 답을 준다.
이후 팀장이 어떤 방법으로 일정을 기록하는지는 신경쓰지 않는다.
이런 중개자도 지나치면 문제가 된다.
클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있다면 코드를 제대로 읽을 수 없게 된다.
이럴 때는 중개자 제거하기를 활용하여 실제로 일을 하는 객체와 직접 소통하게 하자.
모듈 사이의 데이터 거래가 많으면 결합도가 높아진다고 말하는데, 일이 제대로 돌아가게 하려면 거래가 이뤄질 수 밖에 없지만, 그 양을 최소로 줄이고 모두 투명하게 처리해야 한다.
커피 자판기 옆에서 은밀히 데이터를 주고받는 모듈들이 있다면 함수 옮기기와 필드 옮기기 기법으로 떼어놓아서 사적으로 처리하는 부분을 줄인다.
여러 모듈이 같은 관심사를 공유한다면 공통 부분을 정식으로 처리하는 제 3자의 모듈을 새로 만들거나 위임 숨기기를 이용하여 다른 모듈이 중간자 역할을 하게 만든다.
한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어난다.
그리고 클래스에 필드가 너무 많으면 중복 코드가 생기기 쉽다.
이럴 때는 클래스 추출하기로 필드들 일부를 따로 묶는다.
같은 컴포넌트에 모아두는 것이 합당해 보이는 필드들을 선택하면 된다.
더 일반적으로는 한 클래스 안에서 접두어나 접미어가 같은 필드들이 함께 추출할 후보들이다.
이렇게 분리할 컴포넌트를 원래 클래스와 상속 관계로 만드는 게 좋다면 슈퍼클래스 추출하기나 타입 코드를 서브클래스로 바꾸기를 적용하는 편이 더 쉬울 것이다.
클래스가 항시 모든 필드를 사용하지 않을 수도 있다.
이럴 때는 앞에서 언급한 추출 기법을 여러 차례 수행해야 할지도 모른다.
코드도 마찬가지로 너무 길다면 이는 중복 코드가 발생활 확률이 높다는 것을 의미한다.
클래스를 사용할 때의 큰 장점은 필요에 따라 언제든 다른 클래스로 교체할 수 있다는 것이다.
단 교체하려면 인터페이스가 같아야 한다.
따라서 함수 선언 바꾸기로 메서드 시그니처를 일치시킨다.
때로는 이것만으로 부족한데, 이럴 때는 함수 옮기기를 이용하여 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어 넣는다.
그러다 대안 클래스들 사이에 중복 코드가 생기면 슈퍼클래스 추출하기를 적용할지 고려해본다.
데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말한다.
그저 데이터 클래스가 너무 깊이까지 함부로 다룰 때가 많다.
이런 클래스에 public 필드가 있다면 레코드 캡슐화하기로 숨기자
변경하면 안되는 필드는 세터 제거하기로 접근을 봉쇄한다.
C#에서 record라는 타입이 있는데 9버전에서 도입된 나름 신기술이다.
class와 같이 참조형식이지만 구조체의 특징도 같이 가지고 있다.
불변 객체를 보장할 수 있기 때문에 공부해보는 것도 좋을 것 같다.
상속에 관한 논쟁은 아주 뜨겁지만 최근에는 상속을 피하는 것으로 많이 넘어갔다고 생각된다.
최근에 읽은 책 모두 상속을 피하고 합성과 구성에 집중하고 있다.
서브클래스가 부모로부터 메서드와 데이터를 물려받고 싶지 않아한다면 그것이 가능할까?
먼저 같은 계층에서 서브클래스를 하나 새로 만들고, 메서드 내리기와 필드 내리기를 통해서 물려받지 않을 부모 코드를 모조리 새로 만든 서브클래스로 넘긴다.
그러면 부모에는 공통된 부분만 남는다.
주석을 달면 안된다고 말하려는 건 아니니 걱정하지 말자??
주석을 다는 것도 좋지만 좋은 코드로 보여주는 것이 더 바람직하다.
주석은 오히려 가독성을 떨어뜨리고 코드를 복잡하게 만든다.
일종의 변명에 불과하다고 말한다.
주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링해본다.
정말 이 부분은 클린코드의 연장선이자 같은 내용이라 생각했다.
읽은지 얼마 되지도 않아서 지식을 정리하는데 큰 도움이 된 듯하다.
지금은 가볍게 읽고 뒤의 세부 카탈로그를 보고 다시 읽어보는 것도 좋을 둣 하다.
읽으시면서 느끼거나 본 가장 심한 악취는 뭐였나요?
The text was updated successfully, but these errors were encountered:
fkdl0048
No branches or pull requests
3. 코드에서 나는 악취
이번 장에서는 리팩터링의 타이밍을 결정하는 냄새를 살펴본다.
인스턴스 변수를 삭제하거나 상속 계층을 만든느 방법을 설명하기는 쉽다.
하지만 이런 일을 언제 해야하는 지는 명확하게 정립된 규칙이 없다.
결론부터 말하면 이런 결정은 경험과 직관에 의존한다.
즉, 코드 감각을 키워야 하는 영역이다.
따라서 많이 등장하는 코드 악취, 냄새를 예로 보여주며 냄새가 나는 부분을 파악하는 감을 키워보자.
이후 6~12장의 카탈로그로 악취를 없애는 과정을 준비하자
CleanCode에선 냄새의 연장선으로 좀 더 확장된 개념을 다룬 듯 하여 링크를 같이 첨부한다.
CleanCode 코드 악취
3.1 기이한 이름
추리 소설이나 로판소설.. "이번 생은 나혼자만 레벨업을 하도록 하겠습니다?" 등등.. 궁금증을 자아낼수록 좋지만, 코드는 아니다.
기인이나 예술적으로 보이고 싶더라도 참고 단순하고 명료하게 작성해야 한다.
이를 가장 직관적으로 먼저 드러내는 것이 이름이다.
하지만 소프트웨어 개발에서도 어렵다고 평가되는 이름 짓기.. 이후 등장하는 함수 선언 바꾸기, 변수 이름 바꾸기, 필드 이름 바꾸기ㅊ럼 이름을 바꾸는 리팩터링들이 있다.
3.2 중복 코드
똑같은 코드 구조가 여러 곳에서 반복된다면 냄새가 날 수 있다.
하나로 통합하여 더 나은 프로그램을 만들 수 있다.
가장 간단한 중복 코드의 예로, 한 클래스에 딸린 두 메서드가 똑같은 표현식을 사용하는 경우가 있다.
이럴 때는 함수 추출하기를 사용하여 추출된 메서드를 호출하게 바꾸면 된다.
코드가 비슷하긴 한데 완전히 똑같지는 않다면, 먼저 문장 슬라이드하기로 비슷한 부분을 한 곳에 모아 함수 추출하기를 더 쉽게 적용할 수 있는지 살펴본다.
같은 부모로부터 파생된 서브 클래스들에 코드가 중복되어 있다면, 각자 따로 호출되지 않도록 메서드 올리기를 적용해 부모로 옮긴다.
3.3 긴 함수
짧은 함수로 구성된 코드베이스를 얼핏 훑으면 연산하는 부분이 하나도 없어 보인다.
코드가 끝없이 위임하는 방식으로 작성되어 있기 때문이다.
하지만 이런 프로그램을 수십년간 다루다 보면 이 짧은 함수들이 얼마나 중요한지 깨닫게 된다.
간접 호출의 효과, 즉 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점은 함수를 짧게 구성할 때 나오는 것이다.
과거에는 서브루틴을 호출하는 비용이 컸기 때문에 짧은 함수를 꺼렸지만 요즘 언어들은 프로세스 안에서의 함수 호출 비용을 거의 없앴기 때문에 짧은 함수를 만드는 데 부담이 없다.
물론 사람이 보기에 왔다 갔다 해야하기 때문에 여전히 부담이 된다.
그러나 클린코드에서도 말하듯 함수 단위가 작아지면 작아질 수록 이름을 통해 본문을 볼 필요가 없어지게 된다.
이름 자체로 해당 코드가 설명이 된다면, 테스트가 통과 한다면 굳이 본문을 볼 필요가 없기 때문이다.
반대로 함수 길이가 길면 해당 동작과정을 함수 이름으로 나타내기엔 무리가 있다.
이를 위해 Intention
의도
가 드러나게 이름을 짓는다.함수를 짧게 만드는 과정은 99%가 함수 추출하기가 차지한다.
함수가 매개변수와 임시 변수를 많이 사용하면 추출작업에 방해가 된다.
그렇다면 임시 변수를 질의 함수로 바꾸기로 임시 변수의 수를, 매개변수 객체 만들기와 객체 통째로 넘기기로 매개변수의 수르르 줄일 수 있을 것이다.
이 리팩터링을 적용해도 여전히 임시 변수와 매개변수가 너무 많다면 더 큰 수술이라 할 수 있는 함수를 명령으로 바꾸기를 고려해보자
조건문이나 반복문도 추출 대상의 실마리를 제공한다.
조건문은 조건문 분해하기로 대응한다.
거대한 switch문을 구성하는 case문마다 함수 추출하기를 적용해서 각 case의 본문을 함수 호출문 하나로 바꾼다.
같은 조건을 기준으로 나뉘는 switch문이 여러 개 있다면 조건부 로직을 다형성으로 바꾸기를 적용한다.
반복문도 그 안의 코드와 함께 추출해서 독립된 함수로 만든다.
3.4 긴 매개변수 목록
매개변수는 최대 한개 없으면 더 좋다.
이런 매개변수는 매개변수를 질의 함수로 바꾸기로 제거할 수 있다.
사용 중인 데이터 구조에서 값을 뽑아 각각을 별개의 매개변수로 전달하는 코드라면 객체 통째로 넘기기를 적용해서 원본 데이터 구조를 그대로 전달한다.
항상 함께 전달되는 매게변수들은 매개변수 객체 만들기로 하나로 묶어버린다.
함수의 동작 방식을 정하는 플래그 역할의 매개변수는 플래그 인수 제거하기로 없애준다.
클래스는 매개변수를 줄이는 데 효과적인 수단이기도 하다.
특히 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 유용하다.
이럴 때는 여러 함수를 클래스로 묶기를 이용하여 공통 값들을 클래스의 필드로 정의한다.
3.5 전역 데이터
전역 데이터 사용에 주의해야 한다는 말을 정말 많이 들었다.
이를 함부로 사용하는 프로그래머에겐 지옥에 간다는..?
코드의 악취, 안티 패턴의 최고의 속하는 전역 데이터.
이것이 문제를 발생시키는 항목은 정말 많지만 일단 어디서든 접근 가능하다는 것 만으로도 매우 큰 취약점이다.
이를 방지하기 위해 우리가 사용하는 대표적인 리팩터링은 변수 캡슐화하기다.
다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 이 기법을 가장 먼저 적용한다.
역설적으로 데이터 자체를 private으로 두고 필요에 의해 개방하는 방법이 있을 듯 하다
3.6 가변 데이터
데이터를 변경했더니 예상하지 못한 버그가 나올 수 있다.
코드의 다른 곳에서는 다른 값을 기대한다는 사실을 인식하지 못한 채 수정해버리면 프로그램이 오작동한다.
특히 이 문제가 아주 드문 조건에만 발생한다면 알아내기가 매우 어렵다..
따라서 불변데이터를 기본으로 사용하기를 추천한다.
변수 캡슐화하기를 적용하여 함수를 거쳐야만 수정할 수 있도록 하거나 변수 쪼개기를 통해 용도별로 독립변수를 저장하게 하여 값 갱신이 문제를 일으킬 여지를 없앤다.
갱신 로직은 다른 코드와 떨어뜨려 놓는 것이 좋다. 그러기 위해 문장 슬라이드하기와 함수 추출하기를 이용해서 무언가를 갱신하는 코드를 분리한다.
가능한 세터 제거하기도 적용한다.
값을 다른 곳에서 설정할 수 있는 가변 데이터가 풍기는 악취는 특히 고약하므로 이럴 때는 파생 변수를 질의 함수로 바꾸기를 적용한다.
3.7 뒤엉킨 변경
소프트웨어는 자고로 소프트해야 마땅하다.
코드를 수정할 때는 시스템에서 고쳐 할 군데를 찾아서 그 부분만 수정하 수 있기를 바라기 때문
SRP
지원해야할 데이터베이스가 추가될 때마다 함수 세 개를 변경해야 한다면 SRP를 위반하고 뒤엉킨 변경이 발생했다는 뜻이다.
데이터베이스에서 데이터를 가져와서 금융 상품 로직에서 처리해야 하는 일처럼 순차적으로 실행되는 게 자연스러운 맥락이라면, 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하는 식으로 구분한다.
전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높다면, 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모은다.
그러면 처리 과정이 맥락별로 구분된다.
이 때 맥락의 일에 관여하는 함수가 있다면 옮기기 전에 함수 추출하기부터 수행한다.
모듈이 클래스라면 클래스 추출하기가 맥락별 분리 방법을 잘 안내해줄 것이다.
3.8 산탄총 수술
이 냄새는 뒤엉킨 변경과 비슷하면서도 정반대다.
이 냄새는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍긴다.
변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽다.
이럴 때는 함수 옮기기와 필드 옮기기로 한 모듈에 묶어버리면 좋다.
비슷한 데이터를 다루는 함수가 많다면 여러 함수를 클래스로 묶기를 적용한다.
데이터 구조를 변환하거나 보강하는 함수들에는 여러 함수를 변환 함수로 묶기를 적용한다.
이렇게 묶은 함수들의 출력 결과를 묶어서 다음 단계의 로직으로 전달할 수 있다면 단계 쪼개기를 적용한다.
어설프게 분리된 로직을 함수 인라인하기나 클래스 인라인하기같은 인라인 리팩터링으로 하나로 합치는 것도 산탄총 수술에 대처하는 좋은 방법이다.
메서드나 클래스가 비대해지지만, 나중에 추출하기 리팩터링으로 더 좋은 형태로 분리할 수 있다.
사실 우리는 작은 함수와 클래스에 지나칠 정도로 집착하지만 코드를 재구성하는 단계에서는 큰 덩어리에 집착하지 않는다.
3.9 기능 편애
프로그램을 모듈화할 때는 코드를 여러 영역으로 나눈 뒤 영역안에서 이뤄지는 상호작용은 최대한 늘리고 영역 사이에서 이뤄지는 상호작용은 최소로 줄이는 게 주력한다.
기능 편애는 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용할 일이 더 많을 때 풍기는 냄새다.
한 객체가 게터 메서드 대 여섯개를 호출하도록 작성된 함수같은 경우는 함수가 데이터와 가까이 있고 싶어한다는 의중이 드러나므로 데이터 근처로 옮겨주면 된다. 함수 옮기기
때로는 기능을 편애할 수 있다.
이럴 때는 그 부분만 독립함수로 빼낸 다음 원하는 모듈로 보내준다.
물론 어디로 옮길지가 명확하게 드러나지 않을 때도 있다.
예컨대 함수가 사용하는 모듈이 다양하다면 가장 많은 데이터를 포함한 모듈로 옮긴다.
이런 규칙과 다르게 전략 패턴과 방문자 패턴은 함께 변경할 대상을 한데 모으는 것으로 데이터와 이를 활용하는 동작은 함께 변경해야 할 때가 많지만, 같은 데이터를 다르는 코드를 한 곳에서 변경할 수 있도록 옮긴다.
3.10 임시 필드
데이터 항목들은 서너 개가 여러 곳에서 항상 함께 뭉쳐 다니는 모습을 흔히 목격할 수 있다.
클래스 두어 개의 필드에서, 혹은 여러 메서드의 시그니처에서 함께 발견되기도 한다.
가장 먼저 필드 형태의 데이터 뭉치를 찾아서 클래스 추출하기로 하나의 객체로 묶는다.
다음은 메서드 시그니처에 있는 데이터 뭉치 차례이다.
먼저 매개변수 객체 만들기나 객체 통째로 넘기기를 적용해서 매개변수 수를 줄여본다.
그 즉시 메서드의 호출이 간결해질 것이다.
데이터 뭉치인지 확인하는 방법으로 값 하나를 삭제했을 때 나머지 데이터만으로는 의미가 없다면 객체로 환생하길 갈망하는 데이터 뭉치라는 뜻이다.
기능 편애를 없애는 과정에서 새로운 클래스를 만들었다면 그 클래스로 옮기면 좋을 동작은 없는지 확인해본다.
이러한 연계 과정은 상당한 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시키는 결과로 이어지기도 한다.
3.11 기본형 집착
대부분의 프로그래밍 언어는 정수, 부동소수점 수, 문자열 같은 다양한 기본형을 제공한다.
라이브러리를 통해 날짜 같은 간단한 객체를 추가로 제공하기도 한다.
한편 프로그래머 중에는 자신에게 주어진 문제에 딱 맞는 기초 타입을 직접 정의하기를 몹시 꺼리는 사람이 많다.
그래서 금액을 그냥 숫자로 표현하거나, 날짜를 문자열로 표현하거나, 전화번호를 문자열로 표현하는 식이다.
이 냄새는 문자열을 다루는 코드에서 특히 흔하게 보인다.
기본형을 객체로 바꾸기를 적용하면 기본형만이 거주하는 구석기 동굴을 의미 있는 자료형들이 사는 최신식 코드로 탈바꿈할 수 있다.
3.12 반복되는 switch문
많은 사람들이 switch문은 모조리 조건부 로직을 다형성으로 바꾸기로 없애야 할 대상이라고 주장한다.
중복된 switch문이 문제가 되는 이유는 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아서 함께 수정해야 하기 때문이다.
이럴 때 다형성은 반복된 switch문이 내뿜는 사악한 기운을 제압하여 코드베이스를 최신 스타일로 바꿔주는 세련된 무기인 셈이다.
3.13 반복문
반복문은 프로그래밍 언어가 등장할 때부터 함께한 프로그래밍 요소이다.
이제는 일급 함수를 지원하는 언어가 많아져서 반복문을 파이프라인으로 바꾸기를 적용해서 시대에 걸맞지 않은 반복문을 제거할 수 있게 되었다.
Rx프로그래밍
3.14 성의 없는 요소
우리는 코드의 구조를 잡을 때 프로그램 요소를 이용하는 걸 좋아한다.
그래야 그 구조를 변형하거나 재활용할 기회가 생기고, 혹은 단순히 더 의미 있는 이름을 가졌기 때문이다.
그렇지만 그 구조가 필요 없을 때도 있다.
본문 코드를 그대로 쓰는 것과 진배없는 함수도 있고, 실질적인 메서드가 하나인 클래스도 있다.
아마 범용성이나 미래를 예측하여 제작하였지만 사용되지 않아 남겨졌을 것이다.
이런 요소는 제거하는 것이 좋다
이 제거 작업은 함수 인라인하기나 클래스 인라인하기로 처리하며, 상속을 사용했다면 계층 합치기를 적용한다.
3.15 추측성 일반화
추측성 일반화는 우리가 민감하게 반응하는 냄새로 "나중에 필요할 거야"라는 생각으로 당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다.
미래에 대비해 작성한 부분을 실제로 사용하게 되면 다행이지만, 그렇지 않는다면 쓸데없이 낭비일 뿐이다.
당장 걸리적거리는 코드는 눈앞에서 치워버려라
하는 일이 거의 없는 추상 클래스는 계층 합치기로 제거한다.
쓸데없이 위임하는 코드는 함수 인라인하기나 클래스 인라인하기로 삭제한다.
본문에서 사용되지 않는 매개변수는 함수 선언 바꾸기로 없앤다.
추측성 일반화는 테스트 코드 말고는 사용하는 곳이 없는 함수나 클래스에서 흔히 볼 수 있다.
이런 코드를 발견하면 테스트 케이스부터 삭제한 뒤에 죽은 코드 제거하기로 날려버린다.
3.16 임시 필드
간혹 특정 상황에서만 값이 설정되는 필드를 가진 클래스도 있다.
하지만 객체를 가져올 때는 당연히 모든 필드가 채워져 있으리라 기대하는 게 보통이다.
이렇게 임시 필드를 갖도록 작성하면 코드를 이해하기 어렵다.
그래서 사용자는 쓰이지 않는 것처럼 보이는 필드가 존재하는 이유를 파악하느라 머리를 싸메게 된다.
이렇게 덩그러니 떨어져 있는 필드르을 발견하면 클래스 추출하기로 제 살 곳을 찾아준다.
그런 다음 함수 옮기기로 임시 필드들과 관련된 코드를 모조리 새 클래스에 몰아넣는다.
또한, 임시 필드들이 유요한지 확인한 후 동작하는 조건부 로직이 있을 수 있는데, 특이 케이스 추가하기로 필드들이 유요하지 않을 때를 위한 대안 클래스를 만들어서 제거할 수 있다.
3.17 메시지 체인
메세지 체인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다.
기차충돌
게터의 꼬리를 물고 임시 변수들이 줄줄이 나열되는 코드가 있는데, 이는 클라이언트가 객체 내비게이션에 종속됐음을 의미한다.
그래서 내비게이션 중간 단계를 수정하면 클라이언트도 수정해야 한다.
SRP위반
이 문제는 위임 숨기기로 해결한다.
이 리팩터링은 메시지 체인의 다양한 연결점에 적용할 수 있다.
원칙적으로 체인을 구성하는 모든 객체에 적용할 수 있지만, 그러다 보면 중간 객체들이 모두 중재자가 돼버리기 쉽다.
그러니 최종 결과 객체가 어떻게 쓰이는지부터 살펴보는 게 좋다.
직접 해당 객체에 접근하는 것이 아닌 해당 객체의 존재를 숨김으로 중재자가 되는 것이다.
3.18 중개자
객체의 대표적인 기능 중 하나로, 외부로부터 세부사항을 숨겨주는 캡슐화가 있다.
캡슐화의 과정에선 위임이 자주 활용되는데, 예를 들어 우리가 팀장에게 미팅을 요청한다고 하자면, 팀장은 자신의 일정을 확인한 후 답을 준다.
이후 팀장이 어떤 방법으로 일정을 기록하는지는 신경쓰지 않는다.
이런 중개자도 지나치면 문제가 된다.
클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있다면 코드를 제대로 읽을 수 없게 된다.
이럴 때는 중개자 제거하기를 활용하여 실제로 일을 하는 객체와 직접 소통하게 하자.
3.19 내부자 거래
모듈 사이의 데이터 거래가 많으면 결합도가 높아진다고 말하는데, 일이 제대로 돌아가게 하려면 거래가 이뤄질 수 밖에 없지만, 그 양을 최소로 줄이고 모두 투명하게 처리해야 한다.
커피 자판기 옆에서 은밀히 데이터를 주고받는 모듈들이 있다면 함수 옮기기와 필드 옮기기 기법으로 떼어놓아서 사적으로 처리하는 부분을 줄인다.
여러 모듈이 같은 관심사를 공유한다면 공통 부분을 정식으로 처리하는 제 3자의 모듈을 새로 만들거나 위임 숨기기를 이용하여 다른 모듈이 중간자 역할을 하게 만든다.
3.20 거대한 클래스
한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어난다.
그리고 클래스에 필드가 너무 많으면 중복 코드가 생기기 쉽다.
이럴 때는 클래스 추출하기로 필드들 일부를 따로 묶는다.
같은 컴포넌트에 모아두는 것이 합당해 보이는 필드들을 선택하면 된다.
더 일반적으로는 한 클래스 안에서 접두어나 접미어가 같은 필드들이 함께 추출할 후보들이다.
이렇게 분리할 컴포넌트를 원래 클래스와 상속 관계로 만드는 게 좋다면 슈퍼클래스 추출하기나 타입 코드를 서브클래스로 바꾸기를 적용하는 편이 더 쉬울 것이다.
클래스가 항시 모든 필드를 사용하지 않을 수도 있다.
이럴 때는 앞에서 언급한 추출 기법을 여러 차례 수행해야 할지도 모른다.
코드도 마찬가지로 너무 길다면 이는 중복 코드가 발생활 확률이 높다는 것을 의미한다.
3.21 서로 다른 인터페이스의 대안 클래스들
클래스를 사용할 때의 큰 장점은 필요에 따라 언제든 다른 클래스로 교체할 수 있다는 것이다.
단 교체하려면 인터페이스가 같아야 한다.
따라서 함수 선언 바꾸기로 메서드 시그니처를 일치시킨다.
때로는 이것만으로 부족한데, 이럴 때는 함수 옮기기를 이용하여 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어 넣는다.
그러다 대안 클래스들 사이에 중복 코드가 생기면 슈퍼클래스 추출하기를 적용할지 고려해본다.
3.22 데이터 클래스
데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말한다.
그저 데이터 클래스가 너무 깊이까지 함부로 다룰 때가 많다.
이런 클래스에 public 필드가 있다면 레코드 캡슐화하기로 숨기자
변경하면 안되는 필드는 세터 제거하기로 접근을 봉쇄한다.
C#에서 record라는 타입이 있는데 9버전에서 도입된 나름 신기술이다.
class와 같이 참조형식이지만 구조체의 특징도 같이 가지고 있다.
불변 객체를 보장할 수 있기 때문에 공부해보는 것도 좋을 것 같다.
3.23 상속 포기
상속에 관한 논쟁은 아주 뜨겁지만 최근에는 상속을 피하는 것으로 많이 넘어갔다고 생각된다.
최근에 읽은 책 모두 상속을 피하고 합성과 구성에 집중하고 있다.
서브클래스가 부모로부터 메서드와 데이터를 물려받고 싶지 않아한다면 그것이 가능할까?
먼저 같은 계층에서 서브클래스를 하나 새로 만들고, 메서드 내리기와 필드 내리기를 통해서 물려받지 않을 부모 코드를 모조리 새로 만든 서브클래스로 넘긴다.
그러면 부모에는 공통된 부분만 남는다.
3.24 주석
주석을 달면 안된다고 말하려는 건 아니니 걱정하지 말자??
주석을 다는 것도 좋지만 좋은 코드로 보여주는 것이 더 바람직하다.
주석은 오히려 가독성을 떨어뜨리고 코드를 복잡하게 만든다.
일종의 변명에 불과하다고 말한다.
느낀점
정말 이 부분은 클린코드의 연장선이자 같은 내용이라 생각했다.
읽은지 얼마 되지도 않아서 지식을 정리하는데 큰 도움이 된 듯하다.
지금은 가볍게 읽고 뒤의 세부 카탈로그를 보고 다시 읽어보는 것도 좋을 둣 하다.
논의사항
읽으시면서 느끼거나 본 가장 심한 악취는 뭐였나요?
The text was updated successfully, but these errors were encountered: