본문 바로가기

Dev.World/개발상식&언어

[Refactoring] 마틴 파울러, 리팩토링 정리 1편


Refactoring 1편

컴퓨터가 인식 가능한 코드는 바보라도 작성할 수 있지만, 
인간이 이해할 수 있는 코드는 실력있는 프로그래머만 작성할 수 있다.

What, 리팩토링이란 무엇인가
정의
리팩토링이란 겉으로 드러나는 기능은 그대로 둔 채, 아랑보기 쉽고 수정하기 간편하게 소프트웨어 내부를 수정하는 작업을 말한다. 리팩토링 기법을 연달아 적용해서 겉으로 드러나는 기능은 그대로 둔 채 소프트웨어 구조를 변경한다.

목적
첫째, 리팩토링은 소프트웨어를 더 이해하기 쉽고 수정하기 쉽게 만드는 것이다.
둘째, 리팩토링은 겉으로 드러나는 소프트웨어 기능에 영향을 주지 않는다.

Why, 리팩토링은 왜 필요한 것인가
1) 소프트웨어의 설계를 보다 더 나아지게 한다.
2) 코드를 더 이해하기 쉽게 만든다.
3) 코드에서 발생하는 버그를 찾기 쉽게 만든다.
4) 프로그래밍 속도가 빨라져 생산성이 높아진다.

When, 언제 리팩토링을 실시하는가
같은 작업을 3번째 반복하게 되었을 때 실시한다.
어떠한 기능을 추가하기 힘들 때, 리팩토링을 실시한다.


How, 리팩토링 시작하기 ( feat. 코드의 구린내 )
코드에서 '구린내'나는 부분을 찾아서 수정하자.

Duplicated Code (중복 코드)
한 클래스 내에 존재하는 중복 코드에 대해서는 중복된 부분을 하나의 메서드로 추출해야 한다. 서로 상관없는 두 클래스 안에 중복 코드가 있을 때는 클래스 추출이나 모듈 추출을 통해서 제 3의 클래스 (or 모듈)로 추출해야 한다.

Long Method (장황한 메서드)
메서드를 작은 단위로 과감하게 쪼개야 한다. 주석을 달아야 할 것 같은 부분에 주석을 넣는 대신 메서드를 작성하고 안에 주석을 단 코드를 넣는다. 짧은 메서드를 이해하기 쉽도록 메서드 명을 잘 정해야 한다. 메서드 명은 기능 수행 방식이 아니라 목적, 즉 기능 자체를 나타내는 이름으로 정한다.

Large Class (방대한 클래스)
기능이 지나치게 많은 클래스에는 엄청난 수의 인스턴스 변수가 들어있다. 접두어나 접미어가 같다면 하나의 클래스로 추출하는 것이 좋다. 인스턴스 변수를 계속해서 모두 사용하지 않는 클래스에 대해서는 클래스 추출 또는 모듈 추출을 적용한다.

Long Parameter List (과다한 매개변수)
매개변수가 많아지면 서로 일관성이 없어지거나 사용이 불편해지고, 더 많은 데이터가 필요해질 때마다 계속 수정해야 하기 때문에 매개변수들을 이해하는 것이 어려워진다. 이럴 경우 객체를 통째로 전달하는 방법을 사용하거나 매개변수들을 메서드로 전환하여 전달하는 방법을 사용한다.

Divergent Change (수정의 산발)
한 클래스가 다양한 원인 때문에 다양한 방식으로 자주 수정될 때 발생한다. 이럴 경우 하나의 클래스를 여러 개의 변형 객체로 분리하는 것이 좋다. 특정 원인으로 인해 변하는 부분을 별도의 클래스로 추출한다.

Shotgun Surgery (기능의 산재)
수정할 때마다 여러 클래스에서 수많은 부분을 고쳐야 한다면 이 부분을 의심해본다. 이럴 경우 메서드 또는 필드를 하나의 클래스로 추출한다. 기존의 클래스 중 적절한 클래스가 없다면 새로운 클래스를 만들어 추출한다.

Feature Envy (잘못된 소속)
객체의 핵심은 데이터와 그 데이터에 사용되는 프로세스를 한 데 묶는 기술이라는 점이다. 다른 객체의 데이터에 접근하기 위해 읽기 메서드를 자주 호출한다면 이 부분을 의심해봐야 한다. 이 경우도 마찬가지로 메소드를 적당한 클래스로 이동시켜야 한다.

Data Clumps (데이터 뭉치)
데이터들은 주로 다른 데이터들과 함께 그룹을 이룬다. 이렇게 몰려있는 데이터에 대해서는 객체로 만들어야 한다. 데이터 뭉치가 필드처럼 보이는 부분을 찾고 그 부분을 별도의 클래스로 추출한다. 자연스럽게 매개변수들이 객체를 통째로 전달하는 방법으로 전달된다.

Primitive Obsession (강박적 기본 타입 사용)
데이터 값들을 객체로 전환한다. 

Switch Statement (Switch 문)
대부분의 switch 문은 고민할 필요없이 재정의로 바꿔야 한다. 상속 구조를 만들어 조건문을 재정의로 전환한다. case 문이 2~3개밖에 없고 모든 case 문에 대해서 수정할 일이 없으면 재정의로 전환하는 것은 과하다.

Parallel Inheritance Hierachies (평행 상속 계층)
한 클래스의 하위클래스를 만들 때마다 매번 다른 클래스의 하위클래스도 만들어야 한다면 이 부분을 의심해봐야 한다. 이럴 경우에는 중복 코드 부분을 제거하기 위해서 상속 계층의 인스턴스가 다른 상속 계층의 인스턴스를 참조하게 만들어야 한다.

Lazy Class (직무유기 클래스)
직무 유기 클래스란 리팩토링으로 인해 기능이 축소된 클래스 또는 수정할 계획으로 작성했으나 수정을 실시하지 않아 쓸모없어진 클래스를 말한다. 이러한 클래스는 비용 대비 효율이 떨어지므로 병합하거나 클래스 내용 직접 삽입 방법을 통해서 제거해야 한다.

Speculative Generality (막연한 범용 코드)
조만간 이런 기능이 필요하겠다 싶어서 추가했으나 아직은 필요없는 기능의 코드를 말한다. 또 메서드나 클래스가 오직 테스트 케이스에만 사용된다면 이 부분을 의심해볼 수 있다.

Temporary Field (임시 필드)
어떤 객체 안의 인스턴스 변수가 특정 상황에서만 할당되는 경우가 있다. 이러한 부분은 별도의 클래스로 추출해야 한다. 복잡한 알고리즘에 여러 변수를 사용할 때도 이 부분을 의심해야 한다. 알고리즘에 사용되는 필드 멤버들은 이 때 말고는 사용되지 않으므로 복잡성만 증대시킨다. 별도의 클래스로 추출하자.

Message Chains (메세지 체인)
메세지 체인이란 한 객체에 제 2의 객체를 요청하면 제 2의 객체가 제 3객체를 요청하는 식으로 연쇄적 요청이 발생하는 것을 말한다. 이럴 경우 결과 객체가 어느 대상에 사용되는지를 알아내어 별도의 메서드로 추출한 후 메서드 이동을 실시한다.

Middle Man (과잉 중개 메서드)
어떤 클래스의 인터페이스가 절반도 넘는 메서드가 기능을 다른 클래스에 위임하고 있다면 원리가 구현된 객체에 직접 접근하는 방식으로 수정한다. 모든 위임을 추척할 필요없이 기능을 확장할 수 있다.

Inappropriate Intimacy (지나친 관여)
서로 지나치게 관여하는 클래스는 각각의 클래스로 분리하여 양방향 연결이었던 부분을 단방향으로 전환해야 한다. 또는 중개 메서드 역할을 하는 별도의 클래스를 새로 만들어도 된다. 상속으로 인해 지나친 관여가 발생하는 경우가 많은데 이를 위임으로 전환하여 해결한다.

Alternative Classes with Different Interfaces (인터페이스가 다른 대용 클래스)
기능은 같은데 시그너처가 다른 메서드에는 메서드 명을 변경해야 한다.

Data Class (데이터 클래스)
데이터 클래스란 필드와 필드에 대한 읽기/쓰기 메서드만 있는 클래스다. 대부분 데이터 조작은 다른 클래스가 수행한다. 변경되지 않아야 하는 필드에는 private 접근제어자를 통해 set 메서드를 제한한다.

Refused Bequest (방치된 상속물)
하위 클래스는 부모 클래스의 메서드와 데이터를 상속받는다. 이 때 하위 클래스에서 필요한 것을 제외한 나머지는 방치된다. 이는 잘못된 계층 구조 때문에 발생하며 메서드 하향 또는 필드 하향을 통해 방치되는 상속물이 없도록 해야 한다.

Comments (불필요한 주석)
주석이 코드의 구린내를 감추기 위한 탈취제 용도로 쓰이면 안된다. 주석을 넣어야겠다는 생각이 들 땐 먼저 코드를 리팩토링해서 주석을 없앨 수 있게 만들어본다.


1편에서는 리팩토링이 무엇인가에 대해 알아보고 무엇을 리팩토링 할 것인가에 대해 집중적으로 알아보았다.
다음 편에서는 각 이슈들에 대한 해결 방법들에 대해 알아본다.

end