리팩토링이란 소프트웨어를 보다 쉽게 이해할수 있고 적은 비용으로 수정할수 있도록 같으로 보이는 동작의 변화 없이 내부 구조를 변경한 것 이라고 Martin Flowler는 정의했다.

 

리팩토링을 하는 이유

  • 코드가 중복되어 있다.
  • 루틴이 너무 길다
  • 루프가 너무 길거나 깊게 중첩되어 있다
  • 클래스의 응집력이 약하다
  • 클래스 인터페이스가 일관된 추상화 수준을 제공하지 않는다
  • 매개변수가 너무 많다
  • 클래스 내에서의 변경 사항이 상호관계를 고려하지 않고 구분되는 경향이 있다.
  • 변경을 할 때 여러 개의 클래스를 동시에 수정해야 한다
  • 상속 계층 구조가 병렬로 변경되어야 한다
  • case 문이 병렬로 변경되어야 한다
  • 함께 사용되는 연관된 데이터 항목이 클래스로 구성되지 않았다
  • 루틴이 자신이 포함된 클래스보다 다른 클래스의 기능을 더 많이 사용한다
  • 기본 데이터 형이 오버도르되었다
  • 클래스가 많은 일을 수행하지 않는다
  • 일련의 루틴들이 뜨내기 데이터(tramp data)를 전달하고 있다.
  • 미들 맨(middle man)객체가 아무것도 하지 않는다
  • 한 클래스가 지나치게 다른 클래스를 참견한다
  • 루틴의 이름이 엉성하다
  • 데이터 멤버가 public이다
  • 서브 클래스는 부모 클래스가 제공하는 그히 일부만 사용한다
  • 주석이 어려운 코드를 설명하기 위하여 사용되었다.
  • 전역 변수가 사용되었다

 

리팩토링을 하면 안되는 이유 - 일반적으로 리팩토링은 결함을 수정하고 기능을 추가하고 설계를 변경하는 것을 가르키는 것처럼 부정확하게 사용된다. 본질적으로 코드를 변경하는 것과 동의어로 사용된다. 이 용어의 의미를 이렇게 희석시키는 것을 불행한 일이다. 변경 그 자체는 미덕이 아니지만 원칙이 적용된 목적이 있는 변경은 유지 보수중에 프로그램의 품질을 꾸준히 향상 시키고 너무나도 잘 알고 있는  SW 엔트로피라는 죽음의 소용돌이를 예방하기 위한 중요한 전략일수 있다.

 

구체적인 리팩토링

데이터 수준 리팩토링

  • 매직 넘버를 명명된 상수로 대체한다
  • 변수이름을 보다 분명하고 많은 정보를 제공하는 이름으로 다시 명명명한다
  • 표현식을 인라인으로 이동시킨다
  • 표현식을 루틴으로 대체한다
  • 중간 변수를 사용한다 - 표현식의 목적을 잘 요약하는 이름을 갖는 중간 변수에 표현식을 할당한다
  • 여러 목적으로 사용되는 변수를 여러개의 단일 목적을 갖는 변수로 변환한다
  • 로컬에서 사용하기 위한 목적이라면 매개변수 대신 지역변수를 사용한다
  • 기본 데이터를 클래스로 변환한다
  • 타입 코드 집합을 클래스나 열거로 변환한다
  • 타입 코드 집합을 서브 클래스를 갖는 클래스로 변환한다
  • 배열을 객체로 변경한다- 만약 각각의 오소들이 형식이 서로 다른 배열을 사용하고 있다면, 배열의 각 요소들에 대한 필드를 갖는 객체를 생성한다
  • 컬렉션을 캡슐화한다
  • 전형적인 레코드를 데이터 클래스로 대체한다 - 레코드의 멤버를 포함하는 ㅋ르래스를 생성한다. 클래스를 생성하면 오류 검사, 지속성, 그리고 레코드와 관련된 다른 연산들을 집중시킬수 있다

 

명령문 수준 리팩토링

  • 불린 표현식을 분해한다
  • 복잡한 불린 표현식을 잘 명명된 불린 함수로 이동시킨다
  • 서로 다른 조건문 내에 중복되어 있는 코드들을 결합한다
  • 루프제어 변수 대신 break나 return을 사용한다
  • 중첩된 if-then-else 명령문 내에서 리턴 값을 할당하는 대신, 답을 알았을떄 곧바로 리턴한다
  • 조건문(특히, 반복되는 case문)을 다형성으로 대체한다
  • 널 갑3ㅅ을 테스트하는 대신, 널 객체를 생성하여 사용한다

 

루틴 수준 리팩토링

  • 루틴을 추출한다. 메소드를 추출한다
  • 루틴의 코드를 인라인으로 이동시킨다
  • 긴 루틴을 클래스로 변환한다
  • 복잡한 알고리즘 대신 간단한 알고리즘을 사용한다
  • 매개 변수를 추가한다
  • 매개 변수를 제거한다
  • 변경 연산으로부터 쿼리 연산을 분히한다
  • 매개 변수를 이용하여 유사한 루틴을 결합한다
  • 전달되는 매개변수에 따라서 행동하는 루틴을 분리한다
  • 특정한 필드 대신 전체 객체를 전달한다
  • 다운 캐스팅(downcasting)을 캡슐화한다 - 만약 루틴이 객체를 리턴하고 있다면, 일반적으로 루틴이 알고 있는 가장 구체적인 객체의 타입으로 리턴해야한다

 

클래스 구현 리팩토링

  • 값 객체를 참조 객체로 변경한다
  • 참조 객체를 값 객체로 변경한다
  • 가상 루틴들을 데이터 초기화로 대체한다
  • 멤버 루틴이라 데이터의 위치를 변경한다
  • 특화된 코드를 서브 클래스로 추출한다
  • 유사한 코드를 슈퍼 클래스로 결합한다

 

클래스 인터페이스 리팩토링

  • 루틴을 다른 클래스로 이동시킨다
  • 한 클래스를 두개로 변환한다
  • 클래스를 제거한다
  • 위임(delegate)을 숨긴다
  • 미들맨들 제거한다
  • 상속을 위임으로 대체한다 - 만약 클래스가 다른 클래스를 사용해야 하지만, 클래스의 인터페이스를 제어하고 싶다면, 슈퍼클래스를 서브 클래스의 필드로 만들고 응집력있는 추상화를 제공하는 일련의 루틴들을 노출시킨다
  • 위임을 상속으로 대체한다. - 만약 클래스가 위임클래스(멤버 클래스)의 모든 public 루틴들을 노출하고 있다면, 클래스를 사용하는 대신 위임 크래로부터 상속을 받도록 한다
  • 외부 루틴을 소개한다
  • 확장 클래스를 소개한다
  • 노툴된 멤버 변수를 캡슐화한다
  • 변할수 없는 필드에 대한 set()루틴을 제거한다
  • 클래스 외부에서 사용되기 위한 것이 아닌 루틴을 감춘다
  • 사용되지 않는 루틴을 캡슐화한다
  • 슈퍼 클래스와 서브 클래스의 구현이 매우 유사하다면, 이 둘을 결합한다

 

시스템 수준 리팩토링

  • 제어할수 없는 데이터를 위해서 명확한 참조 소스를 생산한다
  • 단방향 클래스 관계를 양방향 클래스 관계로 바꾼다
  • 양방향 클래스 관계를 단방향 클래스 관계로 바꾼다
  • 간단한 생성자 대신 팩토리(factory)메소드를 제공한다 - 타입 코드를 기반으로 객체를 생성해야 하거나 값 객체가 아닌 참조 객체를 다루어야 할때, 팩토리 메소드(루틴)을 사용한다
  • 오류코드를 예외로 대체하거나 그 반대로 한다

 

안전한 리팩토링

  • 리팩토링을 시작하는 코드를 저장한다 - 시작 전에 백업하기
  • 리팩토링을 작게 유지한다
  • 리팩토링은 한번에 하나만 수행한다
  • 여더분이 취할 단계에 대한 목록을 만든다
  • 주차장을 만든다 - 장차 리팩토링을 할 필요가 있는 것의 목록을 만든다
  • 빈번하게 체크 포인트를 설정한다
  • 컴파일러의 경고를 활용한다
  • 다시 테스트한다 - 변경한 코드에 대한 무결성 유지
  • 테스트 케이스를 추가한다
  • 변경 사항을 검토한다
  • 리팩토링의 위험 수준에 따라서 접근 방법을 조절한다

 

리팩토링에 좋지 않은 시기

  • 코드를 작성하고 수정하는 것을 감추는 용도로 리팩토링을 사용하지 않는다 - 제대로 동작하지 않는 코드를 수정하는 것은 리팩토링이 아니다. 개념을 혼동한 것이다.
  • 코드를 재성하는 대신 리팩토링을 하지 않는다 - 리팩토링보다 처음부터 새롭게 시작해야 할때는 리팩토링을 하지 않아야 한다

 

리팩토링 전략

  • 루틴을 추가할때 리팩토링한다
  • 클래스를 추가할때 리팩토링한다
  • 결함을 수정할때 리팩토링한다
  • 오류를 유발한 가능성이 있는 모듈을 대상으로 삼는다
  • 복잡도가 높은 모듈을 대상으로 한다
  • 유지 보수 환경에서는 여러분이 많은 부분을 개선한다
  • 정돈된 코드와 엉성한 코드 간의 인터페이스를 정의한후, 인터페이스를 통해서 코드를 이동시킨다.

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 2. 11. 11:51