Chapter06. 기본적인 리팩터링

김신영·2023년 7월 15일
0

Refactoring

목록 보기
6/12
post-thumbnail

함수 추출하기 (Extract Function)

목적과 구현을 분리한다는 기준

코드를 보고 무슨 일을 하는지 파악하는데 한참이 걸린다면,
그 부분을 함수로 추출한 뒤 “무슨 일”에 걸맞는 이름을 짓는다.

Extract Function

image

반대 리팩터링: Inline Function

절차

  1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다.
    • 어떻게가 아닌 무엇을 하는지가 드러나야 한다.
  2. 추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여 넣는다.
  3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다. 있다면 매개변수로 전달한다.
    • 지역변수와 매개변수가 있다면, 모두를 인수로 전달한다.
    • 추출한 코드에서만 사용하는 변수가 추출한 함수 밖에 선언되어 있다면, 추출한 함수 안에서 선언하도록 수정한다.
    • 추출한 코드 안에서 값이 바뀌는 변수
  4. 변수를 다 처리했다면 컴파일 한다.
  5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수로 호출하는 문장으로 바꾼다.
    • 즉, 추출한 함수로 일을 위임한다
  6. 테스트한다.
  7. 다른 코드에 방금 추출한 것과 똑같거나 비슷한 코드가 없는지 살핀다.
    • 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.
      • 인라인 코드를 함수 호출로 바꾸기

예시

  • Before
function printOwing(invoice) {
	let outstanding = 0;

	console.log("***********************");
	console.log("******** 고객 채무 ******");
	console.log("***********************");

	// 미해결 채무(outstanding)를 계산한다.
	for (const order of invoice.orders) {
		outstanding += order.amount;
	}

	// 마감일(dueDate)을 기록한다.
	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()}`);
}
  • After
function printOwing(invoice) {
	printBanner();

	const outstanding = calculateOutstaniding(invoice);

	recordDueDate(invoice);
	
	printDetails(invoice, outstanding);
}

function printBanner() {
	console.log("***********************");
	console.log("******** 고객 채무 ******");
	console.log("***********************");
}

function calculateOutstaniding(invoice) {
	let result = 0;
	for (const order of invoice.orders) {
		result += order.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()}`);
}

함수 인라인하기 (Inline Function)

Inline Function

image

반대 리팩터링: Extract Function

절차

  1. 다형 메서드인지 확인한다.
    • ❌ 서브클래스에서 오버라이드하는 메서드는 인라인하면 안된다.
  2. 인라인할 함수를 호출하는 곳을 모두 찾는다.
  3. 각 호출문을 함수 본문으로 교체한다.
  4. 하나씩 교체할 때마다 테스트한다.
  5. 원래 함수를 삭제한다.

예시

  • Before
function getRating(driver) {
	return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}

function moreThanFiveLateDeliveries(driver) {
	return driver.numberOfLateDeliveries > 5;
}
  • After
function getRating(driver) {
	return (driver.numberOfLateDeliveries > 5) ? 2 : 1;
}

변수 추출하기 (Extract Variable)

Extract Variable

image

반대 리팩터링: Inline Variable

절차

  1. 추출하려는 표현식에 부작용은 없는지 확인한다.
  2. 불변 변수를 하나 선언하고 이름을 붙일 표현식의 복제본은 대입한다.
  3. 원본 표현식을 새로 만든 변수로 교체한다.
  4. 테스트 한다.
  5. 표현식을 여러 곳에서 사용한다면 각각을 새로 만든 변수로 교체한다. 하나 교체할 때마다 테스트한다.

예시

  • Before
    return order.quantity * order.itemPrice - 
    	Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + 
    	Math.min(order.quantity * order.itemPrice * 0.1, 100);
  • After
    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 + shipping;

변수 인라인하기 (Inline Variable)

Inline Variable

image

반대 리팩터링: Extract Variable

절차

  1. 변수가 불변으로 선언되지 않았다면, 불변으로 만든 후 테스트한다.
    • 변수에 값이 한 번만 대입되는지 확인 가능
  2. 이 변수를 사용하는 순서대로 코드를 찾아서, 대입문 우변의 코드로 변경한다.
  3. 테스트한다.
  4. 변수 선언문과 대입문을 지운다.
  5. 테스트한다.

예시

  • Before
    let basePrice = order.basePrice;
    return (basePrice > 1000);
  • After
    return order.basePrice > 1000;

함수 선언 바꾸기 (Change Function Declaration)

Change Function Declaration

image

절차

간단한 절차

  1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
  2. 메서드 선언을 원하는 형태로 바꾼다.
  3. 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.
  4. 테스트 한다.

마이그레이션 절차

  1. 함수의 본문을 적절히 리팩터링한다.
  2. 함수 본문을 새로운 함수로 추출한다.
  3. 추출한 함수에 매개변수를 추가해야 한다면, 간단한 절차를 따라 추가한다.
  4. 테스트한다.
  5. 기존 함수를 인라인한다.
  6. 기존 함수를 삭제한다.
  7. 테스트 한다.

예시

  • Before
    function circum(radius) {
      return 2 * Math.PI * radius;
    }
  • After
    function circumference(radius) {
    	return 2 * Math.PI * radius;
    }

변수 캡슐화하기 (Encapsulate Variable)

Encapsulate Variable

image

  • 함수는 데이터보다 다루기가 수월하다.
  • 데이터는 유효범위가 넓어질수록 다루기 어려워진다.
  • 데이터의 유효범위가 넓을수록 캡슐화해야 한다.

자가 캡슐화 (Self Encapsulation)

  • 클래스 안에서 필드를 참조할 때조차 반드시 접근자를 통하게 하자는 주장
  • 개인적으로 자가 캡슐화는 지나치다고 생각함.
    • But, 클래스를 쪼개기 전 단계로써 필드를 자가 캡슐화하는 것은 도움이 된다.

절차

  1. 변수로의 접근과 갱신을 전담하는 캡슐화 함수들을 만든다.
  2. 정적 검사를 수행한다.
  3. 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다.
    • 하나씩 바꿀 때마다 테스트한다.
  4. 변수의 접근 범위를 제한한다.
  5. 테스트한다.
  6. 변수 값이 레코드라면, 레코드 캡슐화하기를 적용할지 고려해본다.

예시

  • Before
    let defaultOwner = {firstName: "Martin", lastName: "Fowler"};
  • After
    let defaultOwner = {firstName: "Martin", lastName: "Fowler"};
    export function defaultOwner() { return defaultOwner; }
    export function setDefaultOwner(arg) { defaultOwnerData = org; }

변수 이름 바꾸기 (Rename Variable)

명확한 프로그래밍의 핵심은 이름짓기다.

Rename Variable

image

절차

  1. 폭넓게 쓰이는 변수라면 변수 캡슐화하기 를 고려한다.
  2. 이름을 바꿀 변수를 참조하는 곳을 모두 찾아서, 하나씩 변경한다.
  3. 테스트한다.

예시

  • Before
    let a = height * width;
  • After
    let area = height * width;

매개변수 객체 만들기 (Introduce Parameter Object)

Introduce Parameter Object

image

절차

  1. 적당한 데이터 구조가 아직 마련되어 있지 않다면, 새로 만든다.
    • 값 객체 (Value Object)
  2. 테스트한다.
  3. 함수 선언 바꾸기 로 새 데이터 구조를 매개변수로 추가한다.
  4. 테스트한다.
  5. 기존 매개변수를 사용하던 코드를 새 데이터 구조의 필드를 사용하도록 바꾼다.
  6. 기존 매개변수를 제거한다.
  7. 테스트한다.

예시

  • Before
    function amountInvoiced(startDate, endDate) { ... }
    function amountReceived(startDate, endDate) { ... }
    function amountOverdue(startDate, endDate) { ... }
  • After
    function amountInvoiced(aDateRange) { ... }
    function amountReceived(aDateRange) { ... }
    function amountOverdue(aDateRange) { ... }

여러 함수를 클래스로 묶기 (Combine Functions into Class)

Combine Functions into Class

image

절차

  1. 함수들이 공유하는 공통 데이터 레코드를 캡슐화한다. (매개변수 객체 만들기)
    • encapsulation record
  2. 공통 레코드를 사용하는 함수 각각을 새 클래스로 옮긴다.
  3. 데이터를 조작하는 로직들은 함수로 추출해서 새 클래스로 옮긴다.
    • extract function
    • move function

참고 글

예시

  • Before
    function base(aReading) { ... }
    function taxableCharge(aReading) { ... }
    function calculateBaseCharge(aReading) { ... }
  • After
    class Reading {
    	base() { ... }
    	taxableCharge() { ... }
    	calculateBaseCharge() { ... }
    }

여러 함수를 변환 함수로 묶기 (Combine Functions into Transform)

Combine Functions into Transform

image

절차

  1. 변환할 레코드를 입력받아서 값을 그대로 반환하는 변환 함수를 만든다.
    • deep copy로 처리해야한다.
    • 변환 함수가 원본 레코드를 바꾸지 않는지 검사하는 테스트가 필요
  2. 묶을 함수 중 함수 하나를 골라서 본문 코드를 변환 함수로 옮기고, 처리 결과를 레코드에 새 필드로 기록한다.
  3. 테스트한다.
  4. 나머지 관련 함수도 1~3번 과정 반복

예시

  • Before
    function base(aReading) { ... }
    function taxableCharge(aReading) { ... }
  • After
    function enrichReading(argReading) {
    	const aReading = _.cloneDeep(argReading);
    	aReading.baseCharge = base(aReading);
    	aReading.taxableCharge = taxableCharge(aReading);
    	return aReading;
    }

단계 쪼개기 (Split Phase)

Split Phase

image

절차

  1. 두 번째 단계에 해당하는 코드를 독립 함수로 추출한다. extract function
  2. 테스트한다.
  3. 중간 데이터 구조를 만들어서 앞에서 추출한 함수의 인수로 추가한다.
  4. 테스트한다.
  5. 추출한 두 번째 단계 함수의 매개변수를 하나씩 검토한다.
    • 그중 첫 번째 단계에서 사용되는 것은 중간 데이터 구조로 옮긴다.
    • 테스트한다.
  6. 첫 번째 단계 코드를 함수로 추출하면서 중간 데이터 구조를 반환하도록 만든다.

참고

예시

  • Before
    const orderData = orderString.split(/\s/);
    const productPrice = priceList[orderData[0].split("-")[1]];
    const orderPrice = parseInt(orderData[1]) * productPrice;
  • After
    const orderRecord = parseOrder(order);
    const orderPrice = price(orderRecord, priceList);
    
    function parseOrder(aString) {
    	const values = aString.split(/\s+/);
    	return ({
    		productId: values[0].split("-")[1],
    		quantity: parseInt(values[1]),
    	});
    }
    
    function price(order, priceList) {
    	return order.quantity * priceList[order.productId]
    }
profile
Hello velog!

0개의 댓글