//리팩토링 전
function distanceTravelled(scenario, time) {
let result;
let acc = scenario.primaryForce / scenario.mass;
let primaryTime = Math.main(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;
}
}
//리팩토링 후
function distanceTravelled(scenario, time) {
let result;
const primaryAcceleration = scenario.primaryForce / scenario.mass;
const primaryTime = Math.main(time, scenario.delay);
result = 0.5 * primaryAcceleration * primaryTime * primaryTime;
const secondaryTime = time - scenario.delay;
if (secondaryTime > 0) {
const primaryVelocity = primaryAcceleration * scenario.delay;
const secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;
result +=
primaryVelocity * secondaryTime +
0.5 * secondaryAcceleration * secondaryTime * secondaryTime;
}
}
변수는 긴 코드의 결과를 저장했다가 나중에 쉽게 참조하려는 목적으로 흔히 쓰이는데 이런 변수에는 값을 단 한 번만 대입해야 한다. 대입이 두 번 이상 이뤄진다면 여러 가지 역할을 수행한다는 신호다. 역할이 둘 이상인 변수가 있다면 쪼개야 한다.
//리팩토링 전
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
get country() {
return this._country;
}
set country(value) {
this._country = value;
}
}
//리팩토링 후
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get title() {
return this._name;
}
set title(value) {
this._name = value;
}
get country() {
return this._country;
}
set country(value) {
this._country = value;
}
}
데이터 구조는 프로그램을 이해하는 데 큰 역할을 하고 반드시 깔끔하게 관리해야 하기 때문에 레코드 구조체의 필드 이름은 특히 더 중요하다. 개발을 진행할수록 데이터를 더 잘 이해하게 되었다면 그 깊어진 이해를 프로그램에 반영하여 레코드의 필드 이름을 변경해야 한다.
//리팩토링 전
class Order {
get discountedTotal() {
return this._discountedTotal;
}
set discount(value) {
const old = this._discount;
this._discount = value;
this._discountedTotal += old - value;
}
}
class ProductionPlan {
get production() {
return this._production;
}
applyAdjustment(adjustment) {
this._adjustments.push(adjustment);
this._production += adjustment.amount;
}
}
class Order {
get discountedTotal() {
return this._basePrice - this._discount;
}
set discount(value) {
this._discount = value;
}
}
class ProductionPlan {
get production() {
return this._adjustments.reduce((sum, a) => sum + a.amount, 0);
}
applyAdjustment(adjustment) {
this._adjustments.push(adjustment);
}
}
가변 데이터는 소프트웨어에 문제를 일으키는 가장 큰 골칫거리이다. 한 쪽 코드에서 수정한 값이 연쇄 효과를 일으켜 다른 쪽 코드에 원인을 찾기 어려운 문제를 야기하기도 한다. 값을 쉽게 계산해낼 수 있는 변수를 질의함수로 바꾸므로써 가변 데이터의 유효 범위를 좁힐 수 있다.
//리팩토링 전
class Person {
#name;
#telephoneNumber;
constructor(name, areaCode, number) {
this.#name = name;
this.#telephoneNumber = new TelephoneNumber(areaCode, number);
}
get name() {
return this.#name;
}
set name(arg) {
this.#name = arg;
}
get telephoneNumber() {
return this.#telephoneNumber.toString;
}
get officeAreaCode() {
return this.#telephoneNumber.areaCode;
}
set officeAreaCode(value) {
this.#telephoneNumber.areaCode = value;
}
get officeNumber() {
return this.#telephoneNumber.number;
}
set officeNumber(value) {
this.#telephoneNumber.number = value;
}
}
class TelephoneNumber {
#areaCode;
#number;
constructor(area, number) {
this.#areaCode = area;
this.#number = number;
}
get areaCode() {
return this.#areaCode;
}
set areaCode(arg) {
this.#areaCode = arg;
}
get number() {
return this.#number;
}
set number(arg) {
this.#number = arg;
}
get toString() {
return `(${this.#areaCode}) ${this.#number}`;
}
}
//리팩토링 후
class Person {
#name;
#telephoneNumber;
constructor(name, areaCode, number) {
this.#name = name;
this.#telephoneNumber = new TelephoneNumber(areaCode, number);
}
get name() {
return this.#name;
}
set name(arg) {
this.#name = arg;
}
get telephoneNumber() {
return this.#telephoneNumber.toString;
}
get officeAreaCode() {
return this.#telephoneNumber.areaCode;
}
set officeAreaCode(value) {
this.#telephoneNumber = new TelephoneNumber(value, this.officeNumber);
}
get officeNumber() {
return this.#telephoneNumber.number;
}
set officeNumber(value) {
this.#telephoneNumber = new TelephoneNumber(this.officeAreaCode, value);
}
}
class TelephoneNumber {
#areaCode;
#number;
constructor(area, number) {
this.#areaCode = area;
this.#number = number;
}
get areaCode() {
return this.#areaCode;
}
get number() {
return this.#number;
}
get toString() {
return `(${this.#areaCode}) ${this.#number}`;
}
}
필드를 값으로 다룬다면 내부 객체의 클래스를 수정하여 값 객체로 만들 수 있다. 값 객체는 불변이기 때문에 프로그램 외부로 건네줘도 나중에 그 값이 나 몰래 바뀌어서 내부에 영향을 줄까 염려하지 않아도 된다.
//리팩토링 전
class Order {
constructor(data) {
this._number = data.number;
this._customer = new Customer(data.customerId);
}
get customer() {
return this._customer;
}
}
class Customer {
constructor(id) {
this._id = id;
}
get id() {
return this._id;
}
}
//리팩토링 후
class Order {
constructor(number, customer) {
this._number = number;
this._customer = customer;
}
get customer() {
return this._customer;
}
}
class Customer {
constructor(id) {
this._id = id;
}
get id() {
return this._id;
}
}
class CustomerRepository {
#customers;
constructor() {
this.#customers = new Map();
}
registerCustomer(id) {
if(!this.#customers.has(id)) {
this.#customers.set(id, new Customer(id));
}
return this.findCustomer(id);
}
findCustomer(id) {
return this.#customers.get(id);
}
}
논리적으로 같은 데이터를 물지적으로 복제해 사용할 때 가장 크게 문제되는 상황은 그 데이터를 갱신해야 할때이다. 모든 복제본을 찾아서 빠짐없이 갱신해야 하며, 하나라도 놓치면 데이터 일관성이 깨져버리기 때문에 복제된 데이터들을 모두 참조로 바꿔주는 것이 좋다.
//리팩토링 전
function potentialEnergy(mass, height) {
return mass * 9.81 * height;
}
//리팩토링 후
const STANDARD_GRAVITY = 9.81;
function potentialEnergy(mass, height) {
return mass * STANDARD_GRAVITY * height;
}
코드를 읽는 사람이 숫자 자체로는 의미를 알 수 없다면 뜻을 분명하게 드러내도록 상수를 정의하는 것이 좋다.