본문 바로가기

Develop/소프트웨어 설계

[리팩토링] 제3장 코드의 구린내

반응형

리팩토링 코드의 구린내

리팩토링 제3장 코드의 구린내

리팩토링을 언제 적용할지 파악하는 능력은 리팩토링 기법을 적용하는 방법만큼 중요하다.

중복코드 Duplicated Code

중복코드는 반드시 리팩토링을 통해 개선할 필요가 있다. 똑같은 코드 구조가 두 군데 이상 있을 때는 그 부분을 하나로 통일해서 개선한다. 중복된 코드는 코드를 관리하기 어렵게 한다.

장황한 메서드 Long Method

메서드가 길면 길수록 코드를 이해하기 이려워지고, 메서드를 재사용하기 힘들어 진다. 리팩토링으로 코드의 재사용성을 높이고 메서드를 이해하기 쉽도록 나누어서 개선한다. 특히 기능 설명이 주석으로 처리된 코드 구간을 메서드로 만들 수 있다면 좋다.

방대한 클래스 Large Class

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

과다한 매개변수 Long Parameter List

매개변수 세트가 길면 서로 일관성이 없어지거나 사용이 불편해지고, 더 많은 데이터가 필요해질 때마다 계속 수정해야 하기 때문에 그 매개변수들을 이해하기 힘들다.

수정의 산발 Divergent Change

개발자는 소프트웨어를 수정하기 쉽게 구성한다. 수정할 때 개발자는 시스템의 분명한 위치로 곧장 가서 수정할 수 있어야 한다. 수정의 산발은 한 클래스가 다양한 원인 때문에 다양한 방식으로 자주 수정될 때 일어난다.

기능의 산재 Shotgun Surgery

수정할 때마다 여러 클래스에서 수많은 자잘한 부분을 고쳐야 한다면 이 문제를 의심할 수 있다. 수정할 부분이 여기저기에 있다면 찾기도 힘들 뿐더러 꼭 수정해야 하는 부분을 놓치기 쉽다.

수정의 산발은 한 클래스에 여러 수정이 발생하는 문제이고, 기능의 산재는 하나의 수정으로 여러 클래스가 바뀌게 되는 문제다. 둘 중 어느 것이든 수정과 클래스가 일대일 대응되게 깔끔히 정리해야 한다.

잘못된 소속 Feature Envy

객체의 핵심은 데이터와 그 데이터에 사용되는 프로세스를 한 데 묶는 기술이라는 점이다. 전통적으로 어떤 메서드가 자신이 속하지 않은 클래스에 더 많이 접근한다면 잘못 짜여진 것이다. 잘못 소속된 메서드가 제일 흔히 접근하는 대상은 데이터다. 그러므로 잘못 소속된 메서드를 자주 접근하는 데이터가 있는 클래스로 옮겨야 한다.

데이터 뭉치 Data Clumps

데이터는 관련된 데이터끼리 뭉쳐있는 경우가 많다. 몰려있는 데이터 뭉치는 데이터 객체로 만들어서 관리해야 한다.

강박적 기본 타입 사용 Primitive Obsession

언어에 내장된 기본 타입과 구별하기 힘든 작은 클래스를 사용하여 기본 타입을 관리할 수 있다. 예를 들어 다양한 날짜 형식을 문자 기본 형식으로 관리하지 않고 객체로 관리해서 다양한 입력 형식에 대응할 수 있게 된다.

Switch 문 Switch Statements

대부분의 switch 문은 고민할 필요 없이 재정의로 바꿔야 한다. 객체 지향 코드의 확연한 특징 중 하나는 switch-case 문이 비교적 적게 사용된다는 점이다. switch 문의 단점은 반드시 중복이 생긴다는 점이다. 같은 switch 문이 소스코드 곳곳에 사용된다.

평행 상속 계층 Parallel Inheritance Hierarchines

평행 상속 계층은 사실 _기능의 산재_의 특수한 상황이다. 이 문제점이 있으면 한 클래스의 하위 클래스를 만들 때마다 매번 다른 클래스의 하위 클래스도 만들어야 한다. 서로 다른 두 상속 계층의 클래스명 접두어가 같으면 이 문제를 의심할 수 있다.

직무유기 클래스 Lazy Class

하나의 클래스를 작성할 때마다 유지 관리와 이해하기 위한 비용이 추가된다. 따라서 비용만큼의 기능을 수행하지 못하는 비효율적인 클래스는 없애야 한다. 비용 대비 효율성이 저하된 클래스가 직무유기 클래스에 해당한다. 대체로 계층 병합 또는 클래스 직접 삽입, 모듈 내용 직접 삽입 기법을 적용해 리팩토링한다.

막연한 범용 코드 Speculative Generality

막연한 생각에 아직은 필요없는 기능을 수행하고자 만든 범용 코드는 대체로 코드를 알아보고 유지보수하기 더 어렵게 한다. 그러므로 별다른 기능이 없는 클래스나 모듈이 있다면 계층 병합을 실시해야 하고, 불필요한 위임을 제거하려면 클래스 내용 직접 삽입을 실시해야 한다.

임시 필드 Temporary Field

어떤 객체 안에 인스턴스 변수가 특정 상황에서만 할당되어 사용되는 경우가 종종 있다. 개발자는 객체가 안에 있는 모든 변수를 이용하리라 생각하기 마련이므로 임시 변수는 코드를 파악하는데 방해가 된다. 사용되지 않을 것 같은 변수가 왜 이 객체 내에 있는지 이해하려는 스트레스를 받을 수 밖에 없다. 대체로 클래스 추출을 실시해서 임시 변수와 관련된 코드를 따로 정리해야 한다.

메시지 체인 Message Chains

메시지 체인은 객체를 연속적으로 요청하여 발생하는 문제를 뜻한다. 너무 많은 요청으로 인해 객체 간의 관계에 수정이 발생할 때마다 요청 체제 전부를 수정해야 한다. 이때는 대리 객체 은폐를 실시해야 한다. 결과 객체가 어느 대상에 사용되는지를 알아내는 방법을 통해 객체가 사용되는 코드 부분을 메서드 추출을 통해 별도의 메서드로 빼낸 후 메서드 이동을 실시해서 체인 아래로 밀어낼 수 있는지 여부를 검사해야 한다.

과잉 중개 메서드 Middle Man

객체의 주요 특징 하나가지는 바로 캡슐화다. 켭슐화를 할 때는 대개 위임이 수반된다. 문제는 위임이 지나치면 문제가 된다. 어떤 클래스의 인터페이스를 보니까 그 안의 절반도 넘는 메서드가 기능을 다른 클래스에 위임하고 있다면, 조만간 과잉 중개 메서드 제거를 실시해서 원리가 구현된 객체에 직접 접근하자. 메서드 내용 직접 삽입 기법이나 위임을 상속으로 전환 기법을 실시해서 중개 메서드를 실제 객체의 하위 클래스로 전환하면 된다.

지나친 관여 Inappropriate Intimacy

클래스는 엄격하고 절제된 규칙을 따라 교제해야 한다. 간혹 클래스끼리 지나치게 밀접한 나머지 private 필드까지 알아내는데 과도한 노력을 할 때가 있다. 메서드 이동 기법과 필드 이동 기법을 실시해서 각 클래스를 분리해, 지나친 관여를 줄여야 한다. 만약 행당 클래스들이 공통으로 필요한 부분이 있다면 클래스 추출 기법을 통해 별도의 안전한 클래스로 빼면 된다.

인터페이스가 다른 대용 클래스 Alternative Classes with Different Interfaces

기능은 같은데 시그니처가 다른 메서드에는 메서드명 변경 기법을 실시해야 한다. 프로토콜이 같아질 때가지 메서드 이동을 실시해서 기능을 해당 클래스로 옮겨야 한다.

미흡한 라이브러리 클래스 Incomplete Library Class

라이브러리 클래스 제작자라도 모든 걸 알 수는 없다. 자신이 직접 대부분의 라이브러리 클래스를 완성하지 않는 이상 설계를 파악한다는 것이 거의 불가능하다. 그러므로 라이브러리를 보충하기 위해 외래 클래스에 메서드 추가 기법이나 국소적 상속확장 클래스 사용 기법을 실시해보자.

데이터 클래스 Data Class

데이터 클래스는 필드와 필드의 읽기와 쓰기 메서드만 들어 있는 클래스다. 오로지 데이터 보관만 담당하며 거의 대부분의 구체적인 데이터 조작은 다른 클래스가 수행한다. 그러므로 필드 캡슐화 기법을 활용하여 필드를 제어를 안전하게 할 수 있도록 변경하도록 한다. 데이터 클래스를 이렇게 점차 성장시켜 어느 정도의 책임을 감당할 수 있는 클래스로 만들자.

방치된 상속물 Refused Bequest

하위 클래스는 부모 클래스의 메서드와 데이터를 상속받는다. 하지만 하위 클래스는 상속물을 전부 받아 그 중에 필요한 것 외엔 방치해버리는 경우가 있다. 방치된 상속물의 구린내는 하위클래스가 기능은 재사용하지만 상위클래스의 인터페이스를 지원하지 않을 때 리팩토링이 필요하다. 이럴 경우 새 대등 클래스를 작성하고 메서드 하향 기법과 필드 하향 기법을 실시해서 사용되지 않는 모든 메서드를 형제 클래스에 몰아넣어야 한다. 이렇게 하면 상위 클래스에는 공통 코드만 들어 있게 된다.

불필요한 주석 Comments

주석은 코드를 이해하는데 중요한 정보를 제공한다. 하지만 주석이 코드의 구린내를 감춰주는 탈취제 용도로 쓰일 때가 많기 때문에 주의해야 한다. 대체로 리팩토링을 적절하게 적용하고 나면 주석이 불필요해지고 코드 자체로 깔끔한 설명이 된다. 주석은 무슨 작업을 해야 좋을지 모를 때만 넣는 것이 좋다. 주석을 넣으면 돌아가는 원리를 적어둘 수도 있고 확실치 않은 부분을 표시할 수도 있다. 어떤 코드를 넣은 이유를 메모해 놓을 경우에도 주석을 넣는 것이 적절하다. 이런 정보나 특히 잊기 쉬운 사항을 주석으로 작성해 놓으면 나중에 수정하게 될 개발자가 보고 쉽게 이해할 수 있다.

반응형