마틴 파울러 - 『리팩토링: 코드 품질을 개선하는 객체지향 사고법』를 정리한 글들을 참고하였습니다.
겉으로 보이는 프로그램의 동작이 바뀌지 않으면서 코드 내부 구조를 개선하는 방법. 버그 발생 가능성을 최소화하면서 코드를 정리하는 정형화된 방법이다. 코드의 디자인을 개선하는 작업이지 성능을 최적화시키는 것은 아니다. 시간을 따로 내어 모든 것을 미리 생각하는 것 보다는 개발을 진행하면서 지속적으로 좋은 디자인을 찾아야 한다.
리팩토링을 하는 이유
리팩토링은 소프트웨어의 디자인을 개선시킴으로써 가독성을 높이고 유지보수가 용이해진다.
새로운 코드를 쉽게 추가할 수 있다.
버그를 쉽게 찾도록 도와준다.
프로그램을 빨리 작성하도록 도와준다.
코드 중복을 최소화하여 각 작업에 대한 코드가 오직 한 곳에 있게 할 수 있다.
리팩토링이 필요한 시기
중복된 코드(Duplicated Code)
동일한 코드 구조가 두 곳 이상에서 중복으로 사용될 때, 이를 하나로 통일하면 프로그램을 개선할 수 있다.
한 클래스의 두 메소드 안에 중복된 코드가 있는 경우 -> 메소드 추출
한 클래스의 두 하위 클래스에 중복된 코드가 있는 경우 -> 메소드 추출, 메소드 상향
서로 연관없는 두 클래스에 중복된 코드가 있는 경우 -> 메소드 추출 후 클래스 or 모듈로 분리
긴 메소드(Long Method)
메소드들은 그 기능을 쉽게 알 수 있는 이름을 가져야 하며 코드를 분석하지 않아도 쉽게 기능을 알 수 있어야 한다. 따라서 메소드 길이는 짧아야하고 이를 위해 메소드를 과감하게 쪼개야 한다. 이때, 쪼갠 메소드의 이름은 기능 수행 방식이 아니라 메소드의 기능을 나타내는 이름으로 정해야 한다.
클래스의 기능이 많아질수록 인스턴스 변수의 수도 증가하고 인스턴스 변수가 많을수록 중복된 코드가 존재할 확룔도 높아진다.
연관된 인스턴스 변수들을 묶어 또다른 클래스, 하위 클래스, 모듈 등으로 추출
긴 매개변수(Long Parameter LIsts)
메소드의 매개변수가 길어지게 되면 이해하기 어렵고 사용하기 어려워진다. 객체 지향 코드에서는 객체가 변수를 가지고 있고 해당 객체를 통해 데이터를 얻을 수 있기 때문에 매개변수 리스트를 하나의 객체에서 가져올 수 있으면 매개변수를 객체로 전환한다.
수정의 산발(Divergent Change)
하나의 기능을 수정할 때마다 여러 메소드를 수정해야 한다면 좋은 코드라고 볼 수 없다. 따라서 하나의 클래스가 하나의 책임만 가질 수 있게 여러 클래스로 추출하여야한다.
기능의 산재(Shotgun Surgery)
'수정의 산발'과 비슷하지만 반대되는 개념. 하나의 기능을 수정할 때마다 여러 클래스들을 수정해야 한다면 이역시 좋은 코드가 아니다.
메소드 이동, 필드 이동 등을 사용해 수정한 부분을 하나의 클래스 안으로 넣거나 새로운 클래스를 생성해야 한다.
잘못된 소속 (Feature Envy)
어떠한 메소드가 자신이 속한 클래스가 아닌 외부 클래스에 대한 접근이 더 많다면 잘못된 설계로 볼 수 있다. 따라서 해당 메소드를 더 자주 접근하는 클래스로 옮겨야 한다. 메소드 중 일부분만 추출해서 옮길 수도 있다.
데이터 뭉치 (Data Clumps)
동일한 여러 데이터들이 여러 클래스에 위치해 있는 경우, 즉 하나의 데이터 뭉치로 존재하는 값들은 하나의 객체로 생성해야 한다.
강박적 기본 타입 사용 (Primitive Obsession)
데이터 이용 시 기본 타입만 사용하지 말고 객체도 잘 사용해야 한다. 보통 Money 클래스(개수와 화폐 단위 묶기), 전화번호, 이메일, 우편번호와 같은 특수 문자열 클래스 등을 객체를 잘 사용하지 않으려는 경향이 있는데 이러한 습관은 좋지 않다.
switch 문 (Switch Statements)
객체지향 코드의 특징 중 하나는 switch 구문이 비교적 적게 쓰인다는 점이다. swicth 문을 쓰게되면 반드시 코드 중복이 발생하게 되고 객체지향 코드에서는 swicth 문 보다 다형성 개념을 적용해 코드를 분리, 재정의 하는 것이 좋다.
메소드 추출로 switch문을 뽑아내고 메소드들을 다형성을 적용한 클래스로 옮긴다.
게으른 클래스 (Lazy Class)
get/set 메소드만 가지고 다른 것은 아무것도 없는 등 클래스가 별다른 기능을 가지지 않는 경우가 있다. 클래스 생성, 유지 자체가 비용이 발생하기 때문에 필요 없는 클래스는 삭제 후 클래스를 직접 삽입(Inline Class)하는 것이 좋다.
임시 필드(Temporary Field)
객체 내의 인스턴스 변수가 특정 상황에서만 사용되는 경우가 있다. 보통 객체는 들어 있는 모든 인스턴스 변수를 사용할 것이라 예상하기 때문에 어떤 변수가 특정한 상황에서만 사용된다면 코드를 파악하기 어려워 진다. 이런 경우에는 변수와 메소드를 묶어 하나의 클래스로 추출한다.
메시지 체인 (Message Chains)
어떤 객체를 얻기 위해 다른 객체에 물어보고, 그 객체는 또 다른 객체에 물어보고, 다시 다른 객체에게 물어보고... 이와 같이 메시지 체인이 발생할 수 있다. 이러한 경우 중간에 특정 관계가 변하게 되면 어떤 객체를 얻기 위한 코드들도 수정되야 한다. 이 경우 대리 객체 은폐(Hide Delegate)를 적용해야 한다.
불필요한 주석 (Comments)
특정 메소드에서 어떤 코드의 기능을 설명하는 주석이 길어지게 된다면 메소드 추출이 필요할 수도 있다. 또한, 메소드를 추출하고 난 뒤에도 기능을 설명할 주석이 필요하다면 메소드명 변경을 해야 한다.
리팩토링
마틴 파울러 - 『리팩토링: 코드 품질을 개선하는 객체지향 사고법』를 정리한 글들을 참고하였습니다.
겉으로 보이는 프로그램의 동작이 바뀌지 않으면서 코드 내부 구조를 개선하는 방법. 버그 발생 가능성을 최소화하면서 코드를 정리하는 정형화된 방법이다. 코드의 디자인을 개선하는 작업이지 성능을 최적화시키는 것은 아니다. 시간을 따로 내어 모든 것을 미리 생각하는 것 보다는 개발을 진행하면서 지속적으로 좋은 디자인을 찾아야 한다.
리팩토링을 하는 이유
리팩토링은 소프트웨어의 디자인을 개선시킴으로써 가독성을 높이고 유지보수가 용이해진다.
새로운 코드를 쉽게 추가할 수 있다.
버그를 쉽게 찾도록 도와준다.
프로그램을 빨리 작성하도록 도와준다.
코드 중복을 최소화하여 각 작업에 대한 코드가 오직 한 곳에 있게 할 수 있다.
리팩토링이 필요한 시기
중복된 코드(Duplicated Code)
동일한 코드 구조가 두 곳 이상에서 중복으로 사용될 때, 이를 하나로 통일하면 프로그램을 개선할 수 있다.
긴 메소드(Long Method)
메소드들은 그 기능을 쉽게 알 수 있는 이름을 가져야 하며 코드를 분석하지 않아도 쉽게 기능을 알 수 있어야 한다. 따라서 메소드 길이는 짧아야하고 이를 위해 메소드를 과감하게 쪼개야 한다. 이때, 쪼갠 메소드의 이름은 기능 수행 방식이 아니라 메소드의 기능을 나타내는 이름으로 정해야 한다.
방대한 클래스(Large Class)
클래스의 기능이 많아질수록 인스턴스 변수의 수도 증가하고 인스턴스 변수가 많을수록 중복된 코드가 존재할 확룔도 높아진다.
긴 매개변수(Long Parameter LIsts)
메소드의 매개변수가 길어지게 되면 이해하기 어렵고 사용하기 어려워진다. 객체 지향 코드에서는 객체가 변수를 가지고 있고 해당 객체를 통해 데이터를 얻을 수 있기 때문에 매개변수 리스트를 하나의 객체에서 가져올 수 있으면 매개변수를 객체로 전환한다.
수정의 산발(Divergent Change)
하나의 기능을 수정할 때마다 여러 메소드를 수정해야 한다면 좋은 코드라고 볼 수 없다. 따라서 하나의 클래스가 하나의 책임만 가질 수 있게 여러 클래스로 추출하여야한다.
기능의 산재(Shotgun Surgery)
'수정의 산발'과 비슷하지만 반대되는 개념. 하나의 기능을 수정할 때마다 여러 클래스들을 수정해야 한다면 이역시 좋은 코드가 아니다.
잘못된 소속 (Feature Envy)
어떠한 메소드가 자신이 속한 클래스가 아닌 외부 클래스에 대한 접근이 더 많다면 잘못된 설계로 볼 수 있다. 따라서 해당 메소드를 더 자주 접근하는 클래스로 옮겨야 한다. 메소드 중 일부분만 추출해서 옮길 수도 있다.
데이터 뭉치 (Data Clumps)
동일한 여러 데이터들이 여러 클래스에 위치해 있는 경우, 즉 하나의 데이터 뭉치로 존재하는 값들은 하나의 객체로 생성해야 한다.
강박적 기본 타입 사용 (Primitive Obsession)
데이터 이용 시 기본 타입만 사용하지 말고 객체도 잘 사용해야 한다. 보통 Money 클래스(개수와 화폐 단위 묶기), 전화번호, 이메일, 우편번호와 같은 특수 문자열 클래스 등을 객체를 잘 사용하지 않으려는 경향이 있는데 이러한 습관은 좋지 않다.
switch 문 (Switch Statements)
객체지향 코드의 특징 중 하나는 switch 구문이 비교적 적게 쓰인다는 점이다. swicth 문을 쓰게되면 반드시 코드 중복이 발생하게 되고 객체지향 코드에서는 swicth 문 보다 다형성 개념을 적용해 코드를 분리, 재정의 하는 것이 좋다.
게으른 클래스 (Lazy Class)
get/set 메소드만 가지고 다른 것은 아무것도 없는 등 클래스가 별다른 기능을 가지지 않는 경우가 있다. 클래스 생성, 유지 자체가 비용이 발생하기 때문에 필요 없는 클래스는 삭제 후 클래스를 직접 삽입(Inline Class)하는 것이 좋다.
임시 필드(Temporary Field)
객체 내의 인스턴스 변수가 특정 상황에서만 사용되는 경우가 있다. 보통 객체는 들어 있는 모든 인스턴스 변수를 사용할 것이라 예상하기 때문에 어떤 변수가 특정한 상황에서만 사용된다면 코드를 파악하기 어려워 진다. 이런 경우에는 변수와 메소드를 묶어 하나의 클래스로 추출한다.
메시지 체인 (Message Chains)
어떤 객체를 얻기 위해 다른 객체에 물어보고, 그 객체는 또 다른 객체에 물어보고, 다시 다른 객체에게 물어보고... 이와 같이 메시지 체인이 발생할 수 있다. 이러한 경우 중간에 특정 관계가 변하게 되면 어떤 객체를 얻기 위한 코드들도 수정되야 한다. 이 경우 대리 객체 은폐(Hide Delegate)를 적용해야 한다.
불필요한 주석 (Comments)
특정 메소드에서 어떤 코드의 기능을 설명하는 주석이 길어지게 된다면 메소드 추출이 필요할 수도 있다. 또한, 메소드를 추출하고 난 뒤에도 기능을 설명할 주석이 필요하다면 메소드명 변경을 해야 한다.