함수 추출하기 Extract Function

배경


코드 조각을 찾아 무슨 일을 하는지 파악한 다음, 독립된 함수로 추출하고 목적에 맞는 이름을 붙인다.
코드를 언제 독립된 함수로 묶어야 할지에 관한 의견은 수없이 많다. 먼저, 길이를 기준으로 삼을 수 있다. 가령 함수 하나가 한 화면을 넘어가면 안 된다는 규칙을 떠올릴 수 있다. 재사용성을 기준으로 할 수도 있다. 두 번 이상 사용될 코드는 함수로 만들고, 한 번만 쓰이는 코드는 인라인 상태로 놔두는 것이다.하지만 내 눈에는 '목적과 구현을 분리'하는 방식이 가장 합리적인 가준으로 보인다. 코드를 보고 무슨 일을 하는지 파악하는 데 한참 걸린다면 그 부분을 함수로 추출한 뒤 '무슨 일'에 걸맞는 이름을 짓는다. 이렇게 해두면 나중에 코드를 다시 읽을 때 함수의 목적이 눈에 확 들어오고, 본문 코드(그 함수가 목적을 이루기 위해 구체적으로 수행하는 일)에 대해서는 더 이상 신경 쓸 일이 거의 없다.

절차


  1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다.('어떻게'가 아닌 '무엇을'하는지가 드러나야 한다.)
  • 대상 코드가 함수 호출문 하나처럼 매우 간단하더라도 함수로 뽑아서 목적이 더 잘 드러나는 이름을 붙일 수 있다면 추출한다. 이런 이름이 떠오르지 않는다면 함수로 추출하면 안 된다는 신호다. 하지만 추출한느 과정에서 좋은 이름이 떠오를 수 있으니 처음부터 최선의 이름부터 짓고 시작할 필요는 없다. 일단 함수로 추출해서 사용해보고 효과가 크지 않으면 다시 원래 상태로 인라인해도 된다. 그 과정에서 조금이라도 깨달은 게 있다면 시간 낭비는 아니다. 중첩 함수를 지원하는 언어를 사용한다면 추출한 함수를 원래 함수 안에 중첩시킨다. 그러면 다음 단계에서 수행할 유효범위를 벗어난 변수를 처리하는 작업을 줄일 수 있다. 원래 함수의 바깥으로 꺼내야 할 때가 오면 언제든 함수 옮기기를 적용하면 된다.
  1. 추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여넣는다.
  2. 추출한 코드 중 원본 함수의 지역 변수를 참조하고나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다. 있다면 매개변수로 전달한다.
  • 원본 함수의 중첩 함수로 추출할 때는 이런 문제가 생기지 않는다.
  • 일반적으로 함수에는 지역 변수와 매개변수가 있기 마련이다. 가장 일반적인 처리 방법은 이런 변수를 모두 인수로 전달하는 것이다. 사용은 하지만 값이 바뀌지 않는 변수는 대체로 이렇게 쉽게 처리할 수 있다.
  • 추출한 코드에서만 사용하는 변수가 추출한 함수 밖에 선언되어 있다면 추출한 함수 안에서 선언하도록 수정한다.
  • 추출한 코드 안에서 값이 바뀌는 변수 중에서 값으로 전달되는 것들은 주의해서 처리한다. 이런 변수가 하나뿐이라면 추출한 코드를 질의 함수로 취급해서 그 결과(반환 값)를 해당 변수에 대입한다.
  • 때로는 추출한 코드에서 값을 수정하는 지역 변수가 너무 많을 수 있다. 이럴 때는 함수 추출을 멈추고, 변수쪼개기임시 변수를 질의 함수로 비꾸기와 같은 다른 리펙터링을 적용해서 변수를 사용하는 코드를 단순하게 바꿔본다. 그런 다음 함수 추출을 다시 시도한다.
  1. 변수를 다 처리했다면 컴파일한다.
  • 컴파일되는 언어로 개발 중이라면 변수를 모두 처리하고 나서 한번 컴파일해보자. 제대로 처리하지 못한 변수를 찾는데 도움될 때가 많다.
  1. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다(즉, 추출한 함수로 일을 위임한다)
  2. 테스트한다.
  3. 다른 코드에 방금 추출한 것과 똑같거나 비슷한 코드가 없는지 살핀다. 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.(인라인 코드를 함수 호출로 바꾸기)

예시


before

function printOwing(invoice){
  let outstanding = 0;
  
  console.log("**************");
  console.log("****고객채무****");
  console.log("**************");
  
  //미해결채무(outstanding) 계산
  for(const o of invoice.orders) {
    outstanding += o.mount;
  }
  
  //마감일(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.toLocalDateString()}`);

}

after

function printOwing(invoice){
  
  printBanner();
  
  const outstanding = calculateOutstanding();
  recordDueDate(invoice);
  printDetails(outstanding);
  
  
  function printBanner() {
    console.log("**************");
 	console.log("****고객채무****");
  	console.log("**************");
  }
  
  function calculateOutstanding(invoice) {
     let result = 0;
  	 for(const o of invoice.orders) {
        result += o.mount;
     }
     return result;
  }
  
  function recordDueDate(invoice) {
    const today = Clock.today;
    invoice.dueDate = new Date(today.getFullYear(), today.getMonth()),
    today.getDate() + 30);
  }
  
  function printDetails(nvoice,outstanding) {
    console.log(`고객명: ${invoice.customer}`);
    console.log(`채무액: ${outstanding}`);
    console.log(`마감일: ${invoice.dueDate.toLocalDateString()}`);
  }
 
  function printDetails(i) {
    console.log(`고객명: ${invoice.customer}`);
    console.log(`채무액: ${outstanding}`);
  }
    
}

참조


마틴 파울러 저 리팩터링 2판

좋은 웹페이지 즐겨찾기