개요
함수는 프로그램을 작은 부분으로 나누는 주된 수단이다.
이런 함수 선언부는 소프트웨어의 연결부 역할을 한다. 따라서 선언부를 잘 정의할 필요가 있다.
가장 중요한 건 함수 이름이다. 이름이 좋으면 구현부를 볼 필요가 없다.
이름은 IDE기능으로 쉽게 변경가능하니 좋은 이름이 있다면 주저하지 않고 바꾸자
함수 매개변수는 함수를 사용하는 문맥을 설정한다.
전화번호 포맷팅 함수가 있는데 매개변수로 사람 타입을 받아 그 속 전화번호를 포맷팅한다고 하면, 활용도가 적다. 전화번호만 인자로 받는다면 어떤 전화번호라도 활용이 가능하다.
절차
간단한 절차와 마이그레이션 절차가 있다.
간단한 절차를 최대한 사용하되 불가능하다면 마이그레이션 절차를 사용한다.
간단한 절차
- 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
- 메서드 선언을 원하는 형태로 바꾼다.
- 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.
- 테스트한다.
마이그레이션 절차
- 이어지는 추출 단계를 수월하게 만들어야 한다면 함수의 본문을 적절히 리팩터링한다.
- 함수 본문을 새로운 함수로 추출한다.
- 추출한 함수에 매개변수를 추가해야 한다면 '간단한 절차'를 따라 추가한다.
- 테스트한다.
- 기존 함수를 인라인한다.
- 이름을 임시로 붙여뒀다면 함수 선언 바꾸기를 한 번 더 적용해서 원래 이름으로 되돌린다.
- 테스트한다.
상속 구조 속에 있는 클래스 메서드 변경할 때는 다른 서브 클래스들에도 변경이 반영되어야 한다. 이때 복잡하기에 간접 호출 방식으로 우회하는 방법도 쓴다.
덕 타이핑 처럼 슈퍼클래스와의 연결을 제공하지 않는 언어라면 전달 메서드를 모든 구현 클래스에 추가해야 한다.
예시 : 함수 이름 바꾸기 간단한 절차
function circum(radius){ return 2 * Math.PI * radius; } |
function circumference(radius){ return 2 * Math.PI * radius; } |
함수 이름 변경 핵심은 이 함수를 호출하는 부분을 얼마나 쉽게 찾는가에 있다.
정적 타입 언어와 좋은 IDE 조합이라면 손 쉽게 함수 이름 바꾸기를 처리할 수 있다.
정적 타입 언어가 아니라면 상당히 귀찮아지고 버그 발생 가능성이 높아진다.
추가로 상속관계에 있는 클래스라면 다른 메서드도 수정해야 된다.
리팩터링은 항상 문제를 작게 쪼개서 정복한다.
매개변수 추가/삭제 같은 변경은 반드시 함수 호출 부분을 모두 찾아 수정한 뒤 수행한다.
간단한 방식은 함수 이름 바꾸기로 코드에 영향이 적을 때 적합하다.
예시: 함수 이름 바꾸기 마이그레이션 절차
function circum(radius){ return 2 * Math.PI * radius; } |
//마이그레이션 절차 function circum(radius){ return circumference(radius); } function circumference(radius) { return 2 * Math.PI * radius; } |
이 상태에서 정상 동작을 테스트하고, circum()호출하는 부분을 전부 찾아 circumference()로 바꾼다. |
이 방식은 API같은 선언부를 고칠 수 없는 외부 코드 사용 부분을 리팩터링 하기 좋다.
circumference()를 만들어 두고, circum()에 @Deprecated 같은 표시를 해두어 circumference() 호출하도록 유도를 한다. 이후 모든 클라이언트가 변경 했으면 circum()을 지운다.
예시: 매개변수 추가하기
예약 시 우선순위 큐를 지원하라는 요구사항이 들어왔다.
기존 큐와 우선순위 큐 중 어떤 것으로 할지 결정 매개변수를 추가해야 한다.
class Book{ addReservation(customer){ this._reservations.push(customer); } } |
class Book{ addReservation(customer){ this.zz_addReservation(customer); } //임시이름 zz_addReservation(customer) { this._reservations.push(customer); } } |
class Book{ addReservation(customer){ this.zz_addReservation(customer, false); } //매개변수 추가 zz_addReservation(customer,isPriority) { //자바스크립트 한정 assert 테스트 assert(isPriority === true || isPriority === false); this._reservations.push(customer); } } |
이제 호출 부분을 전부 zz_addReservation()로 변경 한 후 zz_addReservation() 함수 이름을 기존 함수 이름으로 바꾼다. |
class Book{ addReservation(customer,isPriority) { this._reservations.push(customer); } } |
예시: 매개변수를 속성으로 바꾸기
고객이 뉴잉글랜드 살고있는지 파악하는 함수
/* 현재 문제는 매개변수를 객체로 받고 있다. 재사용성이 제한된다.*/ function inNewEngland(aCustomer){ return ["MA","CT","ME","VT","NH","RI"].includes(aCustomer.address.state); } //호출부 const newEnglanders = someCustomers.filter(c=>inNewEngland(c)); |
function inNewEngland(aCustomer){ const stateCode = aCustomer.address.state; return ["MA","CT","ME","VT","NH","RI"].includes(stateCode); } |
function inNewEngland(aCustomer){ const stateCode = aCustomer.address.state; return newFunction(stateCode); } //임시 함수 추출 function newFunction(stateCode) { return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode); } |
function inNewEngland(aCustomer){ //변수 인라인하기 return newFunction(aCustomer.address.state); } function newFunction(stateCode) { return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode); } |
//호출부 변경 const newEnglanders = someCustomers.filter(c=>newFunction(c)); |
//함수 선언 바꾸기로 새함수이름을 기존함수이름으로 변경 function inNewEngland(stateCode) { return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode); } //호출부 변경 const newEnglanders = someCustomers.filter(c=>inNewEngland(c)); |
'IT책, 강의 > 리팩터링' 카테고리의 다른 글
06 - 기본적인 리펙터링 - 매개변수 객체 만들기 (0) | 2023.07.20 |
---|---|
06 - 기본적인 리펙터링 - 변수 캡슐화하기 (0) | 2023.07.19 |
06 - 기본적인 리펙터링 - 변수 인라인하기 (0) | 2023.07.17 |
06 - 기본적인 리펙터링 - 변수 추출하기 (0) | 2023.07.16 |
06 - 기본적인 리펙터링 - 함수 인라인하기 (0) | 2023.07.15 |