본문 바로가기

Software Design/Documents

Refactoring: Code smells

Refactoring may be the single most important technical factor in achieving agility. (Jim Highsmith, Agile Software Development Ecosystems)
... 리팩토링이 중요하다는 소리인거 같은데 민첩성을 얻는다는게 무슨 의민지 와닿지 않는다..
Refactoring is like continuing repair of a living system. The goal is to stay within reasonable operating limits with limited continual damage. By staying within these limits you keep costs low, because costs relate nonlinearly to the amount of repair necessary. It is like maintaining your house. You are best off (financially) if you continuously maintain rather than do large lump repairs. (Dirk Riehle)
리팩토링은 생활 시스템의 지속적인 보수와 같다. 목표는 한정된 지속적인 손상과 합리적인 작동 한도 내에서 유지하는 것이다. 비용은 필요한 수리 양과 비선형적으로 관련되기 때문에 이러한 한계 내에서 유지함으로써 비용을 낮게 유지할 수 있다. 그것은 당신의 집을 유지보수하는 것과 유사하다. 대량 수리를 하는것보다 지속적으로 유지보수를 하는 것이 (재정적으로) 가장 좋다.
Refactoring keeps you ready for change by keeping you comfortable with changing your code. (Jim Highsmith)

리팩토링이란 무엇인가

Duplication and needless complexity are removed from the code during each coding session, even when this requires modifying components that are already "complete." Automated unit tests are used to verify every change. (Lisa Crispin & Tip House, Testing Extreme Programming)
중복 및 불필요한 복잡성은 이미 "완전한" 구성요소를 수정해야 하는 경우에도 각 코딩 세션동안 코드에서 제거된다. 자동화된 단위 테스트는 모든 변경사항을 확인하기 위해 사용된다.
Beck's definition: A change to the system that leaves its behavior unchanged, but enhances some nonfunctional quality - simplicity, flexibility, understandability, performance
동작을 변경하지 않고 일부 비기능적 품질 - 단순함, 유연성, 이해성, 성능 - 을 향상시키는 시스템 변경
Fowler's definition: A change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior
소프트웨어의 내부 구조를 이해하기 쉽고 관찰 가능한 동작을 변경하지 않고 수정하는데 더 저렴함

즉, 리팩토링을 통해 

  • 관찰 가능한 동작을 변경하지 않고
  • 중복과 불필요한 복잡성을 제거하고
  • 소프트웨어 품질을 향상시키고
  • 코드를 간단하고 이해하기 쉽게 만들고
  • 융통성 있는 코드로 
  • 수정에 용이한 코드로 만든다.

리팩토링의 중요성

  • 설계 검증
  • 코드 최적화(To clean up messy code)
  • 코드 간결화
  • 높은 가독성과 이해성
  • 잠재적 오류 제거
  • 디버깅 시간 단축

리팩토링은 1. 모든 테스트 통과 → 2. 코드 smell 찾기 → 3. 코드 간결화 방법 선정 → 4. 간결화 → 1. 모든 테스트 통과 의 반복을 통해 수행된다.

다만, 테스트에 통과하지 못했거나 또는 코드 수정이 필요할 때는 리팩토링을 수행하지 않는다.

리팩토링시 주의사항

리팩터링으로 인해 코드를 끊임없이 만지게 되고 완벽하게 만들려고 하는 경향이 생길 수 있다.

테스트에 통과하지 못했거나 또는 코드 수정이 필요한 경우에는 리팩토링을 수행하지 않는다. 이는 테스트가 작동하지 않을 때 코드를 리팩토링하거나 어플리케이션이 작동하지 않을 때 테스트를 진행하면 잠재적으로 위험한 상황이 발생할 수 있기 때문이다. 또, 데이터베이스는 리팩토링하기 어렵다. 게시되어진 인터페이스를 리팩토링하면 해당 인터페이스를 사용하는 코드에 문제가 발생할 수 있다.

 

 

리팩토링에 대해 어느정도 알아보았다... 근데 여기에서 말하는 코드 스멜이란 무엇일까?

코드스멜

단어 느낌 그대로, 코드에 문제가 생길 수 있는 특징이다. 이는 최대한 줄이고 해결하는 것이 좋고, Production code와 Test code 모두에서 발견될 수 있다.

아래는 Code smell의 예시들을 나열한 표이다.

 

1. Code smells (Fowler)

Duplicated Code

여러 곳에서 코드가 반복되는 것

방법: 메소드나 클래스로 추출

Data Class

데이터를 보유하는 유일한 목적이 있는 클래스

클래스에 인스턴스, getter, setter가 포함되어 있다

방법: 메소드를 이동시키거나 캡슐화

Data Clumps

일반적으로 여러 곳에서 함께 전달되는 변수 집합

방법: 클래스 추출, 파라미터 객체로 선언

Inappropriate Intimacy

다른 클래스에서 private이여야 하는 항목을 많이 사용하는 클래스

방법: 메소드나 필드의 이동하거나 양방향을 단방향으로 변경, 상속을 위임으로 변경

Large Class

인스턴스 변수가 너무 많거나 코드가 너무 많은 클래스

방법: 클래스나 서브클래스 또는 인터페이스로 추출하거나 데이터를 객체로 대체

Lazy Class

유지보수를 정당화할만큼 충분한 작업을 하지 않고 있는 클래스

방법: 클래스로 처리하거나 계층구조 제거

Long Method

statements, loops, variables가 너무 많은 메소드

 Astels는 10줄 초과하면 많다고 정의함

방법: 메소드로 추출 또는 Temp는 쿼리문으로 메소드는 메소드 객체로 대체한다. 혹은 조건문을 분해한다.

Long Parameter List

메소드로 많은 파라미터가 유입되는 것

방법: 파라미터를 메소드로 대체하거나 파라미터 객체를 선언한다.

Middle Man

하나의 클래스가 많은 요청을 다른 클래스에 위임하는 것

방법: Middle Man을 숨기거나 메소드로 처리한다.

Shotgun Surgery

하나의 변경을 수행하려면 여러 장소에서 코드를 변경해야 하는 것

방법: 메소드 또는 필드로 이동시키거나 클래스로 처리

Switch Statements

다형성이 더 잘 작동하는 스위치 문 사용하는 것

방법: 조건부 다형성으로 대체하거나 유형 코드를 서브 클래스나 State/Strategy로 대체한다. 또는 파라미터를 호출의 단순화한다.

Speculative Generality

나중에 필요할 경우를 대비하여 코드를 일반화하는 것 또는 사용하지 않는 훅과 특별한 경우로 인해 코드를 더 이해하기 어려워지는 경우

방법: 계층을 제거하거나 클래스로 묶는다.

파라미터를 제거하거나 메소드의 이름을 재정의한다.

Alternative Classes with Different Interfaces

동일한 작업을 수행하지만 특징이 다른 메소드

방법: 메소드 이름을 달리하거나 이동시킨다.

Comments

Temporary Field

특정 상황에서만 설정된 인스턴스 변수 또는 중간 결과를 유지하는 데 사용되는 필드

방법: 클래스로 추출하거나 널 객체로 선언한다.

Divergent Change

클래스가 다른 이유로 서로 다른 방식으로 변경되었다. (예: 새로운 데이터베이스의 경우 세가지 메소드가 변경되고, 새로운 금융상품의 경우 네 가지 다른 메소드가 변경된다.)

방법: 클래스로 추출한다.

Feature Envy

있는 클래스보다 다른 클래스를 더 많이 사용하는 메소드

방법: 메소드나 필드를 이동시키거나 메소드를 추출한다.

Incomplete Library Class

필요한 메소드를 제공하지 않는 라이브러리

방법: 외부 메소드나 로컬 extension을 참조한다.

Message Chains

어떤 객체는 다른 객체에 무엇인가를 요구하고, 그 객체는 또 다른 객체에 요청하고, 또 다른 객체는 또또 다른 객체에 요청하는 일

방법: Delegate를 숨긴다.

Parallel Inheritance Hierarchies

한 클래스의 하위 클래스를 만들 때마다 다른 클래스의 하위 클래스를 만드는 것

방법: 메소드나 필드를 이동시킨다.

Primitive Obsession

클래스 또는 레코드 유형이 더 잘 작동하도록 원시 데이터 유형을 사용하는 것이나 소규모 작업을 더 잘 처리하기 위해 소규모 클래스를 사용하는 것

방법: 객체로 데이터 값이나 배열을 대체한다. 클래스로 추출한다. 파라미터 객체를 선언한다. 유형 코드를 클래스나 서브클래스, State/Strategy로 대체한다.

Refused Bequest

슈퍼클래스로부터 물려받은 것을 사용하지 않는 것

방법: 상속을 Delegation으로 대체한다.

 

2. Code Smell (van Deursen, et al.)

Sensitive Equality

불필요한 것에 영향을 받는 동등성 검사(예: 사소한 구두점 변경에 영향을 받을 수 있는 toString 사용)

방법: Equality method를 작성한다.

Lazy Test

여러 개의 테스트 메소드가 같은 특징을 가진 메소드를 체크하는 것

방법: 메소드를 추출한다.

Assertion Roulette

설명이 없는 테스트 메소드의 다중 assertions

방법: 설명을 추가한다.

Mystery Guest

외부 자원을 사용한 테스트

방법: 자원을 작성하거나 외부 자원을 설정한다.

Resource Optimism

외부 자원의 존재와 상태에 대해 낙관적인 가정을 하는 테스트

방법: 외부 자원을 설정한다.

Indirect Testing

테스트 중인 주체가 아닌 테스트 클래스 테스트 객체

방법: 메소드를 추출하거나 이동시킨다.

Test Run War

한 번에 한 사람만이 자원 접근을 위해 경쟁하는 것

방법: 자원을 유일무이하게 만든다.

Test Code Duplication

여러개의 테스트에서 반복되는 코드 또는 코드 중복

방법: 메소드로 추출한다.

General Fixture

테스트 클래스의 setUp 루틴에는 일부 테스트 케이스에서만 사용되는 요소가 포함되어 있는 것

방법: 메소드를 추출하거나 안에 집어넣는다. 혹은 클래스로 추출한다.

Eager Test

테스트 중인 객체의 메소드를 확인하는 것

방법: 메소드로 추출한다.

For Testers Only

 

 

3. Code Smell (Astels)

Inappropriate Assertions

테스트에 가장 적합하지 않은 xUnit assert 메소드를 사용한 테스트 메소드

방법: assert를 대체한다.

Duplication between test method and Test Case names

테스트 케이스 클래스 이름에서 받은 정보를 테스트 메소드에서 반복하는 것

(예: public class TestCustomer extends TestCase

public void testCustomerName())

방법: 메소드의 이름을 재정의한다.

Dependent Test Method

테스트 메소드는 실행되어질 수 있는 또 다른 메소드에 따라 달라진다.

방법: 독립 메소드를 만들어준다.

 

 

Refactoring-smells (1).pdf
0.13MB