코드의 구린내
Updated:
코드의 구린내
- 리펙토링을 해야하는 정확한 시점은 없다.
- 많은 연습을 통한 자신만의 기준을 통해서 감을 잡는 방법밖에 없다.
1. 중복코드
- 똑같은 구조가 두 군데 이상 있을 때는 그 부분을 하나로 통일하면 개선된다.
- 한 클래스의 두 메서드 안에 같은 코드가 있는 경우이다.
- 이럴 때는 메서드 추출 기법으로 중복을 없애고 메서드 상향 기법을 적용.
- 코드가 똑같지 않고 비슷하다면 메서드 추출 기법을 적용하여 같은 부분과 다른 부분을 분리. 그런 다음 경우에 따라 템플릿 메서드 형성 기법을 적용.
- 두 메서드가 알고리즘만 다르고 기능이 같다면
- 두 알고리즘 중에서 더 간단한것을 택해서 알고리즘 전환을 적용하면 된다.
-
중복 코드가 메서드 가운데에 있다면 주변 메서드 추출을 적용하면 된다.
- 서로 상관없는 두 클래스 안에 중복 코드가 있을 때는 한 클래스 안의 중복 코드를 클래스 추출이나 모듈 추출을 적용해 제 3의 클래스나 모듈로 떼어낸 후 그것을 다른 클래스에서 호출하는 방법이 있다
2. 장황한 메서드
- 메서드의 기능을 한 눈에 알 수 있는 메서드명을 사용해라.
- 이를 위해서 메서드를 훨신 과감하게 쪼개야 한다.
- 주석을 달아야 할 것 같은 부분에 주석을 넣는 대신 메서드를 작성한다.
- 메서드 안에 주석을 단 코드를 넣고, 그 메서드명은 기능 수행 방식이 아니라 목적(즉, 기능 자체)을 나타내는 이름으로 정한다.
- 메서드 호출이 원래 코드보다 길어져도, 메서드 명은 그 코드의 의도를 잘 반영하는 것으로 정해야 한다.
- 메서드 길이가 아니라 메서드 기능과 수행 방법이 서로 다른 의미임을 먼저 이해해야 한다.
- 메서드 추출 기법을 사용하면 메서드가 줄어든다.
- 그러나 매개변수와 임시 변수가 많으면 메서드 추출이 어려워진다.
- 임시 변수를 메서드 호출 전환 기법 or 메서드 체인 전환 기법으로 제거한다.
- 매개 변수는 객체 전환 기법과 객체를 통째로 전달 기법을 적용한다.
- 여전히 임시, 매개 변수가 많다면 메서드를 메서드 객체 전환 기법을 적용한다.
-
코드를 여러 덩어리로 분리하려면 어떻게 할까?
-
주석을 보면 이런 의지적 차이를 구분할 수 있다.
-
기능 설명이 주석으로 처리된 코드 구간을 메서드로 만들면 된다.
-
이때 메서드명은 주석에 설명된 기능을 참고해서 정하면 된다.
-
한 줄 밖에 안되는 코드라도 별도의 주석이 달려 있을 정도로 다른 기능을 수행한다면 메서드로 추출해야 한다.
-
-
조건문과 루프도 역시 메서드로 빼야 한다. 조건문을 추출하려면 조건문 쪼개기기법을 사용해야 한다.
- 루프를 컬렌션 클로저 메서드로 전환을 실시한 후 그 클로저 메서드 호출과 클로저 자체에 메서드 추출을 실시하면 된다.
3. 방대한 클래스
- 기능이 지나치게 많은 클래스에는 보통 엄청난 수의 인스턴스 변수가 들어 있다. 이는 중복 코드가 반드시 존재한다.
- 서로 연관된 변수를 골라서 클래스 추출을 실시하면 수많은 인스턴스 변수를 하나로 묶을 수 있다.
- 만약 추출할 클래스가 대리자로 부적절할 것 같으면 모듈 추출을 실시하면 된다.
- 인스턴스 변수를 계속해서 모두 사용하지 않는 클래스, 인스턴스 변수가 너무 많은 클래스, 변수가 많이 들어 있는 클래스, 코드 분량이 너무 방대한 클래스는 모두 클래스 추출, 모듈 추출, 하위클래스 추출 중 하나를 여러 번 적용하는 방법도 있다.
- GUI 클래스라면 데이터와 기능을 서로 다른 도메인 객체로 옮겨야 할수도 있다. 이를 위해 두 곳에 있는 일부 중복 데이터는 놔두고 그 데이터와 싱크를 유지해야 할 수 도 있다. 이때 관측 데이터 복제 기법을 실시하면 해결된다.
4. 과다한 매개변수
- 이미 알고 있는 객체에 요청하여 한 매개변수에 들어 있는 데이터를 가져올 수 있을 때는 매개변수 세트를 메서드로 전환을 적용하면 된다.
- 이 객체는 인스턴스 변수일 수도 있고 다른 매개변수 일 수도 있다.
- 객체에 있는 데이터 세트를 가져온 후, 데이터 세트를 그 객체 자체로 전환하려면 통째로 전달을 적용하면 된다.
- 여러 데이터 항목에 논리적 객체가 없다면 매개변수 세트를 객체로 전환을 적용하면 된다.
- 다만 호출되는 객체가 호출 객체에 의존하면 적용하지 말아야 한다.
5. 수정의 산발 (Divergent Change)
- 수정할 때 개발자는 시스템의 분명한 위치로 곧장 가서 수정할 수 없다면 문제가 있는 것이다.
- 수정의 산발은 한 클래스가 다양한 원인 때문에 다양한 방식으로 자주 수정될 때 일어난다.
- 무언가를 추가할 때마다 한 클래스에 여러 메소드를 수정해야 한다면 하나의 클래스를 여러 개의 변형 객체로 분리하는 것이 좋다.
- 그러면 각 객체는 한 종류의 수정에 의해서만 변경된다.
- 특정 원인으로 인해 변하는 모든 부분을 찾은 후 클래스 추출을 적용해서 그 부분들을 합쳐 한 클래스로 빼내야 한다.
6. 기능의 산재
- 수정할 때마다 여러 클래스에서 수많은 자잘한 부분을 고쳐야 한다면 이 문제를 의심할 수 있다.
- 메서드 이동과 필드 이동을 적용해서 수정할 부분들을 전부 하나의 클래스에 넣어야 한다.
- 기존의 클래스 중 어느 것에 넣기에도 부적절해 보이는 것은 새 클래스를 만들어야 한다.
- 클래스 내용 직접 삽입을 적용해서 별도 클래스에 분산되어 있던 모든 기능을 한 곳으로 가져와도 된다.
- 수정의 산발은 한 클래스에 여러 수정이 발생하는 문제이고, 기능의 산재는 하나의 수정으로 여러 클래스가 바뀌게 되는 문제이다.
- 둘 중 어느 것이든 수정과 클래스가 일대일 대응되게 깔끔히 정리해야 한다.
7. 잘못된 소속
- 객체의 핵심은 데이터와 그 데이터에 사용되는 프로세스를 한 데 묶는 기술이라는 점이다.
- 어떤 메소드가 자신이 속하지 않은 클래스에 더 많이 접근한다면 잘못된 소속이다.
8. 데이터 뭉치
- 동일한 3~4개의 데이터 항목이 여러 위치에 몰려있는 경우, 객체로 만든다.
- 데이터 뭉치를 클래스 추출 기법으로 객체로 만든다.
- 그리고 메서드 시그너처를 대상으로 매개변수 세트를 객체로 전환기법과 객체를 통째로 전달기법을 적용하여 간결하게 만들어야 한다.
- 이렇게 하면 매개변수가 적어져서 부수적으로 메서드 호출 코드가 간결해지는 효과도 누릴 수 있다.
- 새로 생긴 객체의 속성들 중 일부만 이용하는 데이터 뭉치라 해도 이 방법으로 효과를 볼 수 있다.
- 둘 이상의 필드를 객체로 전환하면 코드가 개선된다.
- 그러한 효과는 여러 데이터 값 중 하나를 삭제해보면 확실히 알 수 있다.
- 그렇게 했을 때 나머지 데이터 값들이 제대로 돌아가지 않는다면 그 객체를 없애고 새로 만들어야 한다.
9. 강박적 기본 타입 사용
- 객체의 주요 장점 중 하나가 바로 기본 카입 클래스와 응용 클래스 간의 경계를 허문다는 점이다.
- 언어에 내장된 기본 타입과 구별하기 힘든 작은 클래스를 손쉽게 작성할 수 있다.
- 자바에는 문자열과 날짜가 클래스로 존재한다.
-
우편번호, 전화번호 같은 특수 문자열 클래스 등의 사소한 작업에 작은 객체를 잘 사용하지 않으려는 경향이 있다.
-
데이터 값을 객체로 전환을 실시한다.
- 데이터 값이 분류 부호일 땐 그 값이 기능에 영향을 주지 않는다면 분류 부호를 클래스로 전환을 실시하자.
- 조건문에 분류 부호가 사용될 땐 분류 부호를 하위클래스로 전환 기법이나 분류 부호를 상태/전략 패턴으로 전환 기법을 적용하자.
- 뭉쳐 다녀야 할 여러 개의 필드가 있다면 클래스 추출 기법을 적용
- 이런 기본 타입이 매개변수 세트에 들어 있다면 매개변수 세트를 객체로 전환 기법을 적용
- 배열 때문에 불편하다면 배열을 객체로 전환 기법을 적용
10. Switch 문
- switch의 단점은 반드시 중복이 생긴다는 점이다. 이를 위해 다형성, 즉 재정의를 이용하는 것이다.
- 문제는 재정의를 넣을 위치이다.
- switch 문에는 분류 부호가 흔히 사용되는데, 그럴 땐 분류 부호 값이 들어 있는 메서드나 클래스가 있어야 한다.
- 이럴 때는 메서드 추출을 실시해서 switch문을 메서드로 빼낸 후 메서드 이동을 실시해서 그 메서드를 재정의해야 할 클래스에 옮겨 넣으면 된다.
- 그와 동시에 분류 부호를 하위 클래스로 전환 기법과 분류 부호를 상태/전략 패턴으로 전환 기법 중 어느 것을 적용할지 판단해야 한다.
-
상속구조를 만들었다면 조건문을 재정의로 전환 기법을 적용
- 하나의 메서드에 영향을 미치는 case 문이 2~3개 밖에 없고 나중에 그 모든 case 문을 수정할 일이 없을 것 같으면, 재정의로 전환하는 것은 과하다.
- 그럴 때는 매개 변수를 메서드로 변환을 적용하는 편이 낫다.
- 여러 case 문 중 하나가 null일 때는 null 검사를 널 객체에 위임을 실시하면 된다.
11. 평행 상속 계층
- 기능의 산재의 특수한 상황이다.
- 이 문제점이 있으면 한 클래스의 하위클래스를 만들 때마다 매번 다른 클래스의 하위 클래스도 만들어야 한다.
- 서로 다른 두 상속 계층의 클래스명 접두어가 같으면 이 문제를 의심할 수 있다.
- 중복 코드 부분을 제거하려면 보통은 한 상속 계층의 인스턴스가 다른 상속 계층의 인스턴스를 참조하게 만들면 된다.
- 메서드 이동과 필드 이동을 실시하면 참조하는 클래스에 있는 계층이 제거된다.
12. 직무유기 클래스
- 효율이 떨어지는 클래스는 제거해야 한다.
- 하위 클래스 모듈인 경우 계층 병합을 실시한다.
- 쓸모 없는 구성 요소에는 클래스 내용 직접 삽입 or 모듈 내용 직접 삽입 기법을 적용해야 한다.
13. 막연한 범용코드
- 아직은 필요 없는 기능을 수행하고자 만든 코드는 제거하자.
- 별다른 기능이 없는 클래스나 모듈이 있다면 계층 병합을 실시
- 불필요한 위임을 제거하려면 클래스 내용 직접 삽입을 실시해야 한다.
- 메서드에 사용되지 않는 매개변수가 있으면 매개변수 제거를 실시
- 메서드명이 이상하다면 메서드명 변경을 실시해야 한다.
- 메서드나 클래스가 오직 테스트 케이스에만 사용된다면 테스트 케이스를 삭제하자.
- 단, 적절한 기능을 실행하는 테스트 케이스용 헬퍼 메서드나 클래스는 당연히 삭제하지 말고 내버려둬야 한다.
14. 임시 필드
- 어떤 객체 안에 인스턴스 변수가 특정 상황에서만 할당되는 경우 클래스 추출을 실시해야 한다.
- 그렇게 작성한 클래스에 그 변수들과 관련된 코드를 전부 넣어야 한다.
- null 검사를 널 객체에 위임을 실시해서 그 변수들의 값이 올바르지 않을 경우를 대비한 대체 컴포넌트를 작성하면 경우에 따라 조건문 코드를 없앨 수 있다.
15. 메시지 체인
- 메시지 체인은 클라이언트가 한 객체에 제 2의 객체를 요청하면, 제 2의 객체가 3의 객체를 요청하고, 다시 4의 객체를 요청하는 연쇄 요청을 말한다.
- 이런 경우 수많은 코드가 행이 든 geThis 메서드나 임시변수 세트라고 봐도 된다.
- 이럴 때는 대리 객체 은폐를 실시해야 한다.
-
그러나 모든 중간 객체가 중개 메서드로 변해서 과잉 중개 메서드의 구린내를 풍기는 문제가 발생한다.
- 결과 객체가 어느 대상에 사용되는지를 알아내는 방법도 있다.
- 그렇게 알아낸 객체가 사용되는 코드 부분을 메서드 추출을 통해 별도의 메서드로 빼낸 후 메서드 이동을 실시해서 체인 아래로 밀어낼 수 있는 지 여부를 검사해야 한다.
- 만약 체인에 속한 객체 중 한 객체의 여러 클라이언트가 나머지 객체들에 왕래한다면 그 기능을 수행하는 메서드를 추가하면 된다.
16. 과잉 중개 메서드
- 객체의 주요 특징 한가지는 배로 캡슐화다.
- 캡슐화란 내분의 세부적인 처리를 외부에서 볼 수 없게 은폐하는 작업을 뜻한다.
- 캡슐화할 때는 대게 위임이 수반된다.
- 그러나 지나치면 문제가 된다.
- 어떤 클래스의 인터페이스의 절반도 넘는 메서드가 기능을 다른 클래스에 위임하고 있다면, 과잉 중개 메서드 제거를 실시해서 원리가 구현된 객체에 직접 접근하자.
- 일부 메서드에 별 기능이 없다면, 메서드 내용 직접 삽입을 실시해서 그 메서드들의 내용을 호출 객체에 직접 삽입하면 된다.
- 부수적인 기능이 있다면 위임을 상속으로 전환 기법을 실시해서 중개 메서드를 실제 객체의 하위 클래스로 전환하면 된다. 이렇게 하면 모든 위임을 추적하지 않고 기능을 확장할 수 있다.
17. 지나친 관여
- 클래스끼리 서로 관여하는 경우 메서드, 필드 이동을 실시한다.
- 클래스의 양방향 연결을 단방향으로 전환 기법을 적용할 수 있는지 판단해서 만약 해당 클래스들이 공통으로 필요로 하는 부분이 있다면, 클래스 추출을 실시해서 공통 필요 부분을 별도의 안전한 클래스로 빼내면 된다.
- 아니면 대리 객체 은폐를 실시하여 다른 클래스가 중개 메서드 역할을 하게 만들어도 된다.
- 상속으로 인해 관여가 발생하는 경우, 하위클래스는 항상 상위클래스가 공개하는 것보다 많은 데이터를 필요로 한다.
- 상위 클래스에서 하위 클래스를 빼내야 할 경우에는 상속을 위임으로 전환 기법을 적용해야 한다.
18. 인터페이스가 다른 대용 클래스
- 기능은 같은 시그너처가 다른 메서드에는 메서드명 변경을 실시해야 한다.
- 클래스에 여전히 충분한 기능이 구현되지 있지 않기 때문에 대체로 이 기법만 적용해선 충분하지 않다.
- 프로토콜이 같이질 때까지 메서드 이동을 실시해서 기능을 해당 클래스로 옮겨야 한다.
- 단, 코드를 너무 여러 번 옮겨야 한다면 상위클래스 추출을 실시하면 된다.
19. 미흡한 라이브러리 클래스
-
자신이 직접 라이브러리 클래스를 완성하지 않는 이상 설계를 파악한다는 것이 거의 불가능하다.
- 라이브러리 클래스에 넣어야 할 메서드가 두 개뿐이라면 외래 클래스에 메서드 추가 기법을 실시.
- 부가 기능이 많을 때는 국소적 상속확장 클래스 사용 기법을 실시
20. 데이터 클래스
- 데이터 클래스는 캡슐화 기법을 실시해야 한다.
- 변경되지 않아야 하는 필드에는 쓰기 메서드 제거를 적용하자.
- 이런 읽기/쓰기 메서드가 다른 클래스에 의해 사용되는 부분을 찾아서, 메서드 이동을 실시하여 기능을 그 데이터 클래스로 옮겨야 한다.
- 만약 메서드 전체를 옮길 수없다면 메서드 추출을 실시해서 옮길 수 있는 메서드를 작성하면 된다.
- 그리고 나서 읽기/쓰기 메서드에 메서드 은폐를 적용하면 된다.
21. 방치된 상속물
- 하위 클래스는 부모 클래스의 메서드와 데이터를 상속받는다.
- 그런데 하위클래스가 상속 받은 것 중 필요한 것 외에 방치하면 문제가 생긴다.
- 기존에는 이 문제의 원인이 잘못된 계층구조 때문이라고 설명했다.
- 이럴 경우, 새 대등 클래스를 작성하고 메서드 하향과 필드 하향 실시해서 사용되지 않는 모든 메서드를 그 형제 클래스에 몰아넣어야 한다.
- 이렇게 하면 상위 클래스에는 공통 코드만 들어 있게 된다.
- 일부 기능을 언제든 재사용하고자 하위클래스에 넣는 작업은 효과적이다.
- 방치된 상속물로 인해 코드가 복잡해지거나 문제가 생길 때는 위에 설명한 기존 방법을 따르기 권한다.
- 그러나 이 방법을 반드시 적용해야 하는 것은 아니다.
- 이 문제는 심각하지 않은 경우가 대부분이기 때문에 리펙토링이 별로 필요가 없다.
- 방치된 상속물의 구린내는 하위클래스가 기능은 재사용하지만 상위클래스의 인터페이스를 지원하진 않을 때 훨씬 심하게 풍긴다.
- 상속구현을 거부하는 것은 상관없지만, 인터페이스를 거부하는 것은 심각한 문제다.
- 하지만 그렇다고 계층 구조를 건드려서는 안되고, 상속을 위임으로 전환 기법을 적용해서 계층구조를 없애야 한다.
22. 불필요한 주석
- 어떤 코드 구간의 기능을 설명할 주석이 필요할 때는 메서드 추출을 실시해야 한다.
- 메서드가 이미 추출된 상태임에도 기능을 설명할 주석이 여전히 필요하다면 메서드명을 변경해야 한다.
- 시스템의 필수적인 상태에 관해 약간의 규칙을 설명해야 할 때는 어설션 넣기를 실시한다.
- 주석은 무슨 작업을 해야 좋을지 모를 때만 넣는 것이 좋다.
- 주석을 넣으면 돌아가는 원리를 적어 둘 수도 있고 확실치 않은 부분을 표시할 수도 있다.
- 어떤 코드를 넣은 이유를 메모해 놓을 경우에도 주석을 넣는 것이 적절하다.