스터디/리팩토링 2판

[리팩토링 2판] 7장. 캡슐화, 8장. 기능 이동 (11월 2일)

눙엉 2022. 11. 3. 00:07

기본형을 객체로 바꾸기

처음에는 단순한 문자열로 시작해서 나중에는 포맷팅, 특별한 동작이 추가되는 등 중복 코드가 늘어나고 사용할 때마다 드는 노력도 늘어난다.

출력 이상의 기능이 필요해지는 순간 클래스를 정의하면 좋다.

클래스로 집중시켜 놓으면 관련된 로직을 한 곳에서 관리할 수 있으면 중복된 코드를 처리할 수 있다.

 

// before
class Order {
  constructor(data){
    this.priority = data.priority;
  }
}

highPriorityCount = orders.filter
		(o => "high" === o.priority || "rush" === o.priority).length;
// after
class Order {
  constructor(data){
    this.priority = data.priority;
}

  get priorityString() {return this._priority.toString()}
  set priority(string) {this._priority. new Priority(string)}
}

class Priority {
  constructor(value) {this._value = value}
  toString() {return this._value}
}

 

임시 변수를 질의 함수로 바꾸기

함수 안에서 어떤 코드의 결괏값을 다시 참조할 목적으로 임시 변수를 사용한다.

임시 변수를 사용하면 계산하는 중복 코드를 줄이고 값의 의미를 설명할 수 있어 유용하다.

그런데 더 나아가 아예 함수로 만들어 사용하는 편이 나을 때가 많다.

// before
class Order {
  constructor(quantity, item) {
    this._quantity = quantity;
    this._item = item;
  }

  get price() {
    const basePrice = this._quantity * this._item.price;
    const discountFactor = 0.98;

    if (base > 1000) discountFactor -= 0.03;
    return basePrice * discountFactor;
  }
}
// before
class Order {
  constructor(quantity, item) {
    this._quantity = quantity;
    this._item = item;
  }

  get basePrice() {
    return this._quantity * this._item.price;
  }

  get discountFactor() {
    return base > 1000 ? 0.95 : 0.98;
  }

  get price() {
    return this.basePrice * this.discountFactor;  
  }
}

 

클래스 추출하기

기존 클래스를 굳이 쪼갤 필요까지 없다고 생각해서 새로운 역할을 덧씌우며 점점 복잡했는데 이해하기 어려우니 적절히 분리하는 것이 좋다.

// before
class Person{
  conststructor(name,countryCode, phoneNumber){
    this.name = name
    this.countryCode = countryCode
    this.phoneNumber = phoneNumber
  }
	
  get name() {return this.name}
  set name(name) {this.name = name}
  get telephoneNumber() {return `(${this.countryCode} ${this.phoneNumber})`}
  get countryCode = {return this.countryCode}
}
// after
class Person{
  constructor(name,){
    this.name = name
    this.telephone = new Telephone();
  }
	
  get name() {return this.name}
  set name(name) {this.name = name}
  get telephoneNumber() {return `(${this.telephone.countryCode} ${this.telephone.phoneNumber})`}
  get countryCode = {return this.telephone.countryCode}
}

class Telephone {
  constructor(countryCode, phoneNumber){
    this.countryCode = countryCode
    this.phoneNumber = phoneNumber
  }

  get telephoneNumber() {return `(${this.countryCode} ${this.phoneNumber})`}
  get countryCode = {return this.countryCode}
}

 

문장을 함수로 옮기기

중복 제거는 코드를 건강하게 관리하는 가장 효과적인 방법 중 하나다.

// before
function renderPerson(outStream, person){
  const result = [];
  result.push(`<p>${person.name}</p>`);
  result.push(renderPhoto(person.photo));
  result.push(`<p>제목: ${person.photo.title}</p>`);
  result.push(emitPhotoData(person.photo));
  return result.join("\\n");
}

function photoDiv(p){
  return ["<div>", `<p>제목: ${p.title}</p>`, emitPhotoData(p),"</div>"].join("\\n")
}

function emitPhotoData(photo){
  const result = [];
  result.push(`<p>위치: ${photo.location}</p>`);
  result.push(`<p>날짜: ${photo.date.toDateString()}</p>`);
  return result.join("\\n");
}
// after
function renderPerson(outStream, person){
  const result = [];
  result.push(`<p>${person.name}</p>`);
  result.push(renderPhoto(person.photo));
  result.push(emitPhotoData(person.photo));
  return result.join("\\n");
}

function photoDiv(p){
  return ["<div>",emitPhotoData(p),"</div>"].join("\\n")
}

function emitPhotoData(photo){
  const result = [];
  result.push(`<p>제목: ${person.photo.title}</p>`);
  result.push(`<p>위치: ${photo.location}</p>`);
  result.push(`<p>날짜: ${photo.date.toDateString()}</p>`);
  return result.join("\\n");
}

 

인라인 코드를 함수 호출로 바꾸기

함수는 여러 동작을 하나로 묶어준다. 함수의 이름이 코드의 목적을 말하기 때문 이해력이 높아진다.

중복을 없애는 데도 효과적이다.

 

문장 슬라이드 하기

관련된 코드들이 가까이 모여 있다면 이해하기가 쉽다.

가장 흔한 사례는 변수를 선언하고 사용할 때다. 모든 변수 선언을 함수 첫머리에 모아두는 경향이 있는데 그것 보단 처음 사용할 때 선언하는 스타일을 선호한다.

// before
  let result;
  if (availableResources.length === 0) {
    result = createResource();
    allocatedResources.push(result);
  } else {
    result = availableResources.pop();
    allocatedResources.push(result);
  }
  return result;
// after
const result =
  availableResources.length === 0 ? createResource() : availableResources.pop();

allocatedResources.push(result);

return result;

 

반복문을 파이프라인으로 바꾸기

파이프라인을 이용하면 처리 과정을 일련의 연산으로 표현할 수 있다.

// before

// 데이터
office, country, telephone
Chicago, USA, +1 312 373 1000
Beijing, China, +86 4008 900 505

function acquireData(input) {
  const lines = input.split('\\n');
  let firstLine = true;
  const result = [];
  for (const line of lines) {
    if (firstLine) {
      firstLine = false;
      continue;
    }
    if (line.trim() === '') continue;
    const record = line.split(',');
    if (record[1].trim() === 'India') {
      result.push({ city: record[0].trim(), phone: record[2].trim() });
    }
  }
  return result;
}
// after
function acquireData(input) {
  return input
  .split("\\n")
  .slice(1)
  .filter((line) => line.trim() !== "")
  .map((line) => line.split(","))
  .filter((fields) => fields[1].trim() === "India")
  .map((fields) => ({ city: fields[0].trim(), phone: fields[2].trim() }));
					
}

 

이 책을 읽으면 읽을수록 클린 코드와 결이 엄청 비슷하다고 점점 확신이 든다...

 

중복 코드 제거, 동작을 명확히 나타내는 이름 등등

 

코드를 작성할 때 읽었던 내용들이 떠올라서 찜찜하게 만들어 조금이라도 적용해보려는 습관이 생기는 것 같다.