본문 바로가기
Study/디자인 패턴의 아름다움

[디자인 패턴의 아름다움] 5. 리팩터링 기법

by Nahwasa 2024. 5. 17.

스터디 메인 페이지

목차

    - ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한대로 적어놓은 것으로, 책에 나오지 않는 내용입니다. 따라서 책에서 말하고자 하는 바와 다를 수 있습니다.

    - 모든 이미지의 출처는 디자인패턴의 아름다움(왕정 저) 책 입니다.

     

     


     

    CHAPTER 05. 리팩터링 기법

    • 리팩터링할 때는 코드의 나쁜 냄새와 설계 결함에 대한 통찰력을 기반으로 설계 원칙, 디자인 패턴, 코딩 규칙 등을 합리적이고 능숙하게 사용해야 하기 때문에 단순히 문제를 해결하는 것보다 코드를 리팩터링할 때 더 많은 능력이 요구된다.

     

    5.1 리팩터링의 네 가지 요소: 목적, 대상, 시기, 방법

    • 목적
      • 리팩터링 : 코드에 대한 이해를 쉽게 하기 위해 소프트웨어의 내부 구조를 개선하는 것으로, 소프트웨어의 외부 동작을 변경하지 않고 수정 비용을 줄이는 것을 목적으로 한다. 
      • 코드 품질을 보장하는 효과적인 수단이며 코드 품질 저하를 효과적으로 방지할 수 있다.
      • 유지 보수 비용이 새로운 코드를 작성하는 것보다 많이 든다면, 리팩터링하기에는 이미 늦었다
      • 고품질 코드는 훌륭한 설계 한 번에 나오는 것이 아니라 반복적인 작업의 결과로 나오는 것이다.
      • 리팩터링 능력은 소프트웨어 엔지니어의 코딩 능력을 측정하는 중요한 수단이다.
    • 대상
      • 대규모 리팩터링 : 시스템, 모듈, 코드 구조, 클래스 간 관계의 리팩터링을 포함하여 최상위 코드설계를 리팩터링
      • 소규모 리팩터링 : 표준 명명, 표준 주석, 초대형 클래스와 함수 제거, 중복 제거, 중복 코드 추출과 같이 주로 클래스, 함수, 변수 수준에서 코드 세부 정보를 리팩터링하는 것을 말한다.
    • 시기
      • 코드가 이미 망가진 후에 모든 문제를 한꺼번에 해결하는 수단으로 리팩터링에 의존하면 안 된다.
      • 지속 가능하고 진화적인 리팩터링 계획을 탐구해야 한다.
      • 단위 테스트와 코드 리뷰를 개발의 일부로 취급하는 것처럼 지속적인 리팩터링을 개발의 일부로 다루어야 한다.
    • 방법
      • 대규모 리팩터링 : 사전에 종합적인 계획을 수립해 질서 있고 단계적으로 진행
      • 소규모 리팩터링 : 영향을 미치는 범위가 작고 변경 사항이 적기 때문에 원한다면 시간이 있을 때마다 가능
      • ☆ 보이스카우트 규칙 : 캠핑장은 처음 왔을 때보다 더 깨끗하게 하고 떠나라 라는 원칙
      • 깨진 유리창 효과 : 저수준 코드가 있다면, 점점 더 많은 사람들이 저수준 코드를 추가하게 된다.

     

     

    5.2 단위 테스트

    • 통합 테스트 : 요청을 시작하여 코드가 실행 결과를 반환하는 전체 경로에 대한 테스트. 대상은 전체 시스템이나 사용자 등록, 로그인 기능과 같은 기능단위의 모듈
    • 단위 테스트 : 코드 수준의 테스트. 대상은 클래스 또는 함수로 제한되며, 해당 대상이 예상대로 실행되는지 테스트하는 방법
    • 예상되거나 예상치 못한 상황에서 코드가 올바르게 실행될 수 있도록 가능한 한 모든 정상 및 비정상 상황을 포괄하는 테스트 케이스를 신중하게 생각하고 설계해야 한다.
    • ☆ 202쪽 테스트 시 " "도 테스트가 필요할 것 같다. 제시된 코드 형태로 보아 isEmpty() 부터 검사한 후 trim()을 하게될 것이므로, " "는 isEmpty에는 안걸리지만 trim은 되고 이후 ""가 되버린다.
    • 단위 테스트 코드를 작성하는 이유
      • 단위 테스트는 프로그래머가 코드에서 버그를 찾는 데 도움이 될 수 있다.
      • 코드 설계에서 문제를 찾는 데 도움이 될 수 있다.
      • 통합 테스트를 보완해준다 : 가능한 모든 상황에 대해 통합 테스트 케이스를 설계하고 테스트하는 것은 비현실적이다.
      • 단위 테스트 코드를 작성하는 과정은 코드 리팩터링 과정에 해당한다.
    • ☆ 스터디 시에 라이브코딩으로 단위 테스트코드를 사용해 리팩토링 하는 과정을 시연했다. 다소 축약된 형태로 진행하긴 했지만, 기본적으로 얘기한건 'TDD, Mock, SOLID 얘기 - 도시 가스 요금 계산' 이 글에서 볼 수 있다.
    • 단위 테스트 코드는 구현이 간단하고 설계를 고려할 필요가 없다. (☆ 단위 테스트 코드는 중복 괜찮음. 괜히 중복 코드 있다고 단위 테스트에다가 설계 넣는게 더 별로라 생각)
    • 테스트 커버리지를 단위 테스트 품질의 유일한 척도로 사용하는 것은 비합리적이다.

     

     

    5.3 코드 테스트 용이성

    • 테스트 용이성은 코드의 품질을 어느 정도 반영하기도 한다.
    • 코드가 외부 시스템, DB, 네트워크, 파일 시스템과 같이 제어할 수 없는 구성 요소에 종속되는 경우 이 종속 관계를 끊을 방법이 필요하며, 이를 모의 구현 또는 Mock이라고 한다. (☆ 위 'TDD, Mock, SOLID 얘기' 글에서 Mock이 어떤건지도 확인할 수 있다.)
    • 단위 테스트는 복잡한 기능에 대해서만 작성하면 되고, 간단한 코드에 대한 단위 테스트는 굳이 작성할 필요가 없다.
    • 클래스의 단위 테스트 코드가 작성하기 쉬운가에 대해서 관건은 클래스의 독립성, 즉 해당 클래스가 높은 응집도와 낮은 결합도를 만족하는지에 달려 있다.
    • 테스트가 불가능한 코드
      • 코드의 출력이 무작위이거나 불확실하며 대부분 시간 및 난수와 관련이 있는 코드
      • 클래스 변수 (static field)를 잘못 사용하면 단위 테스트를 설계하기가 어렵다.
        • ☆ 책에서 계속 전역 변수라고 써져 있으면서 코드 예시는 자바인데, 자바는 전역 변수가 없다.
        • ☆ 221쪽의 경우 Order 어노테이션으로 순서를 지정 가능하긴 하다. 그리고 이런 경우라면 @BeforeEach 같은걸로 매번 초기화해주는게 좋을 것 같다. 
      • 복잡한 상속 관계로 구현된 코드는 테스트가 어렵다.

     

     

    5.4 디커플링

    • 소규모 리팩터링의 주요 목적은 코드의 가독성을 높이는 것인 반면, 대규모 리팩터링의 주요 목적은 디커플링이라고 할 수 있다.
    • 코드를 읽든 코드를 수정하든 높은 응집도와 낮은 결합도 특성을 사용하면 다른 모듈이나 클래스의 코드에 대해 너무 많이 이해할 필요 없이 모듈이나 클래스에 집중할 수 있다.
    • 코드의 일부가 수정되면 전체 코드를 모두 건드려야 하는 상황이 발생한다는 것은 이 프로젝트의 코드 결합도가 너무 높아 디커플링이 필요하다는 것을 의미한다.
    • 코드 디커플링 방법
      • 캡슐화와 추상화로 디커플링
      • 중간 계층으로 디커플링 : 중간 계층을 도입해 이를 사용하여 이전 인터페이스를 감싸는 새 인터페이스를 제공하는 방식
      • 모듈화와 계층화로 디커플링하기
      • SRP, 구현이 아닌 인터페이스 기반, DI, 상속보다 합성, 디미터 법칙

     

     

    댓글