클린코드를 위한 리팩토링

클린코드를 위한 리팩토링

코드의 구린내 +

구린게 있으면 그 부분을 바로 잡으세요.
리팩토링을 어떨 때 시작하고 어떨 때 그만둬야 할지 판단하는 일은 리팩토링 기법을 적용하는 방법만큼 중요하다.


1. 중복코드

구린내의 제왕 중복코드.
똑같은 코드 구조가 두 군데 이상 있을 때는 그 부분을 하나로 통일하면 프로그램이 개선
한 클래스의 두 메서드 안에 같은 코드가 들어있는경우 -> 메서드 추출 기법을 적용해서 겹치는 코드를 빼내어 별도의 메서드로 만들고 그 메서 드를 두 곳에서 호출
수퍼클래스의 두 하위 클래스에 같은 코드가 들어 있는 경우 -> 메서드 추출 기법을 적용해서 중복을 없앤 후 메서드 상향 기법을 적용하면 된다.
위 상황에 코드가 똑같지 않고 비슷하다면 메서드 추출기법을 적용해서 같은 부분과 다른 부분을 분리해야 한다. 그런 다음 경우에 따라 템플릿 메서드 형성 기법을 적용해야 할 수도있다.
두 메서드가 알고리즘만 다르고 기능이 같다면 개발자는 그 두 알고리즘 중에서 더 간단한 것을 택해서 알고리즘 전환 기법을 적용하면 된다.
중복 코드가 메서드 가운데에 있다면 주변 메서드 추출을 적용하면된다.
서로 상관 없는 두 클래스 안에 중복코드가 있을 때 -> 한 클래스 안의 중복 코드를 클래스 추출 이나 모듈 추출 을 적용해 제 3의 클래스나 모듈로 떼어낸 후 그것을 다른 클래스에서 호출하는 방법이 있다. 또는 중복 코드를 빼서 메서드로 만든 후 그 메서드를 두 클래스 중 하나에 넣고 다른클래스에서 그 메서드를 호출하거나 코드를 빼내어 만든 메서드를 제 3의 클래스에 넣고 그걸 두 클래스에서 호출하는 방법이 있다.

2. 장황한 메서드

최적의 상태로 장수하는 객체 프로그램을 보면 공통적으로 메서드 길이가 짧다.
짧은 메서드를 이해하기 쉽게하려면 메서드 명을 잘 정해야한다. 메섣의 기능을 한눈에 알 수 있는 메서드명을 사용하면 그 메서드안의 코드를 분석하지 않아도된다.
메서드 호출이 원래 코드보다 길어지는 한이 있어도, 메서드 명은 그 코드의 의도를 잘 반영하는 것으로 정해야한다.
메서드명은 기능 수행 방식이 아니라 목적(즉, 기능자체)을 나타내는 이름으로 정한다.
메서드 추출기법을 적용한다.
코드를 여러 덩어리로 분리하려면?
주석을 찾는 것 -> 기능 설명이 주석으로 처리된 코드 구간을 메서드로 만들면 된다. (긴 메서드에서 기능 설명이 주석으로 되어있는 부분)
조건문과 루프도 역시 메서드로 추출

3. 방대한 클래스

기능이 지나치게 많은 클래스에는 보통 엄청난 수의 인스턴스 변수가 들어있다. 클래스에 인스턴스 변수가 너무 많으면 중복 코드가 반드시 존재하게 마련

4. 과다한 매개변수

매개변수 세트가 길면 서로 일관성이 없어지거나 사용이 불편, 더많은 데이터가 필요해질 때마다 계속 수정해야 한다
즉 매개변수는 객체를 넘기도록 한다. 객체를 넘김으로써 위 문제들이 해결

5. 수정의 산발

수정의 산발은 한 클래스가 다양한 원인 때문에 다양한 방식으로 자주 수정될 때 일어난다.
하나의 클래스를 여러 개의 변형 객체로 분리하는것이 좋다. 그러면 각 객체는 한 종류의 수정에 의해서만 변경된다.

6. 기능의 산재

하나의 수정으로 여러 클래스가 바뀌게 되는 문제
수정할 부분들을 전부 하나의 클래스 안에 넣어줘야 한다.

7 .잘못된 소속

메서드가 자신이 속해있는 클래스보다 다른 클래스에서 더 호출이 이루어질 경우 해당 메서드를 더 접근이 많은 클래스로 옮겨줘야 한다.

8. 데이터 뭉치

두 클래스에 들어 있는 인스턴스 변수나 여러 메서드 시그너처(메서드명과 인수들 목록을 메서드 시그너처 라고 부릅니다.)에 들어있는 매개변수 처럼, 동일한 3~4개의 데이터 항목이 여러 위치에 몰려있는 경우
이렇게 몰려있는 데이터 뭉치는 객체로 만들어야한다.

9. 강박적 기본 타입 사용

관련된 데이터를 묶지 못하고 흩어놓게 되면, 각각의 데이터에 대한 정보를 외부에 공개해야한다.
함수를 만들때도 각각의 데이터를 파라미터로 넘겨주어야 하기에 파라미터의 갯수가 늘어나게 된다.
기본형만 사용할 바에는 객체를 만들어서(구조화)해서 사용해라
이때는 각각의 관련된 데이터를 하나의 구조체로 묶어 주어야한다.

10. switch 문

switch 문의 단점은 반드시 중복이 생긴다는 점이다. 동일한 switch가 다른 곳에서 또 쓰일가능성이 크다
switch 문에 새 코드행을 추가하려면 그렇게 여기저기에 존재하는 switch 문을 전부 찾아서 수정해야한다.
이 문제를 해결할수 있는 방법은 다형성 즉 재정의를 이용하는 것이다.
switch 문을 메서드 추출로 빼낸 후 메서드 이동을 실시해서 그 메서드를 재정의해야 할 클래스에 옮겨 넣으면 된다.

11. 평행 상속 계층

한 클래스의 하위클래스를 만들 때마다 매번 다른 클래스의 하위 클래스도 만들어야 한다.
중복 코드 부분을 제거하려면 보통은 한 상속 계층의 인스턴스가 다른 상속계층의 인스턴스를 참조하게 만들면 된다.

12. 직무유기 클래스

하나의 클래스를 작성할 때마다 유지관리와 이해하기 위한 비용이 추가된다.
비용만큼의 기능을 수행하지 못하는 비효율적인 클래스는 삭제해야한다.

13. 막연한 범용 코드

메서드나 클래스가 오직 테스트 케이스에만 사용된다면 구린내를 풍기는 유력한 용의자로 막연한 범용코드를 지목할 수 있다. 이러한 메서드나 클래스를 발견하면 그것과 그것을 실행하는 테스트 케이스는 삭제하자.

14. 임시 필드

객체 안에 인스턴스 변수가 특정 상황에서만 할당되는 경우가 간혹 있다.
이러한 떠돌이 해당 변수들을 사용하는 class를 생성한다

15. 메시지 체인

메시지 체인?
클라이언트가 한 객체에 제 2의 객체를 요청하면, 제 2의 객체가 제 3의 객체를 요청하고 …. 연쇄적으로 요청이 발생하는 문제점

16. 과잉 중개 메서드

어떤 클래스의 인터페이스를 보니 안의 절반도 넘는 메서드가 기능을 다른 클래스에 위임하고 있을경우

17. 지나친 관여

클래스 끼리 관계가 지나치게 밀접한 나머지 서로의 private를 알아내느라 과도한 시간낭비
서로 지나지게 친밀한 클래스는 메서드 이동과 필드 이동으로 떼어 낸다.

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

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

19. 미흡한 라이브러리 클래스

라이브러리 클래스에 넣어야 할 메서드가 두 개 뿐이라면 외래 클래스에 메서드 추가 기법을, 부가 기능이 많을 때는 국소적 상속확장 클래스 사용 기 법을 사용

20. 데이터 클래스

데이터 클래스(domain)는 필드 캡슐화기법을 실시해야한다
변경되지 않아야 하는 필드에는 쓰기 메서드 제거를 적용

21. 방치된 상속물

하위클래스가 부모클래스에게 상속받은 메서드나 데이터가 하위클래스에서 더이상 쓰이지 않거나 필요 없게 되었을때.
위 문제의 원인은 잘못된 계층구조
새 대등 클래스를 작서하고 메서드 하향과 필드하향을 실시해서 사용되지 않는 모든 메서드를 그 형제 클래스에 몰아 넣는다.

22. 불필요한 주석

주석을 넣어야겠다는 생각이 들 땐 먼저 코드를 리팩토링해서 주석을 없앨 수 있게 만들어보자.
주석은 무슨 작업을 해야 좋을지 모를 때만 넣는 것이 좋다.
어떤 코드를 넣은 이유를 메모해 놓을 경우에도 주석을 넣는 것이 적절하다.


레거시 코드를 받았을 때 좋은 코드로 바꾸는 방법 +

레거시 코드의 양이 방대할 경우 부분별로 리팩토링을 진행해야 한다. 이 때, 유의할 점은 정확한 목표를 가져야 한다는 것이다. 대부분의 개발자는 성능 개선에만 초점을 두는데, 이는 또 다른 레거시 코드를 만들어 낼 수 있다. 레거시 코드를 변경하는 목적은 변경 자체가 아니라 변경시키는 행위이다. 따라서 각각의 작업 끝에는 항상 테스트 코드가 있어야 한다. 이 점을 항상 유념하며 리팩토링을 진행해야 한다.

1. 변경지점 식별

변경시킬 지점을 알기 위해서는 대상 프로그램의 전체적인 아키텍처를 보아야한다. 노트/스케치나 스크래치 리팩토링 기법과 같은 방법을 통해 변경해야 할 지점을 식별하자.

2. 테스트 지점 찾기

리팩토링이 필요한 부분을 찾았다면 이제 어느 부분에 테스트 루틴을 작성할 것인지 결정해야 한다. 이를 위한 가장 간단한 판단 기준은 모든 메소드에 대해 테스트를 수행하는 것이다. 또한, 각 메소드들이 의존관계를 이루는 묶음끼리도 테스트가 필요한데, 이를 위해 의존관계를 최소화시키는 것이 중요하다.

3. 의존관계 깨기

코드에서의 의존관계는 다른 클래스나 인터페이스에 방향성을 가지고 의지하는 코드를 의미한다. 예를 들어 A 클래스에서 B 클래스에 있는 메서드를 호출하는 상황을 가정해 보자. 이때 A 클래스가 변경되었다고 해서 B 클래스까지 변경될 필요는 없다. 이를 A 클래스가 B 클래스에 의존하고 있다고 말한다.

이러한 의존관계는 상황별 & 종류별로 매우 다양하며 이를 해소하기 위한 방법도 각양각색이다. 기본적으로 의존관계란 코드의 유지 보수성을 떨어트리기 때문에, 의존관계를 최소화시키는 것이 중요하다. 각 상황별 의존관계 해소 방법을 알고 싶다면, 책으로 출간된 마이클 패더스의 Working Effectively with Legacy Code를 읽어보기 바란다.

4. 테스트 루틴 작성

테스트 루틴을 작성할 때에는 코드의 동작을 이해하는 데 필요하다고 느끼는 만큼의 사례를 작성해야 한다. 기능을 추출하거나 이동시키려 한다면 사례별로 동작들의 존재 여부와 연결을 검증할 수 있는 테스트 루틴을 작성한다. 이동시키고자 하는 코드를 수행하는지와 그 코드가 적절히 연결되었는지를 검증한 후에 변환을 수행하도록 한다.

5. 변경 후 리팩토링

전체적으로 볼 때 리팩토링은 덩치가 큰 메소드를 작은 단위로 쪼개는 것이다. 이렇게 된다면 코드를 좀 더 이해하기 쉽게 작성할 수 있다. 또한, 재사용성을 높일 수 있으며 시스템 안의 다른 영역들에 있는 로직과의 중복을 제거할 수 있다.