본문 바로가기
공부 기록

[리팩토링 1판] Chapter 03. 코드의 구린내

by 매트(Mat) 2024. 3. 29.

Chapter 03. 코드의 구린내

리팩토링이 필요한 경우는 코드에서 구린내가 날 때이다. (?..) 어떤 경우가 구린내가 나는지 알아보자.

중복코드

구린내의 제왕은 누가 뭐래도 중복코드다. 똑같은 구조가 두 군데 이상 있을 때는 그 부분을 하나로 통일하면 프로그램이 개선된다.

중복코드의 가장 단순한 예는 한 클래스의 두 메서드 안에 같은 코드가 들어 있는 경우다. 이럴 때는 메서드 추출 기법을 적용해서 중복을 없앤 후 메서드 상향 기법을 적용하면 된다.

장황한 메서드

최적의 상태로 장수하는 객체 프로그램을 보면 공통적으로 메서드 길이가 짧다. 객체지향 프로그래밍 경험이 쌓일수록 짧은 메서드의 중요성을 실감한다.

짧은 메서드를 이해하기 쉽게 하려면 메서드명을 잘 정해야 한다. 메서드의 기능을 한눈에 알 수 있는 메서드명을 사용하면 그 메서드 안의 코드를 분석하지 않아도 된다.

이는 한 메서드 안에 여러 주석을 달아서 길게 작성하는 것보다는 주석을 달아야 할 것 같은 부분에 주석을 넣는 대신 메서드 추출 기법을 사용하여 분리하는 것이다. 메서드를 줄이려면 메서드 추출 기법은 십중팔구 사용해야 한다.

메서드에 매개변수나 임시변수가 많으면 메서드 추출을 실시하기가 까다롭다. 메서드 추출을 하려면 수많은 매개변수와 임시변수를 새로 만든 메서드의 매개변수로 넘겨야 하는데, 이러면 의미가 없다. 그래서 왠만한 경우에 임시변수를 메서드 호출로 전환 기법이나 임시변수를 메서드 체인으로 전환 기법을 적용하면 임시변수가 제거된다.

길게 열거된 매개변수는 매개변수 세트를 객체로 전환 기법이나 객체를 통째로 전달 기법을 적용하면 간결해진다. 이래도 많을 때는 메서드를 메서드 객체로 전환 기법을 적용하면 된다.

조건문과 루프 역시 메서드로 빼야 한다.

방대한 클래스

기능이 지나치게 많은 클래스에는 보통 엄청난 수의 인스턴스 변수가 들어있다. 클래스에 인스턴스 변수가 너무 많으면 중복코드가 반드시 존재하게 마련이다.

클래스 추출을 실시하면 수많은 인스턴스 변수를 하나로 묶을 수 있다. 서로 연관된 변수를 골라서 클래스로 빼내면 된다.

과도한 매개변수

전역 변수를 사용할 바에는 매개변수로 넘기는 것이 낫다. 그런데 매개변수가 너무 많아지면 유지보수하기 힘들어지는데 객체를 매개변수로 넘기면 해결할 수 있다.

수정의 산발

만약 새 금융 상품을 추가할 때마다 한 클래스에 4개의 메서드를 수정해야 한다면 하나의 클래스를 여러 변형 객체로 분리하는 것이 좋다.

기능의 산재

수정할 때마다 여러 클래스에서 수많은 자잘한 부분을 고쳐야 한다면 이 문제를 의심할 수 있다.

이 때는 메서드 이동과 필드 이동을 적용해서 수정할 부분들을 전부 하나의 클래스 안에 넣어야 한다.

잘못된 소속

객체의 핵심은 데이터와 그 데이터에 사용되는 프로세스를 한 데 묶는 기술이다. 어떤 메서드가 자신이 속하지 않은 클래스에 더 많이 접근한다면 잘못된 소속의 구린내가 풍길 것이다.

이럴 때는 메서드 이동이나 메서드를 추출을 통해 적절한 클래스로 옮기면 된다.

데이터 뭉치

동일한 3~4개의 데이터 항목이 여러 위치에 몰려 있는 경우가 많은데 이렇게 몰려 있는 데이터 뭉치는 객체로 만들어야 한다.

이 때는 필드들을 대상으로 클래스 추출 기법을 적용하면 된다. 그러고 나서 메서드 시그니처를 대상으로 매개변수 세트를 객체로 전화 기법과 객체를 통째로 전달 기법을 적용하여 간결하게 만들어야 한다.

강박적 기본 타입 사용

기본형만 사용할 바에는 객체를 만들어서 사용해라.

객체를 처음 접하는 사람은 보통 숫자와 통화를 연동하는 돈 관련 클래스나 전화번호와 우편번호와 같은 특수 문자열 클래스 등의 사소한 작업에 작은 객체를 잘 사용하지 않으려는 경향이 있다. 이러한 우물안 개구리를 벗어나려면 데이터 값을 객체로 전환하면 된다.

switch 문

객체지향 코드의 확연한 특징 중 하나는 switch-case 문이 비교적 적게 사용된다는 점이다. switch문의 단점은 반드시 중복이 생긴다는 점이다.

이를 해결하려면 다형성, 즉 재정의를 이용하는 것이다.

평행 상속 계층

해당 문제점은 한 클래스의 하위클래스를 만들 때마다 매번 다른 클래스의 하위클래스도 만들어야 한다. 서로 다른 두 상속 계층의 클래스명 접두어가 같으면 이 문제를 의심할 수 있다.

한 상속 계층의 인스턴스가 다른 상속 계층의 인스턴스를 참조하게 만들면 해결할 수 있다.

직무유기 클래스

하나의 클래스를 작성할 때마다 유지관리와 이해하기 위한 비용이 추가된다. 따라서 비용만큼의 기능을 수행하지 못하는 비효율적 클래스는 없애야 한다. 이처럼 없애야 하는 클래스를 직무유기(Lazy) 클래스라 부른다.

막연한 범용 코드

조만간 이런 기능이 필요하겠다고 막연한 생각에 아직은 필요없는 기능을 수행하고자 온갖 호출과 case문을 넣으려 하는 그 순간 막연한 범용 코드의 구린내가 풍긴다.

임시 필드

어떤 객체 안에 인스턴스 변수가 특정 상황에서만 할당되는 경우가 간혹 있다. 개발자는 객체가 그 안에 들어있는 모든 변수를 이용하리라 생각하기 마련이므로 이런 코드는 파악하기 힘들다.

이 때는 해당하는 클래스 추출 기법을 수행하면 된다.

메시지 체인

메시지 체인은 클라이언트가 한 객체에 제 2의 객체를 요청하면, 제 2의 객체가 제 3의 객체를 요청하고, 제 3의 객체가 제 4의 객체를 요청하는 식으로 연쇄적 요청이 발생하는 문제점을 뜻한다.

이를 해결하려면 결과 객체가 어느 대상에 사용되는지를 알아낸 후 해당 부분을 메서드 추출을 통해 별도의 메서드로 빼낸 후 메서드 이동을 실시한다.

과잉 중개 메서드

어떤 클래스의 인터페이스를 보니까 그 안의 절반도 넘는 메서드가 기능을 다른 클래스에 위임하고 있다면, 조만간 과잉 중개 메서드 제거를 실시해서 원리가 구현된 객체에 직접 접근하자.

지나친 관여

간혹 클래스끼리 관계가 지나치게 밀접한 나머지 서로의 은밀한 부분을 알아내느라 과도한 시간을 낭비하게 될 때가 있다.

메서드 이동과 필드 이동을 통해 각 클래스를 분리해서 지나친 관여를 줄여야 한다.

인터페이스가 다른 대용 클래스

기능은 같은데 시그니처가 다른 메서드에는 메서드명 변경을 실시해야 한다.

미흡한 라이브러리 클래스

라이브러리는 재사용 한다는 점에서 유용하다.
단순 정렬 알고리즘도 잊어버릴 정도로 라이브러리 클래스에 의존한다.

하지만 개발자가 라이브러리 클래스를 변경할 수 없는 경우가 종종 있다. 클래스 라이브러리를 만든 사람이라고 모든 것을 다 아는 것은 아니다.

데이터 클래스

데이터 클래스는 필드와 getter/setter 메서드만 들어 있는 클래스다. 이 클래스는 데이터 보관만 담당하며, 필드가 public이라면 즉시 필드 캡슐화를 해야 한다.

방치된 상속물

하위클래스가 부모클래스로부터 상속 받은 메서드 혹은 데이터를 더이상 쓰지 않거나 필요없을땐 메서드, 필드 하향 기법을 사용하자.

상속을 제거하고 상속을 위임으로 대체한다.

불필요한 주석

주석이 다 필요없다거나 주석을 작성히지 말라는 얘긴 아니다. 오히려 사용하면 좋다.

다만 불필요한 주석의 경우를 말하는 것이고, 코드 구간의 기능을 설명할 주석이 필요할 때는 메서드 추출을 실시해야 한다.

주석을 먼저 넣는 것보다는 먼저 코드를 리팩토링해서 주석을 없앨 수 있게 만들어보자.

주석은 무슨 작업을 해야 좋을지 모를 때만 넣는 것이 좋다.

느낀 점

코드에서 어떤 것들이 구린 내가 나는지 대략 알 수 있었다. 코드에 구린내(?)가 난다는 이론들을 쭉 봤는데 절대 외우지 말고, "아, 이런 것들이 있겠구나." 알고 실무에서 실전으로 해보면서 경험을 쌓는 것이 훨씬 중요하겠다는 생각이 들었다.

이 중에서 실무에서 이미 당연하게 적용하고 있는 것도 있을 것이고, 그렇지 않은 것도 있을 것이다.

중요한 것은 리팩토링을 항상 인지하면서 소프트웨어를 개선해나가는 것이라고 생각한다.

References

댓글