리팩터링 정리 chapter7
7장 캡슐화
모듈을 분리하는 가장 중요한 기준은 시스템에서 각 모듈이 자신을 제외한 다른 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기느냐에 있을 것이다.
7장에 나오는 리팩터링 기법들
- 대표적 형태의 데이터구조: 레코드 캡슐화, 컬렉션 캡슐화
- 기본형 데이터의 경우: 기본형을 객체로 바꾸기
- 정확한 순서로 계산이 필요할 때: 임시 변수를 질의 함수로 바꾸기
- 추출/인라인하기의 클래스 버전: 클래스 추출하기, 클래스 인라인하기
- 클래스 연결 관계를 은닉: 위임숨기기
- 위임 숨기기의 반대기법: 중개자 제거하기
- 함수도 구현을 캡슐화할 때: 함수 추출하기, 알고리즘 교체하기
7.1 레코드 캡슐화하기
절차
- 레코드를 담은 변수를 캡슐화한다.
-> 레코드를 캡슐화하는 함수의 이름은 검색하기 쉽게 지어준다. - 레코드를 감싼 단순한 클래스로 해당 변수의 내용을 교체한다. 이 클래스에 원본 레코드를 반환하는 접근자도 정의하고, 변수를 캡슐화하는 함수들이 이 접근자를 사용하도록 수정한다.
- 테스트한다.
- 원본 레코드 대신 새로 정의한 클래스 타입의 객체를 반환하는 함수들을 새로 만든다.
- 레코드를 반환하는 예전 함수를 사용하는 코드를 4단계에서 만든 새 함수를 사용하도록 바꾼다.
예시
// before
organization = {name: "애크미 구스베리", country: "GB"}
// after
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get name() {return this._name;}
set name(arg) {this._name = arg;}
get country() {return this._country;}
set country(arg) {this._country = arg;}
7.2 컬렉션 캡슐화하기
절차
- 아직 컬렉션을 캡슐화하지 않았다면 변수 캡슐화하기부터 한다.
- 컬렉션에 원소를 추가/제거하는 함수를 추가한다
-> 컬렉션 자체를 통째로 바꾸는 세터는 제거한다. 세터를 제거할 수 없다면 인수로 받은 컬렉션을 복제해 저장하도록 한다. - 정적 검사를 수행한다.
- 컬렉션을 참조하는 부분을 모두 찾는다. 컬렉션의 변경자를 호출하는 코드가 모두 앞에서 추가한 추가/제거 함수를 호출하도록 수정한다. 하나씩 수정할 때마다 테스트한다.
- 컬렉션 게터를 수정해서 원본 내용을 수정할 수 없는 읽기전용 프락시나 복제본을 반환하게 한다.
- 테스트한다.
예시
// before
class Person {
get courses() {return this._courses;}
set courses(aList) {this._courses = aList;}
}
// after
class Person {
get courses() {return this._courses.slice()}
addCourse(aCourse) {}
removeCourse(aCourse) {}
}
7.3 기본형을 객체로 바꾸기
절차
- 아직 변수를 캡슐화하지 않았다면 캡슐화한다.
- 단순한 값 클래스를 만든다. 생성자는 기존 값을 인수로 받아서 저장하고, 이 값을 반환하는 게터를 추가한다.
- 정적 검사를 수행한다.
- 값 클래스의 인스턴스를 새로 만들어서 필드에 저장하도록 세터를 수정한다. 이미 있다면 필드의 타입을 적절히 변경한다.
- 새로 만든 클래스의 게터를 호출한 결과를 반환하도록 게터를 수정한다.
- 테스트한다.
- 함수의 이름을 바꾸면 원본 접근자의 동작을 더 잘 드러낼 수 있는지 검토한다.
예시
// before
orders.filter(o => "high" === o.priority || "rush" === o.priority)
// after
orders.filter(o => o.priority.higherTahn(new Priority("normal")))
7.4 임시 변수를 질의 함수로 바꾸기
절차
- 변수가 사용되기 전에 값이 확실히 결정되는지, 변수를 사용할 때마다 계산 로직이 매번 다른 결과를 내지는 않는지 확인한다.
- 읽기전용으로 만들 수 있는 변수는 읽기전용으로 만든다.
- 테스트한다.
- 변수 대입문을 함수로 추출한다.
-> 변수와 함수가 같은 이름을 가질 수 없다면 함수 이름을 임시로 짓는다. 또한, 추출한 함수가 부수효과를 일으키지 않는지 확인한다. 부수효과가 있다면 질의 함수와 변경 함수 분리하기로 대처한다. - 테스트한다.
- 변수 인라인하기로 임시변수를 제거한다.
예시
// before
const basePrice = this._quantity * this._itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
} else {
return basePrice * 0.98;
}
// after
get basePrice() {this._quantity * this._itemPrice;}
if (this.basePrice > 1000) {
return this.basePrice * 0.95;
} else {
return this.basePrice * 0.98;
}
7.5 클래스 추출하기
절차
- 클래스의 역할을 분리할 방법을 정한다.
- 분리될 역할을 담당할 클래스를 새로 만든다.
- 원래 클래스의 생성자에서 새로운 클래스의 인스턴스를 생성하여 필드에 저장해둔다.
- 분리될 역할에 필요한 필드들을 새 클래스로 옮긴다. 하나씩 옮길 때마다 테스트한다.
- 메서드들도 새 클래스로 옮긴다. 이때 저수준 메서드, 즉 다른 메서드를 호출하기 보다는 호출을 당하는 일이 많은 메서드부터 옮긴다. 하나씩 옮길때마다 테스트한다.
- 양쪽 클래스의 인터페이스를 살펴보면서 불필요한 메서드를 제거하고, 이름도 새로운 환경에 맞게 바꾼다.
- 새 클래스를 외부로 노출할지 정한다. 노출하려거든 새 클래스에 참조를 값으로 바꾸기를 적용할지 고민해본다.
예시
// before
class Person {
get officeAreaCode() {return this._officeAreaCode;}
get officeNumber() {return this._officeNumber;}
}
// after
class Person {
constructor() {
this._telephonNumber = new TelephoneNumber()
}
get officeAreaCode() {return this._telephonNumber.areaCode;}
get officeNumber() {return this._telephoneNumber.number;}
}
class TelephoneNumber {
get areaCode() {return this._areacode;}
get number() {return this._number;}
}
7.6 클래스 인라인하기
절차
- 소스 클래스의 각 public 메서드에 대응하는 메서드들을 타깃 클래스에 생성한다. 이 메서드들은 단순히 작업을 소스 클래스로 위임해야 한다.
- 소스 클래시의 메서드를 사용하는 코드를 모두 타깃 클래스의 위임 메서드를 사용하도록 바꾼다. 하나씩 바꿀 때마다 테스트한다.
- 소스 클래스의 메서드와 필드를 모두 타깃 클래스로 옮긴다. 하나씩 옮길 때마다 테스트한다.
- 소스 클래스를 삭제하고 조의를 표한다.
예시
// before
class Person {
constructor() {
this._telephonNumber = new TelephoneNumber()
}
get officeAreaCode() {return this._telephonNumber.areaCode;}
get officeNumber() {return this._telephoneNumber.number;}
}
class TelephoneNumber {
get areaCode() {return this._areacode;}
get number() {return this._number;}
}
// after
class Person {
get officeAreaCode() {return this._officeAreaCode;}
get officeNumber() {return this._officeNumber;}
}
7.7 위임 숨기기
절차
- 위임 객체의 각 메서드에 해당하는 위임 메서드를 서버에 생성한다.
- 클라이언트가 위임 객체 대신 서버를 호출하도록 수정한다. 하나씩 바꿀때마다 테스트한다.
- 모두 수정했다면, 서버로부터 위임객체를 얻는 접근자를 제거한다.
- 테스트한다.
예시
// before
manager = aPerson.department.manager;
// after
manager = aPerson.manager;
class Person {
get manager() {return this.department.manager;} // 중개자 getter 생성
}
7.8 중개자 제거하기
절차
- 위임 객체를 얻는 게터를 만든다.
- 위임 메서드를 호출하는 클라이언트가 모두 이 게터를 거치도록 수정한다. 하나씩 바꿀때마다 테스트한다.
- 모두 수정했다면 위임 메서드를 삭제한다.
-> 자동 리팩터링 도구를 사용할 때는 위임 필드를 캡슐화한 다음, 이를 사용하는 모든 메서드를 인라인 한다.
예시
// before
manager = aPerson.manager;
class Person {
get manager() {return this.department.manager;} // 중개자 getter 생성
}
// after
manager = aPerson.department.manager;
7.9 알고리즘 교체하기
절차
- 교체할 코드를 함수 하나에 모은다.
- 이 함수만을 이용해 동작을 검증하는 테스트를 마련한다.
- 대체할 알고리즘을 준비한다.
- 정적 검사를 수행한다.
- 기존 알고리즘과 새 알고리즘의 결과를 비교하는 테스트를 수행한다. 두 결과가 같다면 리팩터링이 끝난다. 그렇지 않다면 기존 알고리즘을 참고해서 새 알고리즘을 테스트하고, 디버깅한다.
예시
// before
function foundPerson(people) {
for(let i = 0; i < people.length; i++) {
if (people[i] == "Don") {
return "Don";
}
if (people[i] == "John") {
return "John";
}
if (people[i] == "Kay") {
return "Kay";
}
return "";
// after
function foundPerson(people) {
const candidates = ["Don", "John", "Kay"]
return people.find(p => candidates.includes(p)) || '' ;
Author And Source
이 문제에 관하여(리팩터링 정리 chapter7), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@keywookim/리팩터링-정리-chapter7저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)