이 글은 리팩터링 2판(마틴 파울러) 책을 참고하였습니다.

순서

I. 배경
II. 절차
III. 예시
IV. 기타


I. 배경

함수가 길어질 때, 혹은 코드가 복잡해 질 때
각 구현부를 작은 함수별로 쪼개는 작업이 필요하다.

언제?  "목적과 구현을 분리"하는 방식이 필요할 때.

코드를 보고 무슨 일을 하는지 파악하기 어렵다면
그 부분을 함수로 추출하여 '무슨 일'에 걸맞는 이름을 짓는다.
*이름이 직관적이어야 구현이 분리될 수 있다.

Q. 짧은 함수들을 많이 호출하면 성능이 느려진다?
A. 함수가 짧으면 캐싱하기 더 쉬워 컴파일러의 최적화에 오히려 유리함.

 


II. 절차

1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다.
('어떻게'가 아닌 '무엇을' 하는지가 명확히 드러나야 한다.)

매우 간단하더라도 함수로 뽑아 목적이 더 잘 드러나는 이름을 붙일 수 있다면 추출한다
떠오르지 않는다면 추출하면 안된다는 신호이다.

 

2.추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여 넣는다.

 

3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나
추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다. 있다면 매개변수로 전달한다.

가장 일반적인 처리 방법은 모두를 인수로 전달하는 것이다.
만약 추출 코드에서만 사용하는 변수가 있다면 지역변수 옮겨온다.
종속성이 생기는 추출함수의 문제는 연계함수의 솔루션으로 해결할 수 있다.
값이 바뀌어 전달되는 것들은 주의하여 반환 값으로 처리하여 전달해준다.

 

4. 함수만 만들고 컴파일 -> 원본 바꾸고 컴파일 + 테스트하기

 


III. 예시


코드 원본

#include <stdio.h>
#include "gtest/gtest.h"

struct Order {
    int amount;
};

struct Invoice {
    char* customer;
    Order orders[2];
    int due_date;
};

void PrintOwing(Invoice* invoice) {
    int outstanding = 0;
    printf("***********************\n");
    printf("**** Customer Owes ****\n");
    printf("***********************\n");
    // calculate outstanding
    for (int i = 0; i < 2; ++i) {
        outstanding += invoice->orders[i].amount;
    }
    // record due date
    invoice->due_date += 3;
    // print details
    printf("name: %s\n", invoice->customer);
    printf("amount: %d\n", outstanding);
    printf("due: %d\n", invoice->due_date);
}

TEST(ExtractFunction, PrintOwing) {
    struct Invoice invoice = {"Martin Fowler", 4, 6, 20210419};
    PrintOwing(&invoice);
}

 


1. PrintCustomerOwesBanner() 함수 추출

// 선언 및 테스트 생략

// 1. 함수를 만든다
// 2. 함수 복붙해서 컴파일 먼저 해본다.
// 3. 함수로 교체하여 컴파일 + 테스트
// 4. 필요없는 주석 제거

void PrintCustomerOwesBanner() { // 함수쪼개기1
    printf("***********************\n");
    printf("**** Customer Owes ****\n");
    printf("***********************\n");
}

void PrintOwing(Invoice* invoice) {
    int outstanding = 0;
    PrintCustomerOwesBanner(); // 함수쪼개기1
    
    // calculate outstanding
    for (int i = 0; i < 2; ++i) {
        outstanding += invoice->orders[i].amount;
    }
    // record due date
    invoice->due_date += 3;
    // print details
    printf("name: %s\n", invoice->customer);
    printf("amount: %d\n", outstanding);
    printf("due: %d\n", invoice->due_date);
}

 


2. printInvoiceDetails() 함수 추출

// 선언 및 테스트 생략

void printCustomerOwesBanner() { // 함수쪼개기1
    printf("***********************\n");
    printf("**** Customer Owes ****\n");
    printf("***********************\n");
}

void printInvoiceDetails(Invoice* invoice) { //함수쪼개기2
    printf("name: %s\n", invoice->customer);
    printf("amount: %d\n", outstanding);
    printf("due: %d\n", invoice->due_date);
}

void PrintOwing(Invoice* invoice) {
    int outstanding = 0;
    PrintCustomerOwesBanner(); // 함수쪼개기1
    
    // calculate outstanding
    for (int i = 0; i < 2; ++i) {
        outstanding += invoice->orders[i].amount;
    }
    // record due date
    invoice->due_date += 3;
    
    printInvoiceDetails(invoice); // 함수쪼개기2
}

 


3. recordDueDate() 함수 추출

// 선언 및 테스트 생략

void printCustomerOwesBanner() { // 함수쪼개기1
    printf("***********************\n");
    printf("**** Customer Owes ****\n");
    printf("***********************\n");
}

void printInvoiceDetails(Invoice* invoice) { // 함수쪼개기2
    printf("name: %s\n", invoice->customer);
    printf("amount: %d\n", outstanding);
    printf("due: %d\n", invoice->due_date);
}

void recordDueDate(Invoice* invoice) { // 함수쪼개기3
    invoice->due_date += 3;
}

void PrintOwing(Invoice* invoice) {
    int outstanding = 0;
    PrintCustomerOwesBanner(); // 함수쪼개기1
    
    // calculate outstanding
    for (int i = 0; i < 2; ++i) {
        outstanding += invoice->orders[i].amount;
    }
    
    recordDueDate(invoice); // 함수쪼개기3
    
    printInvoiceDetails(invoice); // 함수쪼개기2
}

 


4. calculateOutstanding() 함수 추출 + 지역변수 옮겨오기

// 선언 및 테스트 생략

void printCustomerOwesBanner() { // 함수쪼개기1
    printf("***********************\n");
    printf("**** Customer Owes ****\n");
    printf("***********************\n");
}

void printInvoiceDetails(Invoice* invoice) { // 함수쪼개기2
    printf("name: %s\n", invoice->customer);
    printf("amount: %d\n", outstanding);
    printf("due: %d\n", invoice->due_date);
}

void recordDueDate(Invoice* invoice) { // 함수쪼개기3
    invoice->due_date += 3;
}

int calculateOutstanding(Invoice* invoice) { // 함수쪼개기4
    int calculated_outstanding = 0; // 함수쪼개기4 지역변수 옮기기
    for (int i = 0; i < 2; ++i) {
        calculated_outstanding += invoice->orders[i].amount;
    }
    return calculated_outstanding;
}
    
void PrintOwing(Invoice* invoice) {
    
    PrintCustomerOwesBanner(); // 함수쪼개기1
    
    int outstanding = calculateOutstanding(invoice); // 함수쪼개기4 지역변수 옮기기
    
    recordDueDate(invoice); // 함수쪼개기3
    
    printInvoiceDetails(invoice); // 함수쪼개기2
}

 


IV. 기타

값을 반환할 변수가 여러개라면?

묶어서 반환해도 되지만(비추)
임시 변수를 질의 함수로 바꾸거나
변수를 쪼개거나 새로운 문맥으로 옮겨서 하나만 반환하도록 수정해야한다.

 

+ Recent posts