• 2021. 12. 28.

    by. 문익점

    반응형

    리팩터링 2판(마틴 파울러 저)을 읽고 작성한 리뷰입니다.

    1~4장에서는 리팩터링의 간단한 맛을 보고 왜 리팩터링이 필요한 지에 대해 알아보았다면 6장부터는 리팩터링의 기법들을 하나씩 소개한다. 5장에서는 6장부터 소개 할 카탈로그를를 어떤 형식으로 소개 할 것인지 설명한다.

    5.1 리팩터링 설명 방식

    • 이름
    • 리팩토링 기법의 네이밍
    • 개요
    • 리팩토링 기법의 핵심 개념을 개념도와 코드예시로 간단히 표현합니다. 코드가 어떻게 달라지는지 보여준다.이 기법이 어떤 것인지 쉽게 떠올리기 위한 것이다.
    • 배경
    • 리팩토링 기법이 왜 필요한지와 적용하면 안되는 상황을 설명한다.
    • 절차
    • 리팩토링 기법의 과정을 단계별로 제시한다. 제대로된 설명은 `예시'에서 설명한다. 구체적인 순서가 기억이 안날 때 살펴보면 된다.
    • 예시
    • 리팩토링 기법을 실제로 적용하는 간단한 예와 효과를 보여준다

    5장은 책에서 앞으로 설명 할 리팩토링 기법들을 어느순서로 설명할지에 대한 정보일 뿐이다.

    6.0

    6장에서는 드디어 리팩터링 기법들이 등장한다. 가장 기본적이고 많이 사용되는 것들 부터 소개한다.

    6.1 함수 추출하기

    함수 추출하기는 코드 조각을 찾아서 무슨 일을 하는지 파악한 다음 독립된 함수로 추출하는 것이다. 코드를 보고 무슨 일을 하는지 파악이 되어야 한다. 만약 무슨일을 하는지 파악이 도지 않는다면 그 부분을 함수로 추출한 뒤 무슨 일을 하는지 이름을 짓는다. 이렇게 되면 함수 명으로 인하여 코드를 읽을 때 어떠한 일을 함수인지 파악이 가능하여 최종적으로 무슨일을 하는 코드인지 파악이 쉽다.

    간단한 리팩터링 절차

    1. 함수를 추출하고 목적을 드러내는 이름 붙힌다.
    2. *추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사하고 있다면 매개변수로 전달한다.
    3. 원본 코드(추출되기 전)를 추출한 함수로 대체한다.
    4. 테스트한다.
    5. 원본 코드(추출되기 전)와 똑같거나 비슷한 코드가 있는지 살핀 후 있다면 추출한 함수로 대체할지 검토한다

    한번 아래 코드를 리펙터링을 진행해보자.

    function printOwing(invoice) {
        let outstanding = 0;
    
        console.log("*****************")
        console.log("**** 고객 채무 ****")
        console.log("*****************")
    
        // 미해결 채무 계산
        for (const o of invoice.orders) {
            outstanding += o.amount;
        }
    
        // 마감일 기록
        const today = Clock.today;
        invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() +30)
    
        //세부 사항 출력
        console.log(`고객명: ${invoice.customer}`)
        console.log(`채무액: ${outstanding}`)
        console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
    }

    예시: 유효범위를 벗어나는 변수가 없는 경우

    함수를 추출하기에 아주 간단한 코드이다. Clock.today는 시스템 시계를 감싸는 객체이다. Date.now() 호출 시 테스트마다 결과가 달라져서 직접 호출하지 않는다.

    function printOwing_refactor(invoice) {
        let outstanding = 0;
    
            // 배너 출력
        printBanner();
    
        // 미해결 채무 계산
        for (const o of invoice.orders) {
            outstanding += o.amount;
        }
    
        // 마감일 기록
        const today = Clock.today;
        invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() +30)
    
        //세부 사항 출력
        printDetails();
    
        function printDetails() {
            console.log(`고객명: ${invoice.customer}`)
            console.log(`채무액: ${outstanding}`)
            console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
        }
    
        function printBanner() {
            console.log("*****************")
            console.log("**** 고객 채무 ****")
            console.log("*****************")
        }
    }

    이 코드에선 먼저 배너를 출력하는 코드를 추출 할 수 있다. 또한 세부 사항을 출력하는 코드도 추출 할 수 있어 보인다. JS에선 중첩 함수를 지원하므로 invoice와 oustanding에 접근이 가능하기 때문에 매개변수 없이 바로 추출이 가능하다.

    예시: 지역 변수를 사용할 때

    지역변수를 사용하지만 다른 값을 다시 대입하지 않을 때, 즉 수정이 발생하지 않을 때에는 그냥 매개변수로 넘기면 된다.

    function printOwing_refactor(invoice) {
        let outstanding = 0;
        printBanner();
    
        // 미해결 채무 계산
        for (const o of invoice.orders) {
            outstanding += o.amount;
        }
    
        // 마감일 기록
        const today = Clock.today;
        invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() +30)
    
        //세부 사항 출력
        printDetails(invoice, outstanding);
    
        function printDetails(invoice, outstanding) {
            console.log(`고객명: ${invoice.customer}`)
            console.log(`채무액: ${outstanding}`)
            console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
        }
    
        function printBanner() {
            console.log("*****************")
            console.log("**** 고객 채무 ****")
            console.log("*****************")
        }
    }

    사용하는 지역 변수가 배열과 같은 데이터 구조여서 필드나 요소를 추가하는 경우라면 매개변수로 넘긴 뒤 값을 수정하게 할 수 있다. 여기서는 마감일을 설정하는 함수를 추출한다.

    function printOwing_refactor(invoice) {
        let outstanding = 0;
        printBanner();
    
        // 미해결 채무 계산
        for (const o of invoice.orders) {
            outstanding += o.amount;
        }
    
        // 마감일 기록
        recordDueDate();
    
        //세부 사항 출력
        printDetails(invoice, outstanding);
    
        function recordDueDate(invoice) {
            const today = Clock.today;
            invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30)
        }
    
        function printDetails(invoice, outstanding) {
            console.log(`고객명: ${invoice.customer}`)
            console.log(`채무액: ${outstanding}`)
            console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
        }
    
        function printBanner() {
            console.log("*****************")
            console.log("**** 고객 채무 ****")
            console.log("*****************")
        }
    }

    예시: 지역 변수의 값을 변경할 때

    지역 변수에 새로운 값을 대입하게 되는 경우라면 복잡하다. 지금의 케이스는 추출한 함수에 있는 변수가 추출한 함수 밖에서 사용될 때다. 이럴 떄는 변수값을 리턴해야 한다.

    // 미해결 채무 계산
    let outstanding = 0;
    for (const o of invoice.orders) {
        outstanding += o.amount;
    }

    outstanding 변수에 미해결 채무를 더하는 코드이다. 이 함수를 추출한다면 outstanding은 밖에서 사용되기 때문에 리턴처리 해준뒤 사용하면 된다.

    최종적으로 리팩터링 된 코드이다.

    function printOwing_refactor(invoice) {
        printBanner();
    
        // 미해결 채무 계산
        const outstanding = calculateOutstanding();
    
        // 마감일 기록
        recordDueDate();
    
        //세부 사항 출력
        printDetails(invoice, outstanding);
    
        function calculateOutstanding() {
            let result = 0;
            for (const o of invoice.orders) {
                result += o.amount;
            }
            return result;
        }
    
        function recordDueDate(invoice) {
            const today = Clock.today;
            invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30)
        }
    
        function printDetails(invoice, outstanding) {
            console.log(`고객명: ${invoice.customer}`)
            console.log(`채무액: ${outstanding}`)
            console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
        }
    
        function printBanner() {
            console.log("*****************")
            console.log("**** 고객 채무 ****")
            console.log("*****************")
        }
    }

    6.2 함수 인라인하기

    함수를 추출하면 그 목적을 알 수 있어 코드가 명료해진다고 했다. 하지만 때로는 함수 본문이 명확하여 따로 이름을 지어 추출하지 않아도 되는 경우도 있다. 또한 호출을 너무 과하게 사용되어 위임 관계가 복잡하게 얽혀 있으면 인라인해야 된다. (추출과 반대되는 개념)

    간단한 리팩터링 절차

    1. 다형 매소드인지 확인한다.
    2. 인라인할 함수를 호출하는 곳을 모두 찾는다.
    3. 추출된 함수에서 본문을 모두 대체한다.
    4. 이때 하나 씩 교체 할 때마다 테스트한다.
    5. 기존 추출된 함수는 삭제한다.

    예시

    아래는 아주 간단한 예시이다.

    function rating(aDriver) {
      return moreThanFiveLateDeliveries(aDriver) ? 2 : 1;
    }
    function moreThanFiveLateDeliveries(aDriver) {
      return aDriver.numberOfLateDeliveries > 5;
    }

    moreThanFiveLateDeliveries 함수는 너무 간단해서 굳이 함수로 추출되어 있을 필요가 없다. 이러한 코드는 단순하게 잘라서 붙혀넣으면 된다. 절차에 따라서 인라인 작업을 진행한다.

    function rating_refactor(aDriver) {
      aDriver.numberOfLateDeliveries > 5 ? 2 : 1;
    }

    위와 같이 쉬운 경우만 있는 것은 아니다. 아래 코드를 보자. 단순히 잘라 붙혀서는 안되고 약간의 수정이 발생한다.

    function reportLines(aCustomer) {
      const lines = [];
      gatherCustomerDate(lines, aCustomer);
      return lines;
    }
    function gatherCustomerDate(out, aCustoemr) {
      out.push(["name", aCustoemr.name]);
      out.push(["location", aCustoemr.location]);
    }

    이러한 코드를 인라인 하려면 gatherCustomerDate에서 실행하는 코드들을 reportLines로 가지고 온 뒤 매개변수를 수정해주고 gatherCustomerDate 함수를 제거해준다.

    function reportLines_refactor(aCustomer) {
      const lines = [];
      lines.push(["name", aCustoemr.name]);
      lines.push(["location", aCustoemr.location]);
      return lines;
    }

    6.3 변수 추출하기

    한 표현식이 너무 복잡해서 이해하기 어려울 경우에 사용한다. 그러면 복잡한 로직을 구성하는 단계마다 이름(변수 이름)을 붙일 수 있어서 코드의 목적을 명확하게 드러낸다.

    간단한 리팩터링 절차

    1. 추출 시에 발생하는 부작용이 없는 체크한다.
    2. 불변 변수를 선언하고 표현식의 복제본은 대입한다.
    3. 원본을 새로만든 불변 변수로 대체한다.
    4. 테스트한다.
    5. 표현식을 여러군데에서 사용했다면 각각을 새로만든 불변변수로 대체한다. 교체할 때마다 테스트 한다.

    예시

    function price(order) {
      // 가격 = 기본 가격 - 수량 할인 + 배송비
      return (
        order.quantity * order.itemPrice -
        Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
        Math.min(order.quantity * order.itemPrice * 0.1, 100)
      );
    }

    간단한 계산식 코드 이지만 코드 파악을 위해선 기본 가격 = 상품 가격 * 수량 임을 파악해내야 한다. 천천히 하나하나 씩 변수로 추출해본다.

    function price_refactor(order) {
      const basePrice = order.quantity * order.itemPrice;
      const quantityDiscount =
        Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
      const shipping = Math.min(basePrice * 0.1, 100);
      return basePrice - quantityDiscount;
    }

    basePrice라는 불변 변수를 만든 뒤 표현식을 대입한다. 그 다음 원본을 불변변수로 대체한다. 이 표현식이 여러 군데에서 사용되는지를 체크한 다음 각각을 불변변수로 대체한다. 여기선 두 번 변경되었다. 마지막으로 할인과 배송비도 변수로 따로 추출해준다. 좀 더 코드의 목적을 쉽게 이해 할 수 있도록 리팩터링 됬다.

    클래스안에서 메소드를 수정하는 경우라면 맴버 변수로 빼는 것이 아닌 get 메소드를 이용한다.

    ...
    get basePrice() 
    get quantityDiscount()
    get shipping()
    ...

    6.4 변수 인라인

    변수를 추출하지 않아도 되는 경우도 있다. 또한 그 이름이 원래 표현식과 다를 바 없을 때도 있다. 호출한 변수가 주변 코드를 리팩터링하는데 방해가 된다면 인라인 해야한다. (추출과 반대되는 개념)

    6.5 함수 선언 바꾸기

    함수는 프로그램을 작은 부분으로 나누는 주된 수단이다. 이러한 함수들은 소프트웨어 시스템의 여냐결 역할을 한다. 이 연결부를 잘 정의하면 새로운 부분을 추가하기도 수정하기도 쉽다. 이러한 연결부에서 가장 중요한 요소는 함수의 이름이다. 호출문만 보고도 무슨 일을 하는지 파악할 수 있어야 한다.

    반응형

    '코딩' 카테고리의 다른 글

    Javascript 엔진  (0) 2022.01.04
    리액트 살펴보기 - 정보  (0) 2021.12.29
    리팩터링 2판 리뷰 - 4장  (0) 2021.12.24
    리팩터링 2판 리뷰 - 3장  (0) 2021.12.22
    리팩터링 2판 리뷰 - 2장  (0) 2021.12.22