리펙토링 개론

Updated:

리펙토링 개론

1. 리펙토링은 무엇인가?

1.1 정의

  1. 겉으로 드러나는 기능은 그대로 둔 채, 알아보기 쉽고 수정하기 간편하게 소프트웨어 내부를 수정하는 작업
  2. 리펙토링 기법을 연달아 적용해서 겉으로 드러나는 기능은 그대로 둔 채 소프트웨어 구조를 변경한다.

1.2 강조

  1. 리펙토링의 목적은 소프트웨어를 더 이해하기 쉽고 수정하기 만드는 것이다.
    • 리펙토링을 수행하면 겉으로 드러나는 기능에 거의 또는 영향을 주지 않은 채 각종 기능을 변경할 수 있ㄷ.
    • 성능 최적화와 상반된다.
    • 성능 최적화를 수행할 때도 대개는 기능이 변경되지 않으며 단순히 내부 구조만 바뀐다.
    • 그러나 이 둘의 목적은 다르다.
    • 성능 최적화를 수행하면 코드를 파악하기가 더 어려워질 때가 많지만, 필요한 성능을 얻으려면 어쩔 수 없이 해야 한다.
  2. 리펙토링은 겉으로 드러나는 소프트웨어 기능에 영향을 주지 않는다.

1.3 리펙토링 하는 방법

  • 리펙토링을 적용할 때 기능 추와 리펙토링이라는 별개의 두 작업에 시간을 분배.
  • 기능을 추가할 때에는 코드를 수정하지 말고 기능을 구현하기 위한 코드만 작성한다.
  • 진행 상태를 파악하려면 테스트를 추가하면 된다.
  • 리펙토링할 때에는 코드를 추가하지 말고 코드 구조 개선만 한다.
  • 작업의 일관성을 유지해야 한다.

2. 리펙토링은 왜 해야 하나

2.1 소프트웨어 설계가 개선되니까

  • 코드는 오래될 수록 노후된다. 따라서 처음 설계 설계 구조를 유지하려면 정기적으로 리펙토링을 실시해야 한다.
  • 설계를 개선하는 주요 비법 중 하나는 중복 코드를 없애는 것이다.

2.2 소프트웨어를 이해하기 더 쉬워지니까

  • 공유하는 코드일 수록 남들에게 쉽게 읽히는 코드를 작성하는 것이 중요하다.
  • 낯선 코드를 쉽게 이해할 수 있다.

2.3 버그를 찾기가 쉬워지니까

  • 코드를 파악하기 쉬우면 버그를 발견하기 쉽다.

2.4 프로그래밍 속도가 빨리지니까

  • 깔끔한 설계는 전적으로 신속한 개발을 목적으로 한다.
  • 깔끔한 설계를 유지하려면 리펙토링을 해야한다.

3. 리펙토링은 어떨 때 필요한가

  • 리펙토링을 일부러 시간 내서 하지는 말자
  • 일상적으로 틈틈히 해야 한다
  • 리펙토링은 작성해서 하는 게 아니라, 뭔가 다른 걸 해야 겠는데 리펙토링을 실시하면 그 작업이 쉬워지기 때문에 하는 것이다.

3.1 같은 작업의 삼진 아웃 때

  • 같은 작업을 세 번째 하게 되면 리펙토링을 실시

3.2 기능을 추가할 때

  • 새 기능을 추가할 때 한다.
  • 이 시점에 해야하는 이유 첫번째는 코드를 이해하기 쉽게 만들기 위해서
  • 두 번째는 설계가 지저분해서 어떤 기능을 추가하기 힘들 때

3.3 버그를 수정할 때

  • 버그가 있다는 것은 버그가 있는 줄도 모를 만큼 코드가 지저분하다는 뜻이다.

3.4 코드를 검수할 때

  • 코드를 검수하면서 새로운 아이디어가 떠오를 때가 있다.
코드 수정이 어려운 이유
  1. 코드 알아보기 힘들 때
  2. 중복된 로직이 들어 있을 때
  3. 추가 기능을 넣어야 해서 실행 중인 코드를 변경해야 할 때
  4. 조건문 구조가 복잡할 때

4. 팀장에게 어떻게 말을 꺼내나

  • 품질에 큰 비중을 둔다면 검수 과정에서 리펙토링하는 것이 효율적
  • 일정이 빠듯할 때에는 몰래하자
  • 새 기능을 추가해야 하는데 설계를 수정하기 힘들 때는 먼저 리펙토링을 실시하고 나서 기능을 추가하는 것이 빠르다.

4.1 인다이렉션과 리펙토링

인다이렉션 (Indirection) 은
 - 한 객체가 다른 객체에 작업을 위임하고, 그 객체가 또 다른 객체에 작업을 위임하는 식으로 코드를 짜는 것으로 추정된다.
 - 하나의 로직을 여러 곳에서 공유할 수 있게 해준다.
 - 의도와 구현부를 따로 보여줌으로써 주요 정보를 좀 더 분명하게 드러내는 코드를 만들 수 있게 한다.
 - 하위 클래스를 만들고, 변하는 경우에 참조하게 하는 식으로 수정 부분을 분리할 수 있게 한다.
 - 조건문을 코드화하여 보다 명료하고 유연한 코드를 만들 수 있게 한다.
  • 리펙토링을 적용하면 프로그램에 생각 외로 많은 인다이렉션이 들어가는 것도 어쩌면 당연하다.
  • 리펙토링은 방대한 객체와 장황한 메서드를 잘게 쪼개는 경향이 있다.
  • 인다이렉션은 양날의 검이다. 관리하는 부분이 늘어나고 객체 사이 위임으로 알아보고 힘들어 질 수 있다.
  • 따라서 무조건적인 자세는 안 좋다. 적절한 적용이 필요
4.1.1 인다이렉션 장점
  1. 로직을 공유
    • 두 위치에서 호출되는 하위 메서드나 모든 하위클래스가 공유하는 상위클래스의 메서드 등이 있다.
    • 이런 식으로 하나의 로직을 여러 곳에서 공유할 수 있다.
  2. 의도와 구현부를 따로 나타냄
    • 클래스명과 메서드명을 정해서 의도한 바를 드러낼 수 있고, 클래스나 메서드의 내부 코드를 통해 그 의도를 어떻게 구현했는지 보여줄 수 있다.
    • 내부 코드를 다시 더 잘게 쪼개어 의도적인 측면에서 작성했다면 그 코드의 구조에 대한 대부분의 주요 정보를 잘 드러내는 코드를 작성할 수 있다.
  3. 수정 부분을 분리
    • 한 객체를 두 위치에 사용했는데 두 경우 중 한 상황에 대해 동작을 수정해야 할 때 그 객체를 수정하면 두 상황이 모두 변경될 위험이 있다.
    • 따라서 우선 하위클래스를 만들고 변하는 경우에 참조하게 만들자.
    • 그러면 다른 경우로 예기치 못하게 변할 위험을 감수하지 않고 클래스를 수정할 수 있다.
  4. 조건문을 코드화
    • 객체에는 재정의 메시지라는 우수한 메커니즘이 존재해서 조건문을 유연하면서도 분명하게 표현할 수 있다.
    • 조건문을 메시지로 바꾸면 중복 코드가 줄어들어 명료해지며 동시에 유연성도 높아진다.

4.2 시스템 품질 높이기 위한 방법

  1. 프로그램을 살펴보자 (인다이렉션이 필요한 곳이 얺는 것)
    • 인다이렉션이 필요한 곳에 기존 동작을 건드리지 않고 그 인다이렉션을 넣자.
    • 그러면 프로그램의 품질이 향상되서 가치가 더 높아지며 향후 수정하기도 편해진다.
    • 짐작을 바탕으로 설계하는 것은 틀리기 십상이다.
    • 리펙토링을 하면 이러한 틀리기 쉬운 예상을 잡아준다.
  2. 불필요한 인다이렉션을 찾아 없애는 것.

5. 리펙토링 관련 문제들

5.1 데이터베이스

  • 데이터베이스는 스키마와 강력하게 결합되어 있기 때문에 리펙토링에 걸림돌
  • 이를 위해 객체 모델과 데이터베이스 모델 사이에 별도의 소프트웨어 계층을 두는 방법이 있다.
  • 이렇게 하면 두 모델에 생긴 변경 사항을 따로 유지할 수 있어서 한 모델을 수정할 때 다른 모델은 수정할 필요 없이 중개 계층만 수정하면 된다.
  • 처음부터 별도의 계층을 사용할 필요는 없다.
  • 차후에 객체 모델의 일정 부분들이 변경될 가능성이 높다고 판단되면 그때 생성하면 된다.

5.2 인터페이스 변경

  • 객체의 장점중 하나는 인터페이스를 건드리지 않고 내부의 구현 코드를 수정할 수 있다는 점이다.
  • 그러나 인터페이스의 경우 수정되면 무슨 문제가 생길지 알 수가 없다.
  • 그래서 적어도 인터페이스를 사용하는 부분이 새로운 인터페이스 변경에 맞춰 수정되기 전까지 기존 인터페이스와 새 인터페이스를 모두 그대로 유지시켜야 한다.
  • 기존 인터페이스가 새 인터페이스를 호출하게 하면 된다.
  • 메서드명을 변경할 때는 기존 메서드가 새 메서드를 호출하게 수정해서 계속 유지되게 해야 한다.
  • 메서드 내용 자체를 복사해서는 안된다.
  • 자바의 경우 @deprecated을 작성해서 코드를 사용하지 말라고 알려야 한다.
  • 다른 방법으로는 인터페이스를 published(배포) 타입으로 만들지 말자. 팀원 간의 트러블이 생기지 않는 융통성 있는 리펙토링을 위해 코드 소유권 정책을 수정하자.

5.3 리펙토링을 어렵게 하는 설계를 수정하는 일

  • 설계 자체 오류, 추후 결정, 민감한 부분등 리펙토링으로 모두 해결이 가능

5.4 리펙토링하면 안 되는 상황

  1. 코드를 처음부터 새로 작성해야 할 때
    • 코드가 돌아가지 않는 다는 것은 새로 작성하라는 신호. 기능 구현을 우선으로 하고 추후 완성후 리펙토링
  2. 납기가 임박했을 때에도 삼가야 한다.
  • 언제나 시간이 쫒긴다는 것은 리펙토링을 해야 한다는 신호이다.

6. 리펙토링과 설계

  • 사전 설계는 해야 하지만 완벽함보다 적당한 것 솔루션으로 생각한다.
  • 선택한 솔루션을 구현해나가면서 리펙토링을 통해 사전설계의 단점을 보완해 가는 것이 좋다.

7. 리펙토링과 성능

  • 리펙토링을 실시하면 분명 소프트웨어는 느려진다.
  • 그러나 성능을 더 간단히 조절할 수 있다.
  • 성능을 올리려면 튜닝이 가능하도록 만들어 놓고 나중에 속도가 나오게 튜닝해야 한다.
  • 리펙토링은 이러한 튜닝을 유연하게 만들어준다.

7.1 빠른 소프트웨어를 작성할 수 있는 방법

  1. 철저한 실시간 시스템에 주로 사용되는 시간 분배이다.
    • 설계를 분해하면서 각 구성 요소에 시간이나 메모리 사용량 같은 자원별 예산을 할당하는 것
    • 컴포넌트는 할당된 시간을 상호 교환하는 방식은 허용되지만 예산을 초과해서는 안 된다.
    • 이 방식은 철저한 성능 시간이 핵심이다.
    • 철저한 성능 시간이란 프로그램 실행이 할당된 시간 내에 정확히 실행되어야 함을 뜻한다.
    • 예를 들어 심박 조정기 같은 시스템을 말한다.
  2. 성능에 꾸준한 관심을 갖는다.
    • 모든 개발자는 성능을 높게 유지하기 위해서 수단과 방법을 가리지 말아야 한다.
    • 직관적이지만 효과는 없다.
    • 성능을 높이기 위해 작업도 어려워지고 개발 속도도 떨어진다.
  3. 개발자가 대개 개발 절차 중 후기 단계에 있는 성능 최적화 전까지는 성능에 신경쓰지 않고 프로그램을 잘 쪼개진 방식으로 제작하는 것

8. 리펙토링의 유래