9.1 변수 쪼개기
- 역할이 둘 이상인 변수가 있다면 쪼개야 함. 역할 하나당 변수 하나임
절차
- 변수를 선언한 곳과 값을 처음 대입하는 곳에서 변수 이름을 바꿈
- 가능하면 이때 불변으로 선언함
- 이 변수에 두 번째로 값을 대입하는 곳 앞까지의 모든 참조(이 변수가 쓰인 곳)를 새로운 변수 이름으로 바꿈
- 두 번째 대입 시 변수를 원래 이름으로 다시 선언함
- 테스트함
- 반복함
- 매 반복에서 변수를 새로운 이름으로 선언하고 다음번 대입 때까지의 모든 참조를 새 변수명으로 바꿈. 이 과정을 마지막 대입까지 반복함.
예시
function distanceTraveled(scenario, time) {
let result;
let acc = scenario.primaryForce / scenario.mass; // ⬅️
let primaryTime = Math.min(time, scenario.delay);
result = 0.5 * acc * primaryTime * primaryTime;
let secondaryTime = time - scenario.delay;
if (secondaryTime > 0) {
let primaryVelocity = acc * scenario.delay;
acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass; // ⬅️
result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
}
return result;
}
// acc 변수에 값이 두 번 대입되는 부분을 쪼갬
function distanceTraveled(scenario, time) {
let result;
const primaryAcceleration = scenario.primaryForce / scenario.mass; // ✅
let primaryTime = Math.min(time, scenario.delay);
result = 0.5 * primaryAcceleration * primaryTime * primaryTime; // ✅
let secondaryTime = time - scenario.delay;
if (secondaryTime > 0) {
let primaryVelocity = primaryAcceleration * scenario.delay; // ✅
const secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass; // ✅
result +=
primaryVelocity * secondaryTime + 0.5 * secondaryAcceleration * secondaryTime * secondaryTime; // ✅
}
return result;
}
9.2 필드 이름 바꾸기
- 데이터 구조는 프로그램을 이해하는 데 큰 역할을 함
- 클래스에서 게터와 세터 메서드의 이름은 레코드 구조체의 필드 이름만큼 중요함
절차
- 레코드의 유효 범위가 제한적이라면 필드에 접근하는 모든 코드를 수정한 후 테스트함. 이후 단계는 필요 없음
- 레코드가 캡슐화되지 않았다면 우선 레코드를 캡슐화함
- 캡슐화된 객체 안의
private
필드명을 변경하고, 그에 맞게 내부 메서드들을 수정 함 - 테스트 함
- 생성자의 매개변수 중 필드와 이름이 겹치는 게 있다면 함수 선언 바꾸기로 변경함
- 접근자들의 이름도 바꿔줌
예시
const organization = { name: 'KAY', country: 'KOR' };
// name을 title로 하고, organization 레코드를 클래스로 캡슐화한 뒤, 입력 데이터 구조를 내부 데이터 구조와 분리
class Organization {
constructor(data) {
this._title = data.title;
this._country = data.country;
}
get title() {
return this._title;
}
set title(aString) {
this._title = aString;
}
get country() {
return this._country;
}
set country(aCountryCode) {
this._country = aCountryCode;
}
}
const organization = new Organization({
title: 'KAY',
country: 'KOR',
});
9.3 파생 변수를 질의 함수로 바꾸기
- 가변 데이터의 유효 범위는 가능한 한 좁혀야 함
-
그 방법 중 하나로, 값을 쉽게 계산해낼 수 있는 변수들을 모두 제거함. 다만 예외가 있는데, 새로운 데이터 구조를 생성하는 변형 연산이라면 그대로 두는 것도 좋음
- 데이터 구조를 감싸며 그 데이터에 기초하여 계산한 결과를 속성으로 제공하는 객체
- 데이터 구조를 받아 다른 데이터 구조로 변환해 반환하는 함수
절차
- 변수 값이 갱신되는 지점을 모두 찾음. 필요하면 변수 쪼개기를 활용해 각 갱신 지점에서 변수를 분리함
- 해당 변수의 값을 계산해주는 함수를 만듦
- 해당 변수가 사용되는 모든 곳에 어서션을 추가하여 함수의 계산 결과가 변수의 값과 같은지 확인함
- 테스트함
- 변수를 읽는 코드를 모두 함수 호출로 대체함
- 테스트함
- 변수를 선언하고 갱신하는 코드를 죽은 코드 제거하기로 없앰
예시
class ProductionPlan {
// ...
get production() {
return this._production;
}
applyAdjustment(anAdjustment) {
this._adjustments.push(anAdjustment);
this._production += anAdjustment.amount;
}
}
// 조정 값 adjustment를 적용하는 과정에서 직접 관련이 없는 누적 값 production까지 갱신하고 있음
// (데이터 중복) 누적 값을 매번 갱신하지 않고 계산으로 변경
class ProductionPlan {
// ...
get production() {
return this._adjustments.reduce((sum, a) => sum + a.amount, 0);
}
applyAdjustment(anAdjustment) {
this._adjustments.push(anAdjustment);
}
}
9.4 참조를 값으로 바꾸기
- 객체(데이터 구조)를 다른 객체(데이터 구조)에 중첩하면 내부 객체를 참조 혹은 값으로 취급할 수 있음
- 참조로 다루는 경우에는 내부 객체는 그대로 둔 채 그 객체의 속성만 갱신하며, 값으로 다루는 경우에는 새로운 속성을 담은 객체로 기존 내부 객체를 통째로 대체함
- 필드를 값으로 다룬다면 내부 객체의 클래스를 수정하여 값 객체(Value Object)로 만들 수 있음
- 값 객체는 불변이기 때문에 대체로 자유롭게 활용하기 좋음
- 하지만 특정 객체를 여러 객체에서 공유하고자 한다면, 그래서 공유 객체의 값을 변경했을 때 이를 관련 객체 모두에 알려줘야 한다면 공유 객체를 참조로 다뤄야 함
절차
- 후보 클래스가 불변인지, 혹은 불변이 될 수 있는지 확인함
- 각각의 세터를 하나씩 제거함
- 이 값 객체의 필드들을 사용하는 동치성 비교 메서드를 만듦
예시
- 생성 시점에는 전화번호가 올바로 설정되지 못한 사람(Person) 객체가 있음
class Person {
constructor() {
this._telephoneNumber = new TelephoneNumber();
}
get officeAreaCode() {
return this._telephoneNumber.areaCode;
}
set officeAreaCode(arg) {
this._telephoneNumber.areaCode = arg;
}
get officeNumber() {
return this._telephoneNumber.number;
}
set officeNumber(arg) {
this._telephoneNumber.number = arg;
}
}
class TelephoneNumber {
get areaCode() {
return this._areaCode;
}
set areaCode(arg) {
this._areaCode = arg;
}
get number() {
return this._number;
}
set number(arg) {
this._number = arg;
}
}
- 전화번호를 불변으로 만들고,
- 필드들의 세터만 제거한다.
- 전화번호를 ‘값’ 객체로 인정받기 위해 동치성 메서드를 만듦
class Person {
constructor() {
this._telephoneNumber = new TelephoneNumber();
}
get officeAreaCode() {
return this._telephoneNumber.areaCode;
}
set officeAreaCode(arg) {
this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber);
}
get officeNumber() {
return this._telephoneNumber.number;
}
set officeNumber(arg) {
this._telephoneNumber = new TelephoneNumber(this.officeAreaCode, arg);
}
}
class TelephoneNumber {
constructor(areaCode, number) {
this._areaCode = areaCode;
this._number = number;
}
// getter와 setter
equals(other) {
if (!(other instanceof TelephoneNumber)) return false;
return this.areaCode === other.areaCode && this.number === other.number;
}
}
9.5 값을 참조로 바꾸기
- 하나의 데이터 구조 안에 논리적으로 똑같은 제3의 데이터 구조를 참조하는 레코드가 여러 개 있을 때가 있음
- 논리적으로 같은 데이터를 물리적으로 복제해 사용할 때 가장 크게 문제되는 상황은 그 데이터를 갱신해야 할 때
- 이런 상황이라면 복제된 데이터들을 모두 참조로 바꿔주는 게 좋음
- 값을 참조로 바꾸면 엔티티 하나당 객체도 단 하나만 존재하게 되는데, 그러면 보통 이런 객체들을 한데 모아놓고 클라이언트들의 접근을 관리해주는 일종의 저장소가 필요함
절차
- 같은 부류에 속하는 객체들을 보관할 저장소를 만듦
- 생성자에서 이 부류의 객체들 중 특정 객체를 정확히 찾아내는 방법이 있는지 확인함
- 호스트 객체의 생성자들을 수정하여 필요한 객체를 이 저장소에서 찾도록 함. 하나 수정할 때마다 테스트함
예시
class Order {
constructor(data) {
this._number = data.number;
this._customer = new Customer(data.customer);
}
get customer() {
return this._customer;
}
}
class Customer {
constructor(id) {
this._id = id;
}
get id() {
return this._id;
}
}
- 이런 방식으로 생성한 고객 객체는 값이기 때문에, 고객 ID가 123인 주문을 다섯 개 생성한다면 독립된 고객 객체가 다섯 개 만들어짐
- 이 중 하나를 수정하더라도 나머지 네 개에는 반영되지 않음
- 저장소 객체를 만들고, 고객 객체를 값으로 변경함
let _repositoryData;
export function initialize() {
_repositoryData = {};
_repositoryData.customers = new Map();
}
export function registerCustomer(id) {
if (!_repositoryData.customers.has(id)) {
_repositoryData.customers.set(id, new Customer(id))''
}
return findCustomer(id);
}
export function findCustomer(id) {
return _repositoryData.customers.get(id);
}
class Order {
constructor(data) {
this._number = data.number;
this._customer = registerCustomer(data.customer);
}
get customer() {
return this._customer;
}
}
- 이 예에서는 특정 고객 객체를 참조하는 첫 번째 주문에서 해당 고객 객체를 생성함
- 또 다른 방법으로, 고객 목록을 미리 다 만들어서 저장소에 저장해놓고 주문 정보를 읽을 때 연결해줄 수도 있음
9.6 매직 리터럴 바꾸기
- 매직 리터럴(magic literal)이란 소스 코드에 (보통은 여러 곳에) 등장하는 일반적인 리터럴 값을 말함.
- 매직 리터럴보다는, 코드 자체가 뜻을 분명하게 드러내는 게 좋음
- 상수를 정의하고 숫자 대신 상수를 사용하면 됨
- 상수가 특별한 비교 로직에 주로 쓰이는 경우에는 상수값 그대로를 사용하기보다는 함수 호출로 바꿀 수도 있음
방법
- 상수를 선언하고 매직 리터럴을 대입 함
- 해당 리터럴이 사용되는 곳을 모두 찾음
- 찾은 곳 각각에서 리터럴이 새 상수와 똑같은 의미로 쓰였는지 확인하여, 같은 의미라면 상수로 대체한 후 테스트 함