레코드 캡슐화하기
// before
someRecord = {name: "choijaewon", country: "korea"};
// after
class SomeRecord {
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;
}
}
- 레코드를 직접 노출하는 것이 아닌 클래스로 감싸고, 접근자 메서드를 통해 접근 및 수정할 수 있도록 바꾼다.
컬렉션 캡슐화하기
// before
class SomeClass {
get someList() {
return this.someList;
}
set someList(aList) {
this.someList = aList;
}
}
// after
class SomeClass {
get someList() {
return this.someList.slice();
}
addSome();
removeSome();
}
- 이전 코드는 get을 호출할 때 필드의 컬렉션을 그대로 반환한다.
- 이러면 get() 으로 받아온 뒤 그 컬렉션을 수정하면 그대로 반영된다 → 캡슐화가 깨짐
- 그래서 컬렉션을 반환할 때는 읽기 전용 프록시나 복제본을 반환하게 한다.
- 또한 전체를 바꾸는 setter는 없애고, 원소를 추가/제거하는 메소드를 만든다.
- 만약 없앨 수 없다면, setter로 들어온 컬렉션의 복제본을 필드로 저장한다 → 세터로 설정한 컬렉션을 외부에서 수정하면 내부에 반영되므로
기본형을 객체로 바꾸기
// before
const phoneNumber = "010-4321-4321";
if (phoneNumber == "010-1234-5678") {
// do something
}
// after
const phoneNumber = new PhoneNumber("010-4321-4321");
const anotherNumber = new PhoneNumber("010-1234-5678");
if (phoneNumber.isEqual(anotherNumber)) {
// do something
}
- 무언가 의미가 있는 데이터를 기본형으로만 표현하는 경우 이를 클래스로 만들어 재사용성과 가독성을 높인다.
임시 변수를 질의 함수로 바꾸기
// before
const someResult = a * b;
if (someResult > 1000) {
// do something
}
// after
function someResult(a, b) {
return a * b;
}
if (someResult(a, b) > 1000) {
// do something
}
// after 2
class Some {
a;
b;
get someResult() {return a * b}
someFunc() {
if (someResult() > 1000) {
// do something
}
}
}
- 반복적으로 사용되는 연산 값을 저장하는 변수가 꼭 있다.
- 이런 걸 함수로 추출한 뒤, 이 함수를 호출하는 형태로 바꾸는 것
- 이 방식은 특히 클래스에서 매우 유용하다. → 필드를 통해 매개변수가 필요없기 때문이다.
클래스 추출하기
// before
class Person {
get countryCode() {
return this.countryCode;
}
get officeNumber() {
return this.officeNumber;
}
}
// after
class Person {
get countryCode() {
return this.countryCode;
}
get officeNumber() {
return this.telephoneNumber.number;
}
}
class TelephoneNumber {
get areaCode() {
return this.areaCode;
}
get number() {
return this.number;
}
}
- 개발이 진행되면서 한 클래스가 너무 많은 역할을 담당하는 경우가 있다.
- 이 경우, 각 역할을 클래스로 추출하고, 원래 클래스가 추출된 클래스의 객체를 필드로 가지도록 리팩터링한다.
클래스 인라인하기
// before
class Person {
get countryCode() {
return this.countryCode;
}
get officeNumber() {
return this.telephoneNumber.number;
}
}
class TelephoneNumber {
get areaCode() {
return this.areaCode;
}
get number() {
return this.number;
}
}
// after
class Person {
get countryCode() {
return this.countryCode;
}
get officeNumber() {
return this.officeNumber;
}
}
- 위와 반대로, 어떤 클래스의 역할이 너무 없는 경우도 있다.
- 이 경우에는 해당 클래스를 사용하는 다른 클래스와 합쳐버릴 수 있다.
- 옮길 때 먼저 없어질 클래스의 메서드를 합칠 클래스에 모두 만들어주고, 없어질 클래스의 메소드를 호출하는 부분을 하나씩 바꿔준다.
위임 숨기기
// before
manager = aPerson.department.manager;
// after
manager = aPerson.manager;
class Person {
get manager() {
return this.department.manager;
}
}
- 서버의 필드가 가리키는 객체의 메서드를 호출하려면 클라이언트가 이 위임 객체를 알아야 한다.
- 또한 어쩌다가 그 객체의 인터페이스가 바뀌면, 이 또한 전부 바뀌어야 한다.
- 이러한 경우, 이 필드 객체의 필드를 서버에서 호출해주도록 바꾸면 인터페이스가 수정되어도 서버만 바꿔주면 되어서 편하다.
중개자 제거하기
// before
manager = aPerson.manager;
class Person {
get manager() {
return this.department.manager;
}
}
// after
manager = aPerson.department.manager;
- 위와 반대로, 너무 과해지면 해당 클래스가 중개자 역할만 하는 메서드로만 가득 찰 수도 있다.
- 이 경우, 정확히 반대로 수행해주면 된다.
알고리즘 교체하기
// before
function findPerson(people) {
for (let i = 0; i < people.length; i++) {
if (people[i] == "jaewon") {
return "jaewon";
}
}
return "";
}
// after
function findPerson(people) {
const candidates = ["jaewon"];
return people.find(p => candidates.includes(p)) || '';
}
- 이 방법은 단순히 더 나은 알고리즘을 발견했을 때 그냥 바꿔주는 것이다.
- 그러나 절차에서 주목할 점이 있다.
- 교체할 코드를 함수 하나에 모은다
- 이 함수를 테스트할 코드를 마련한다.
- 알고리즘 교체 시 그 알고리즘을 함수 하나에 모으고 테스트를 그 함수로 국한시키는 것이 주목할 점인 것 같다.
https://product.kyobobook.co.kr/detail/S000001810241