[리팩터링 2판] 6장 - 기본적인 리팩터링
도서

[리팩터링 2판] 6장 - 기본적인 리팩터링

반응형

6장을 읽고나서의 느낌은 기능의 공통점을 잘 찾아내어 같은 공통점끼리 묶고 나누는 것이 중요하다는 것이다.

예를 들어 아래의 그림 밴다이어그램이 하나의 함수에 담겨져 있으면, 정확히 함수 셋 (교집합, 노랑, 보라) 라고 이름을 지어 이름에 맞게 각각 나누어 담는 것이라 느꼈다.

밴다이어그램

 

6.1 함수 추출하기

반대 리팩터링 : 6.2 함수 인라인하기

내용

  • 코드가 하는 일을 '목적과 구현'을 기준으로 분리하여 함수로 추출하고 목적에 맞는 함수 이름을 붙이는 것.

적용

  • 함수 안에 코드가 5~6줄 이상 넘어갈 때 부터.
  • 코드가 하는 일이 무엇인지 파악하기 어려운 경우.
  • 함수안 코드가 1줄이라도 적용.

- 색상을 반전시켜 강조하는 highlight() 메서드의 경우 본문에 reverse() 메서드 호출 1라인만 작성, 

이유는 코드의 목적(강조)과 구현(반전) 사이의 차이가 큼.

 

 

결과

  • 코드를 읽을 때 함수의 목적이 눈에 쉽게 들어옴.
  • 본문 코드에 대해서는 더이상 쓰지 않는다.
  • 함수를 짧게 만들면 캐싱하기가 더 쉽기 때문에 컴파일러가 최적화하는데 유리할 때가 많다.

- 최적화를 할 때는 다음 두 규칙을 따르기 바란다. 첫 번째, 하지마라. 두번째(전문가 한정), 아직 하지마라 - M.A. 잭슨

 

 

글쓴이는 여기서 함수의 이름을 어떻게 잘 짓는지에 대한 강조를 많이 한다.

함수로 뽑아서 목적이 더 잘들어 나는 이름을 붙일 수 있다면 추출한다고 한다. 이런 이름이 떠오르지 않는다면 함수를 추출하면 안되는 신호이지만, 좋은 이름이 이후에 떠오를 수 있으니 추출해서 사용해보고 6.2 함수 인라인을 하면 된다.

 

함수를 추출할 때 중요한 점은 변수의 유효 범위이다. 책에서는 아래의 예시를 들어 놓았다.

코드로 책에서는 설명하지만 코드를 일일이 쓰기에는 비효율 적으로 이해한대로 풀어보려한다.

 

1. 유효범위를 벗어나는 변수가 없을 때

함수 내부에서 변수를 사용하는 코드가 없어 간결하게 추출가능할 때를 말한다.

쉽게 표현하면 변수를 사용하지 않는 경우라고 볼 수 있을 것 같다.

책에서 예시는 console.log를 추출하여 예시를 들었다. 추출을 하여도 함수에 영향이 있는 것이 아니기에 추출한 다음 함수를 호출하였다.

2. 지역 변수를 사용할 때

함수 내부에서 변수를 사용할 때이다. 보통 지역변수를 추출한 함수에 매개변수로 넘겨주면 된다.

물이 흐르는 1자로 된 호스를 ㄱ자로 만드려 할 때, 1자로 된 호스를 반으로 잘라 납땜 하는 느낌이었다.

3. 지역 변수의 값을 변경할 때

앞선 예시보다 복잡한 경우인데, 변수를 하나의 함수에 몰아 넣어 정리 하는 방법이다. 그리고 몰아 넣은 함수를 호출하면서 다시 변수에 넣는 방법으로 설명하고 있다.

 

Before

function printOwing(invoice) {
  printBanner();
  let outstanding  = calculateOutstanding();

  //세부 사항 출력
  console.log(`고객명: ${invoice.customer}`);
  console.log(`채무액: ${outstanding}`);  
}

After

function printOwing(invoice) { 
  printBanner(); 
  let outstanding = calculateOutstanding(); 
  printDetails(outstanding); 

  function printDetails(outstanding) { 
  console.log(`고객명: ${invoice.customer}`); 
  console.log(`채무액: ${outstanding}`); } 
}

 

 

6.2 함수 인라인하기

반대 리팩터링 : 6.1 함수 추출하기

내용

  • 함수 본문이 이름만큼 명확한 경우 쓸데없는 간접 호출 함수이기 때문에 제거한다.

적용

  • 함수 본문이 이름이 명확할 때.
  • 리팩터링 과정에서 잘못 추출된 함수.
  • 간접 호출을 너무 과하게 쓰는 코드.

결과

  • 유용한 것만 남기고 나머지는 제거 가능

 

6.1 에서 하나의 함수를 두개로 나누었다면 6.2는 반대 리팩터링임으로 함수 두개를 합치는 경우로 생각하면 쉽다.

'1+1=2, 2+1=3' 으로 두개의 식으로 표현했다면

'1+1+1=3' 으로 하나의 식으로 표현 하는 것으로 바꿔주는 것이다.

합칠 때 그냥 합쳐 지는게 아니므로 변수나 호출하는 이름 등 확인을 해주어야 한다.

수학 식에서도 합칠 때 숫자 2를 제거한 것 처럼.

 

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; 
}

 

 

6.3 변수 추출하기

반대 리팩터링 : 6.4 변수 인라인하기

내용

  • 표현식이 복잡할 때 지역변수를 활용하여 표현식을 쪼개서 관리
  • 변수가 함수를 벗어난 넓은 문맥에서는 함수로 추출

적용

  • 표현식이 복잡할 때

결과

  • 디버깅에 도움(중단점을 지정하거나 상태를 출력하는 문장을 추가)
  • 코드의 목적이 명확

변수 추출하기에 대해 읽고나서 생각나는게 있었다. 알고리즘 문제에 대한 사람들이 쓴 답을 보면 가끔 빽빽하게 메소드를 사용하여 한줄로 처리된 답을 종종 볼 수있다. 물론 대단하다고 느끼지만, 그 대단함을 느끼기에는 생각보다 시간이 걸린다. 그리고 그냥 이렇게 코드를 써도 되는게 맞을까? 라는 생각도 종종들었는데 아래의 예시처럼 나눌 수 있는 것을 보니 같은 코드라도 참 다르게 느껴졌다.

 

 그리고 책에서 클래스 안에서 라는 예시가 있는데 클래스 안에서는 변수가 아닌 메서드로 추출한다고 한다. 로직과 공유하려 할때 정보를 설명해주는 문맥이 되어 준다고 한다. 예시 코드를 보면서 복잡하면 복잡할 수록 빛을 볼 수 있는 방법이라 생각했다.

 

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;

 

 

6.4 변수 인라인하기

반대 리팩터링 : 6.3 변수 추출하기

내용

  • 불필요한 변수는 인라인 하기

적용

  • 표현식과 변수 이름이 다를 바 없을 때

결과

  • 리팩토링 방해 요소를 줄임

 

6.3 내용과 반대되는 내용으로 불필요한 변수를 줄이는 내용이다. 코드 예시를 보면 쉽게 알 수 있다.

 

 

Before

let basePrice = anOrder.basePrice; 
return (basePrice > 1000);

After

return anOrder.basePrice > 1000;

 

 

6.5 함수 선언 바꾸기

내용

  • 함수는 프로그램을 작은 부분으로 나누는 주된 수단 
  • 함수 이름 변경 (파라미터 추가/삭제 포함)

적용

  • 함수에서 의미가 이해가 되지 않는 함수 이름을 발견하는 경우
  • 매개변수에는 정답이 없지만 문맥을 판단 후 결정

결과

  • 연결부를 잘 정의하면 쉽게 기능추가, 그렇지 않으면 지속적인 방해요인

 

함수를 추출하고 인라인하는 과정을 마이그레이션 절차라 설명하고 이 과정에서 좋은 이름을 짓는것은 변함이 없다.

 

Before

function circum(radius) {...}

After

function circumference(radius) {...}

 

 

6.6 변수 캡슐화하기

내용

  • 데이터는 참조하는 모든 부분을 한 번에 바꿔야 코드가 제대로 작동한다.

적용

  • 유효범위가 넓을수록 다루기 어려울 때 접근을 독점하는 함수를 만드는 식으로 캡슐화 한다.
  • 가변이 아닌 불변데이터는 수정이 불가함으로 캡슐화 할 이유가 적다.

결과

  • 데이터 재구성이 아닌 함수의 재구성으로 더 단순한 작업으로 변환 가능
  • 전/후 검증 및 처리 로직을 쉽게 끼워 넣을 수 있다.

 

캡슐화는 굉장히 유용하지만 과정은 간단하지 않을 때가 많고, 데이터 사용 범위가 넓을수록 적절히 캡슐화 하는게 좋다.

여기서 게터와 세터의 개념이 등장하는데 설명하기 햇갈리는 개념이라 해당 블로그를 참조하니 쉽게 이해 되었다. 

변수를 하나로 캡슐화 하여 컨트롤 및 제어를 하는 좋은 방식이라 느꼈다.

 

Before

let defaultOwner = {firstName: "Martin", lastName: "Fowler"};

After

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

 

 

6.7 변수 이름 바꾸기

내용

  • 명확한 프로그래밍의 핵심은 이름짓기
  • 프로그래머가 하려는 일에 관해 많은것을 설명해준다(단, 이름을 잘 지었을 때)
  • 글쓴이는 이름 앞에 타입을 드러내는 문자를 붙이는 스타일 (매개변수를 가령 aCustomer으로)

적용

  • 변수이름이 명확하지 않을 때
  • 람다식 변수는 쉽게 파악가능하여 한 글자로 된 이름을 짓기도 한다.
  • 값이 영속되는 필드라면 이름에 더 신경써야한다.

결과

  • 변수명만으로 코드가 하는일 파악이 쉽다.

 

Before

let a = height * width;

After

let area = height * width;

 

 

6.8 매개변수 객체 만들기

내용

  • 데이터가 여러 함수로 함께 몰려다니는 경우 구조를 하나로 모아줌 (매개변수 객채 만들기)

적용

  • 여럿 매개변수가 함수로 몰려다니는 패턴이 있는경우

결과

  • 데이터 사이의 관계가 명확해진다.
  • 매개변수 수가 줄어들고 일관성이 높아진다.
  • 궁극적으로 코드를 더 근본적으로 바꾸어준다.

 

여러 매개변수를 하나로 모아주는 클래스를 만들어 주고 관리하는 것이다. 6.6 변수 캡슐화하기랑 비슷하다고 생각한다.

아래 예시는 쉽게 표현 되지만 책에서 과정을 설명해주는 코드들을 보았을 때 쉽지는 않았다. 하지만 하나의 함수에 들어가는 여러개의 매개변수를 줄이니 확실히 깔끔하고 보기가 좋았다.

 

Before

function amountInvoiced(startDate, endDate) {...} 
function amountReceived(startDate, endDate) {...} 
function amountOverdue(startDate, endDate) {...}

After

function amountInvoiced(aDateRange) {...} 
function amountReceived(aDateRange) {...} 
function amountOverdue(aDateRange) {...}

 

 

6.9 여러 함수를 클래스로 묶기

내용

  • 공통 데이터로 사용하는 함수들이 다수 있을 때 묶는다.

적용

  • 공통 데이터를 중심으로 작동하는 함수들을 클래스로 묶는 작업
  • 중첩 함수 보다 클래스를 선호하는 이유는 중첩함수는 테스트하기가 까다로움

결과

  • 함수들이 공유하는 공통 환경을 명확하게 표현가능하다.
  • 각 함수에 전달되는 인수를 줄여서 객체 내부에서의 함수 호출을 간결하게 만들 수 있다.
  • 클라이언트가 객체의 핵심 데이터를 변경 가능.

공용으로 쓰는 물건이 있으면 그 물건을 쓰는 여러사람이 여기저기 들고가는 것 보다. 물건을 가운데 두고 사람을 모은 느낌이다.

데이터가 변경된다고 하면 그 사용자를 한데 모았기 때문에 큰 도움이 된다고 책에서는 설명한다.

 

Before

function base(aReading) {...} 
function taxableCharge(aReading) {...} 
function calculateBaseCharge(aReading) {...}

After

class Reading { 
	base() {...} 
	taxableCharge() {...} 
	calculateBaseCharge() {...} 
}

 

 

6.10 여러 함수를 변환 함수로 묶기

내용

  • 데이터를 입력받아 여러가지 정보를 도출하는 로직을 모아두는 작업
  • 변환 함수 (원본 데이터를 입력받아 필요한 정보를 모두 도출한 뒤, 각각 출력 데이터의 필드에 넣어 반환)

적용

  • 데이터를 입력받아 여러가지 정보를 도출하는 로직이 분산 되는 경우

결과

  • 검색과 갱신을 일관된 장소에서 처리가능
  • 로직 중복 예방
  • 도출 과정을 검토할 때 변환 함수만 살피면 된다.

 

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; 
}

 

 

6.11 단계 쪼개기

내용

  • 코드를 수정해야 할 때 두 대상을 동시에 생각하지 않고 하나에만 집중하기 위해 모듈을 나누는 작업

적용

  • 여러 단계로 분리하기 좋은 코드를 발견할 때

결과

  • 목적이 명확하여 코드 수정이 쉬워짐

 

6.1 함수추출하기와 비슷한 내용이지만 다른 두 대상을 다룬다라는 조건으로 조금 다르게 느껴질 수도 있다. 하지만 맥락은 같다고 생각한다.

 

 

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];
}
반응형

'도서' 카테고리의 다른 글

[리펙터링 2판] 9장 - 데이터 조직화  (0) 2022.02.08
[리펙터링 2판] 8장 - 기능이동  (0) 2022.01.21
[리펙터링 2판] 7장 - 캡슐화  (0) 2021.12.28
리펙터링 2판 - 2~3장  (0) 2021.12.15
리팩터링 2판 - 1장  (0) 2021.12.04