리팩터링 Chapter 8 - 기능 이동
이번 챕터는 프로그램 요소를 생성 혹은 제거하는 것에 더해 클래스나 모듈이동을 주로 설명합니다.
이 챕터에서 가장 중요한 작업은 테스팅이라고 생각합니다.
필드나 함수를 클래스 간의 이동 할 경우 충돌이 발생할 수 있습니다.
그래서 한 단계씩 하면서 꼭 테스트를 진행해야합니다. 그럼 시작해보겠습니다.
8-1 함수 옮기기
class Account {
get overdraftCharge() {...}
class AccountType {
get overdraftCharge() {...}
함수를 이동하는 기준은 모듈화를 얼마나 잘 시켰는지입니다.
모듈화는 프로그램을 수정하려 할 때 해당 기능의 작은 부분만 이해해도 가능하게 해주는 능력입니다.
예를 들어 어떤 함수가 자신이 속한 모듈 A의 요소들보다 다른 모듈 B의 요소들을 더 많이 참조한다면 B로 옮겨줘야합니다.
방법
선택한 함수가 현재 컨텍스트에서 사용 중인 모든 프로그램 요소를 살펴봅니다.
선택한 함수가 다형 메서드인지 확인합니다.
선택한 함수를 타깃 컨텍스트로 복사합니다.
정적 분석을 수행합니다. 소스 컨텍스트에서 타깃 함수를 참조할 방법을 찾아 반영합니다.
소스 함수를 타깃 함수의 위임 함수가 되도록 수정합니다.
8-2 필드 옮기기
class Customer {
get plan() {return this._discountRate}
get discountRate() {return this._discountRate;}
class CustomerType {
get discountRate() {return this.plan.discountRate;}
}
필드 옮기기는 어떻게 보면 데이터 구조를 바꾼다는 의미와 동일합니다.데이터 구조가 잘못 설계되는 경우에는 나중에 큰 골칫덩어리가 될 수 있습니다.이러한 이유때문에 데이터 구조가 잘못 되었을 경우에는 고치고 일을 진행하시는 것을 추천드립니다.
방법
소스 필드가 캡슐화되어 있지 않다면 캡슐화합니다.
타깃 객체에 필드를 생성합니다. 정적 검사를 수행합니다.
소스 객체에서 타깃 객체를 참조할 수 있는지 확인합니다.
접근자들이 타깃 필드를 사용하도록 수정합니다. 소스 필드를 제거합니다.
8-3 문장을 함수로 옮기기
result.push(`<p>title: ${person.photo.title}</p>`);
result.concat(photoData(person.photo));
function photoData(aPhoto) {
return
[
`<p>location: ${aPhoto.location}</p>`,
`<p>date: ${aPhoto.date.toDateString()}</p>`,
];
}
result.concat(photoData(person.photo));
functionphotoData(aPhoto) {
return [
`<p>title: ${aPhoto.title}</p>`,
`<p>location: ${aPhoto.location}</p>`,
`<p>date: ${aPhoto.date.toDateString()}</p>`,
];
}
문장을 함수로 옮기기는 중복 코드 제거에 아주 유용합니다.
그러나 아무렇게나 문장을 뭉쳐놓게되면 나중에 여러 클래스가 다른 행동을 하고 싶을 경우에는 불편합니다.
상황을 잘 이해하고 적용하시길바랍니다.
방법
반복코드를 문장 슬라이드하기로 함수 주변으로 옮깁니다.
타깃 함수 호출이 한 번 이상이면 피호출 함수로 복사합니다.
타깃 함수 호출이 두 번 이상인 경우에는 타깃 함수 호출 부분과 그 함수로 옮기려는 문장을 함께 함수로 추출합니다.
하나씩 호출하여 테스트를 진행합니다.
8-4 문장을 호출한 곳으로 옮기기
emitPhotoData(outStream, person.photo);
functionemitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`);
outStream.write(`<p>location: ${photo.location}</p>\n`);
}
appliesToMass = states.includes("MA");
8-3와는 반대 기법으로 여러 곳에서 사용하던 기능을 일부 호출자에게는 다르게 동작하도록 바꿔야하는 경우에 사용합니다.
그래서 우리는 달라지는 동작을 호출자로 옮긴 뒤에는 필요할 때마다 독립적으로 수정할 수 있게됩니다.
8-5 인라인 코드를 함수 호출로 바꾸기
let appliesToMass = false;
for(const sof states) {
if (s === "MA") appliesToMass = true;
}
emitPhotoData(outStream, person.photo);
outStream.write(`<p>location: ${person.photo.location}</p>\n`);
functionemitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`);
}
이미 존재하는 함수와 똑같은 일을 하는 코드를 발견 할 때는 따로 함수를 만드는게 효율적입니다.
비슷한 코드에는 적용시키면 안됩니다.
8-6 문장 슬라이드하기
const pricingPlan = retrievePricingPlan();
const order = retreiveOrder();
let charge;
const chargePerUnit = pricingPlan.unit;
const pricingPlan = retrievePricingPlan();
const chargePerUnit = pricingPlan.unit;
const order = retreiveOrder();
let charge;
이 방법은 오로지 가독성을 위해서 비슷한 내용을 묶어놓는 기능입니다.
다른 사람이 코드를 볼때 코드를 이해하기 쉬워집니다.
문장들을 이동할 목표 위치를 찾습니다. 그 사이에 문장이 사용된다면 혹시 달라지는 점이 없는지 확인합니다.
없는 경우 코드 조각을 원래 위치에서 잘라내어 목표 위치에 붙여 넣습니다.
8-7 반복문 쪼개기
let averageAge = 0;
let totalSalary = 0;
for (const pof people) {
averageAge += p.age;
totalSalary += p.salary;
}
averageAge = averageAge / people.length;
let totalSalary = 0;
for (const pof people) {
totalSalary += p.salary;
}
let averageAge = 0;
for (const pof people) {
averageAge += p.age;
}
averageAge = averageAge / people.length;
반복문 내에서 두 개 이상의 작업을 하게되는 경우 두 개의 작업을 이해해야한다는 단점이 있습니다.
위와 같이 반복문을 분리하면 오직 수정할 동작만 이해하고 수정하면 되기 때문에 편하다는 장점이 있습니다.
방법
반복문을 복제해 두 개를 생성합니다.
반복문이 중복되어 생기는 부수효과를 파악합니다.
테스트 후 함수 추출을 고민해봅니다.
8-8 반복문을 파이프라인으로 바꾸기
const names = [];
for (const iof input) {
if (i.job === "programmer")
names.push(i.name);
}
const names = input
.filter(i => i.job === "programmer")
.map(i => i.name)
;
위의 파이프라인은 java8버전부터 함수형 프로그래밍이 유행하면서 적용된 걸로 알고있습니다.
앞서 반복문을 추출하면 위의 파이프라인을 이용해 작성하시면 됩니다.
대표적으로 map, filter가 있습니다.
저도 물론 파이프라인 방식으로 코드를 작성합니다. 가독성이 좋아지고 이해하기가 훨씬 쉬워집니다.
8-9 죽은 코드 제거하기
코드가 사용되지 않으면 무조건 지워버려야합니다.
꼭 필요하면 버전 관리를 통해 다시 불러올 수 있으므로 삭제하는게 맞습니다.
스터디를 진행하다가 컨텍스트의 개념과 함수형 프로그래밍이라는 주제로 이야기를 나눴습니다.
일단 컨텍스트는 개념적으로 쉬웠지만 머리로 이해하는 것이 어려웠습니다.
Application의 컨텍스트라는 것은 현재 App의 상태를 관리하고 활용하는 공간입니다.
처음에는 메모리같은 느낌인가?라고 생각했지만 아니였습니다.
JPA에서 흔히 말하는 영속성 컨텍스트를 생각해봤습니다.
영속성 컨텍스트는 DB앞에서 객체들의 상태들을 관리해주는 공간이고 이것을 App에서 사용할 수 있도록 만들어진 공간입니다.
아직까지 컨텍스트에 대한 이해가 부족하지만 느낌은 잡혔습니다.
다음으로 함수형 프로그래밍입니다.
주니어 개발자인 저는 항상 절차지향적 프로그래밍을 선호했고 이해하기 쉬웠습니다.그러나 Java뿐만 아니라 다양한 언어들이 함수 지향으로 바뀌어가는 추세입니다.함수란 입력과 출력이 있는 것입니다. 함수형 프로그래밍은 조금 다릅니다.함수형 프로그래밍은 입력 1개 출력 1개로 어떠한 것에도 영향을 받으면 안된다는 뜻입니다.즉 if, for, switch문을 사용하면 안됩니다. 그러면 사이드 이펙트가 생기기 때문입니다.솔직히 위의 방식으로는 프로그래밍을 해본적도 없고 제대로 작성된 함수형 프로그래밍을 본 적이 없습니다.
이렇게 스터디를 끝마쳤습니다. 그래도 이번 스터디를 하면서 멘토님꼐서 모르는 것을 설명해주셔서 많이 배웠습니다.
리팩터링이란 책이 많이 어렵지만 이것을 읽지 못하고 이해하지못한다면 훌륭한 개발자가 될 수 없습니다.
그래서 끝까지 꼭 완주하고 싶습니다. 긴 글 읽어주셔서 감사합니다.