코드의 구린내

(Bad Smells in Code)

2019.09.11

박영준

학습 내용 및 목표

  • 리팩토링을 어떨 때 시작하고 어떨 때 그만두어야 하는지 파단 기준을 제시
  • 리팩토링으로 해결 될 가능성이 있는 문제들을 설명

중복 코드

  • 구린내의 제왕
  • 똑같은 코드가 두 군데 이상 있을 때, 그 부분을 하나로 통일하면 프로그램이 개선된다.

Duplicated Code

장황한 메서드

  • 최적의 상태로 장수하는 프로그램은 메서드 길이가 짧다.
  • 짧은 메서드를 이해하기 쉽게 하려면 메서드명을 잘 정해야 한다.
  • 메서드는 훨씬 과감하게 쪼개야 한다.
  • 메서드명은 기능 수행 방식이 아니라 목적(기능 자체)을 나타내는 이름으로 정한다.

Long Method

방대한 클래스

  • 클래스에 인스턴스 변수가 너무 많으면 중복 코드가 반드시 존재하기 마련
  • 클래스 추출 기법을 사용하여 인스턴스 변수를 하나로 묶자
  • 클래스 자체에서 중복 코드를 없애야 한다.
  • GUI를 나타내는 클래스라면 데이터와 기능을 서로 다른 도메인 객체로 옮겨야 할 수도 있다.

Large Class

과다한 매개변수

  • 메서드에 필요한 모든 데이터를 전달하는 게 아니라 그 모든 데이터를 가져올 수 있는 메서드만 전달하면 된다.
  • 메서드가 필요로 하는 각종 데이터는 그 메서드가 속한 클래스에 들어있다.
  • 매개변수 세트가 길면 서로 일관성이 없어지고 사용이 불편해진다.
  • 데이터가 필요해 질 때 마다 계속 수정해주어야 한다.
  • 객체를 넘기는 것 만으로 웬만한 수정은 할 필요가 없어진다.

Long Parameter List

수정의 산발

  • 프로그램을 수정할 때 개발자는 시스템의 분명한 위치로 곧장 가서 수정할 수 있어야 한다.
  • 수정의 산발은 한 클래스가 다양한 원인 때문에 자주 수정될 때 발생한다.

Divergent Change

기능의 산재

  • 수정 할 때마다 여러 클래스에서 수많은 자잘한 부분을 고쳐야 한다면 이 문제를 의심
  • 수정할 부분이 여기저기 있으면 찾기도 힘들고 꼭 수정해야 하는 부분을 놓치기 쉽다.
  • 수정의 산발 : 한 클래스에 여러 수정이 발생하는 문제
  • 기능의 산재 : 하나의 수정으로 여러 클래스가 바뀌게 되는 문제
  • 수정과 클래스가 일대일 대응되도록 깔끔히 정리해야 한다. (SRP)

Shotgun Surgery

잘못된 소속

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

 Feature Envy

데이터 뭉치

  • 데이터 항목은 몰려다니는 습성이 있다.
  • 동일한 3~4개의 데이터 항목이 여러 위치에 몰려있는 경우가 많다.
  • 몰려있는 데이터 뭉치는 객체로 만들어야 한다.
  • 객체로 전환하고 나면 전체적 성능이 개선될 여지도 있다.

 Data Clumps

강박적 기본 타입 사용

  • 돈 관련 클래스나 전화번호, 우편번호 같은 특수 문자열 클래스 등 사소한 작업에 작은 객체를 잘 사용하지 않으려는 경향이 있다.
  • 우물 안 개구리를 벗어나려면 데이터 값을 객체로 전환을 실시해야 한다.

 Primitive Obsession

Switch 문

  • 객체지향 코드의 확연한 특징 중 하나는 switch-case 문이 비교적 적게 사용된다는 점이다.
  • switch 문의 단점은 반드시 중복이 생긴다는 점이다.
  • 객체지향 개념중 하나인 다형성을 이용해서 문제를 해결할 수 있다.
  • 대부분의 switch 문은 고민할 필요 없이 재정의로 바꿔야 한다.

 Switch Statements

평행 상속 계층

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

 Parallel Inheritance Hierarchies

직무유기 클래스

  • 하나의 클래스를 작성할 때 마다 유지관리와 이해하기 위한 비용이 추가된다.
  • 비용만큼의 기능을 수행하지 못하는 비효율적인 클래스는 없애야 한다.
  • 리팩토링 실시로 인해 기능이 축소된 클래스, 쓸모 없어진 클래스 등이 이에 해당한다.

 Lazy Class

막연한 범용 코드

  • 조만간 기능이 필요할 것 같아 아직은 필요 없는 기능을 넣으면 막연한 범용 코드의 구린내가 풍김
  • 코드를 알아보고 유지보수하기가 더 어려워진다.
  • 코드가 전부 활용된다면 문제가 없지만, 그렇지 않다면 장애물에 불과하니 제거해야 한다.
  • 메서드나 클래스가 오직 테스트 코드에서만 사용된다면 유력한 용의자!
  • 그 클래스와 테스트 케이스를 모두 삭제하자.

 Speculative Generality

임시 필드

  • 개발자는 객체가 그 안에 들어있는 모든 변수를 이용 할것으로 생각하기 마련이므로, 특정 상황에서만 사용되는 임시 변수 코드는 파악하기 힘들다.
  • 사용되지 않을 것 같은 변수가 왜 거기 있는지 이해하려다 보면 스트레스를 받는다.
  • 임시 필드의 구린내는 복잡한 알고리즘에 여러 변수를 사용해야 할 때 풍긴다.

 Temporary Field

메시지 체인

  • 메시지 체인은 한 객체1 -> 객체2 -> 객체3.. 처럼 연쇄적 요청이 발생하는 문제
  • 그 사이의 관계들에 수정이 발생할 때 마다 클라이언트도 수정해야 한다.

 Message Chains

과잉 중개 메서드

  • 지나친 캡슐화와 위임도 문제가 된다.
  • 어떤 클래스의 절반이 넘는 메서드가 기능을 다른 클래스에 위임하고 있다면 과잉 중개 메서드 제거를 실시해야 한다.

 Middle Man

지나친 관여

  • 클래스 끼리 관계가 지나치게 밀접하면 서로의 private한 부분을 알아내느라 과도한 시간을 낭비하게 된다.
  • 클래스는 엄격하고 절제된 규칙을 따라야 한다.
  • 상속으로 인해 지나친 관여가 발생하는 경우가 많다.

 Inappropriate Intimacy

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

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

 Alternative Classes with Different Interfaces

미흡한 라이브러리 클래스

  • 라이브러리의 설계를 파악하는 것은 거의 불가능하다.
  • 라이브러리 클래스를 원하는 기능을 수행하게 수정하는 것은 보통 불가능하다.
  • 라이브러리 클래스에 넣어야 할 메서드가 두 개뿐이라면 외래 클레스에 메서드 추가 기법을 실시한다.
  • 부가 기능이 많을 때는 국소적 상속 확장 클래스 사용 기법을 실시한다.

 Incomplete Library Class

데이터 클래스

  • 데이터 클래스는 필드와 읽기/쓰기 메서드만 있는 클래스.
  • 만약 public 필드가 있다면 즉시 캡슐화 기법을 실시해야 한다.
  • 컬렉션 필드가 적절히 캡슐화 되어있지 않다면 컬렉션 캡슐화 기법을 적용한다.

 Data Class

방치된 상속물

  • 상속받은 데이터나 메서드가 하위 클래스에서 더이상 쓰이지 않으면 상속물을 전부 받아 그 중에서 필요한 것 외엔 방치해버리는 문제가 생긴다.
  • 하위 클래스가 기능은 재사용 하지만 상위 클래스의 인터페이스를 지원하진 않을 때 훨씬 심하다.

 Refused Bequest

불필요한 주석

  • 주석은 구린내를 감추는 용도로 쓰일 때가 많다.
  • 리팩토링을 마친 후에는 그 주석들이 불필요한 것이었다는 사실을 알게된다.
  • 주석은 무슨 작업을 해야 좋을지 모를 때만 넣는 것이 좋다.
  • 어떤 코드를 넣은 이유를 메모해 좋을 경우에도 주석이 적절하다.
  • 잊기 쉬운 사항을 주석으로 작성해 놓으면 나중에 수정하게 될 사람들이 보고 쉽게 이해할 수 있다.
  • 주석을 넣어야겠다는 생각이 들 땐 먼저 코드를 리팩토링 해서 주석을 없앨 수 있게 만들자

 Comments

코드의 구린내

By Young Jun Park (박영준)

코드의 구린내

  • 173