코드 속의 냄새
12 Apr 2021 | DesignPattern Refactoring코드 속의 냄새
코드 속의 악취, 이것이 왜 생기는가?
- 중복된 코드가 있다.
- 코드의 의미가 명확하지 않다.
- 코드가 복잡하다.
물론, 코드를 만드는 데 확일화된 규칙도 없고, 무엇이 잘못된 것 인지 알 방법도 잘 없다.
아래 표를 참조해서, 무슨 문제가 있는지 확인해보자.
중복된 코드
가장 흔한 악취이다. 명백한 중복과 잠재정 중복이 있는데. 명백한 중복은 말 그대로의 의미고 잠재적 중복은 구조나 처리 단계에 있어 본질적으로는 같은 부분이 존재한다는 것을 뜻한다.
긴 메서드
두 개의 긴 메서드는 같은 로직을 공유할 가능성이 있다.
따라서 긴 메서드를 조각조각 나누면 로직을 공유할 수 있는 방법이 보일 수도 있다.
짧은 메서드의 적정 길이는 어느 정도일까?
메서드의 길이를 10줄 이하로 하고, 대부분의 메서드는 1~5줄 정도로 하는 것이 좋다고 생각한다.
연쇄 호출을 걱정하는 프로그래머들이 있을텐데, 이 것은 프로파일러를 통해 확인한다면 아무런 성능 감소가 없고 오히려 좋다는 것을 알 수 있다.
복잡한 조건문
기존엔 괜찮은 조건문이였다가, 기능을 추가하다보니 조건 로직이 갑자기 복잡하게 될 수가 있다.
만약 조건 로직이 여러 변형중 하나의 분기를 위한 것이라면, Replace Conditional Logic with Strategy을 통하여 적용할 수 있다.
만약 조건 로직이 그 클래스의 핵심 동작 외의 특별한 경우에 대한 몇몇 동작을 제어하는 것이라면, Move Embellishment to Decorator을 사용할 수 있다.
객체의 상태 전이를 제어하는 복잡한 조건식의 경우, Replace State-Altering Conditionals with State을 사용한다.
마지막으로 Null을 처리하기 위해 사용하는 경우가 많은데, 이 땐 Introduce Null Object을 사용한다.
기본 타입에 대한 강박관념
정수, 문자열, 배열 등의 기본 타입을 사용하는 데에 강박관념을 가지고 있을 때다.
이는 보통 높은 수준의 추상화가 어떻게 코드를 명확하고 단순하게 만드는지 보지 못했을 때 나타난다.
만약 클래스에서 로직의 흐름을 제어하는 변수가 기본 타입이고 그에 대한 타입 안정성이 보장되어 있지 않으면, Replace Type Code with Class을 적용할 수 있다.
결과로 나온 코드는 안정성이 보장되고, 새로운 동작을 쉽게 추가할 수 있게 된다.
만약 객체의 상태 전이가 기본 타입의 값을 기준으로 한 복잡한 조건 로직을 통해 제어된다면, Replace Implicit Tree with Composite를 사용할 수 있다. 그 결과 각 상태를 나타내는 여러가지 클래스와 단순화된 상태 전이 로직이 나온다.
만약 복잡한 조건 로직이 기본 타입의 값에 따라 실행시킬 알고리즘을 선택한다면, Replace Conditional Logic with Strategy을 적용한다.
만약 문자열과 같은 기본 타입을 통해 묵시적으로 트리 구조를 생성하고 있다면, 코드는 작업하기 어렵고 오류를 범하기 쉬우며 중복도 많아진다. 이 때 Replace Implicit Tree with Composite를 사용한다면 해결할 수 있다.
만약 어떤 클래스에 입력되는 기본 타입 값의 수많은 조합을 지원하기 위한 많은 메서드가 있다면 이것은 묵시적인 언어가 있음을 의미한다. 이럴 때 Replace Implicit Tree with Composite를 사용한다.
만약 클래스에 핵심이 아닌 추가 기능을 덧붙이기 위해 사용되는 기본 타입 값이 존재한다면, Move Embellishment to Decorator를 적용한다.
마지막으로 클래스가 너무 원시적이라 클라이언트에서 쉽게 사용할 수 없고, 다루기 까다로운 컴포짓을 사용하는 경우 Encapsulate Composite with Builder을 사용하여 해결한다.
추잡한 노출
정보 은폐 가 부족할 때 나는 악취이다.
생성자를 Public으로 선언하지 않아도 되고, 어떤 클래스는 공동 인터페이스를 통해서만 참조돼야 하거나, 팩토리를 통해서 선언하도록 하면 된다.
문어발 솔루션
어떤 기능을 수행하는데 사용되는 코드나 데이터가 여러 클래스에 걸쳐있을 때 나는 악취이다.
인터페이스가 서로 다른 대체 클래스
어떤 두 클래스의 인터페이스는 다르지만, 클래스가 서로 상당히 비슷할 때 발생하는 악취이다. 공통 인터페이스를 공유하도록 만드는 것이 목표이다.
게으른 클래스
자신의 존재 비용을 감당할 만큼 충분한 일을 하지 않는 클래스를 의미한다.
보통 싱글톤 클래스에서 많이 관찰되며, Inline Singletone으로 해결한다.
거대한 클래스
한 클래스에 지나치게 많은 인스턴스가 있는 것은, 클래스 하나가 너무 많은 일을 하려 함을 나타내는 징조이다.
다양한 요청에 따라 서로 다른 동작을 수행하는 클래스에는 Replace Conditional Dispatcher with Command을 적용하여 각 동작을 별도의 커맨드 클래스로 뽑아내면 그 크기를 상당히 줄일 수 있다.
상태 전이 코드로 가득 찬 거대한 클래스의 크기를 줄이려면, Replace State-Altering Conditionals with State을 이용하여 각 상태에 대한 처리를 별도의 스테이트 클래스로 분리하면 된다.
Replace Implicit Language with Interpreter은 다양한 입력 조건을 처리하느라 수많은 코드가 잠재적으로 중복되어 있는 거대한 클래스를 인터프리터로 대체해 간단하게 만드는 방법이다.
Swtich 문
Switch문이 나쁜것은 아니지만, 설계를 복잡하게 만들거나 필요 이상으로 융퉁성 없게 만들 때가 있다. 이럴 경우 switch문을 좀 더 객체지향적인 다형성을 이용하도록 리팩터링하는 것이 좋다.
Replace Conditional Dispatcher with Command은 거대한 switch문을 커맨드 객체(조건 로직에 의존하지 않고 런타임에 검색되어 호출되는)의 집합으로 쪼개는 방법이다.
Move Accumulation to Visitor으로 서로 다른 인터페이스를 갖는 클래스의 인스턴스들로부터 데이터를 얻기 위해 switch문을 사용하는 예제를 설명한다.
조합의 폭발적 증가
중복의 잠재적 형태로, 다양한 종류의 데이터나 객체를 이용하지만 결국 하는 일은 동일할 때 발생한다.
예를 들어 어떤 클래스에 쿼리 수행을 위한 많은 메서드가 있다고 하자.
메서드는 각 특정 조건과 데이터를 이용해 쿼리를 수행한다. 지원해야 하는 특수한 쿼리가 많을수록, 더 많은 쿼리 메서드를 만들어야 한다. 따라서 쿼리 수행을 위한 다양한 방법을 처리하기 위해 메서드의 수가 폭발적으로 증가한다. 즉, 묵시적으로 일종의 쿼리 언어가 있는 것과 마찬가지다. Replace Implicit Language with Interpreter을 이용해 이러한 조합의 폭발적 증가 냄새를 제거할 수 있다.
괴짜 솔루션
어떤 문제가 시스템 전체에서 한 가지 방법으로 해결되고 있는데, 같은 문제가 특정 부분에서만 다른 부분으로 해결된다면, 이 다른 해결방법은 괴짜 또는 비 일관적인 솔루션이다. 이런 중복을 제거하려면 먼저 마음에 드는 솔루션을 결정한 뒤 Substitute Algorithm 리팩터링을 통해 솔루션을 일관적으로 만든다.
또한 보통 비슷한 부류의 클래스를 사용할 때, 일부 클래스의 인터페이스가 나머지와 달라 문제가 발생할 때가 있다. 이 때 동일한 인터페이스를 만들기 위해 Unify interfaces with Adapter 방식을 사용한다.