blog logo
Published on

캡슐화





모듈을 분리하는 가장 중요한 기준은 시스템에서 각 모듈이 자신을 제외한 다른 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기느냐에 있다.

클래스는 본래 정보를 숨기는 용도로 설계되었다. 그리고 내부 정보뿐 아니라 클래스 사이의 연결 관계를 숨기는 데도 유용하다. 가장 큰 캡슐화 단위는 클래스와 모듈이지만 함수도 구현을 캡슐화한다.


1. 레코드 캡슐화하기

레코드는 연관된 여러 데이터를 직관적인 방식으로 묶을 수 있어서 각각을 따로 취급할 때보다 훨씬 의미 있는 단위로 전달할 수 있게 해준다.

하지만 레코드는 계산해서 얻을 수 있는 값과 그렇지 않은 값을 명확히 구분해 저장해야 하는 점이 번거롭다. 그래서 가변 데이터를 저장하는 용도로는 레코드보다 객체를 더 선호하는 것이 좋다.

값이 불변일 때는 레코드를 사용하지만, 바꿀 때는 객체를 활용해서 점진적으로 수정할 수 있다.

레코드 구조는 두 가지로 구분할 수 있다. 하나는 필드 이름을 노출하는 형태, 다른 하나는 (필드를 외부로 숨겨서) 내가 원하는 이름을 쓸 수 있는 형태다.

후자의 방식대로 한다면 해시맵, 연관 배열 등의 자료구조를 활용할 수 있지만 불투명한 레코드를 만들어서 문제를 발생시킬 수 있다. 그래서 불투명한 레코드가 만들어질 바에 클래스를 사용하는 편이 낫다.


리팩터링 절차

  1. 레코를 담은 변수를 캡슐화한다.
    • 레코드를 캡슐화하는 함수의 이름은 검색하기 쉽게 지어준다.
  2. 레코를 감싼 단순한 클래스로 해당 변수의 내용을 교체한다. 이 클래스에 원본 레코드를 반환하는 접근자도 정의하고, 변수를 캡슐화하는 함수들이 이 접근자를 사용하도록 수정한다.
  3. 테스트한다.
  4. 원본 레코드 대신 새로 정의한 클래스 타입의 객체를 반환하는 함수들을 새로 만든다.
  5. 레코드를 반환하는 예전 함수를 사용하는 코드 → 방금 만든 함수를 사용하도록 바꾼다.
    • 필드에 접근할 때는 객체의 접근자 사용
    • 중첩된 구조의 읽기 전용 데이터라면, 데이터의 복제본이나 읽기 전용 프락시를 고려해본다.
  6. 클래스에서 원본 데이터를 반환하는 접근자와 원본 레코드를 반환하는 함수들을 제거한다.
  7. 테스트한다.
  8. 레코드의 필드도 데이터 구조인 중첩 구조라면 레코드 캡슐화하기와 컬렉션 캡슐화하기를 재귀적으로 적용한다.


예시 코드

const person = {
  name: "ayaan",
  job: "front-end",
};

  1. 가장 먼저 상수를 캡슐화한다.
function getRawDataOfPerson() {
  return person;
}

<p>{getRawDataOfPerson().name}</p>; // 읽기 예시
getRawDataOfPerson().name = newName; // 쓰기 예시

레코드를 캡슐화하는 목적은 변수 자체는 물론 그 내용을 조작하는 방식도 통제하기 위해서다.


  1. 클래스로 변환
class Person {
  constructor(data) {
    this._data = data;
  }
}

  1. 테스트
describe("Test person data", () => {
  beforeEach(() => {
    personData = new Person(person);
  });

  it("name data", () => {
    expect(personData._data.name).toBe("ayaan");
  });

  it("job data", () => {
    expect(personData._data.job).toBe("front-end");
  });
});

  1. 클래스 타입의 객체를 반환하는 함수를 만든다.
const personData = new Person(person);

function getRawDataOfPerson() {
  return personData._data;
}

// 클래스 타입의 객체를 반환
function getPerson() {
  return personData;
}

  1. 레코드를 갱신하던 코드는 모두 세터를 사용하도록 고친다.
class Person {
  constructor(data) {
    this._data = data;
  }

  set name(args) {
    this._data.name = args;
  }
}

getPerson().name = "lee";

getPerson()._data;
// { name: 'lee', jobs: 'front-end' }

  1. 여기까지 다 바꿨다면 처음에 지었던 임시 함수를 제거한다.
~~function getRawDataOfPerson() {
  return personData._data;
}~~

function getPerson() {
  return personData;
}

그리고 person의 데이터를 _data 안에 펼쳐놓으면 더 좋다.

const person = {
  name: "ayaan",
  jobs: "front-end",
};

class Person {
  constructor(data) {
    this._name = data.name;
    this._job = data.job;
  }

  get name() {
    return this._name;
  }

  set name(args) {
    this._name = args;
  }

  get job() {
    return this._job;
  }

  set job(args) {
    this.job = args;
  }
}

const ayaanData = new Person(person);
ayaanData.name; // ayaan

ayaanData.name = "lee";
ayaanData.name; // lee

console.log(person);
// { name: 'ayaan', jobs: 'front-end' }

이렇게 하면 입력 데이터 레코드와의 연결을 끊어준다는 장점이 있다. 특히 이 레코드를 참조하여 캡슐화를 깰 우려가 있는 코드가 많을 때 좋다.



1.1 중첩된 레코드 캡슐화하기

지금까지 단순한 레코드를 캡슐화하는 방법을 살펴봤다. 그렇다면 복잡한 구조의 JSON 문서처럼 여러 겹 중첩된 레코드라면 어떻게 해야 할까?

const customerData = {
  "1920": {
    name: "마틴 파울러",
    id: "1920",
    usages: {
      "2016": {
        "1": 50,
        "2": 55,
      },
      "2015": {
        "1": 70,
        "2": 55,
      },
    },
  },
  "38673": {
    name: "닐 포드",
    // 생략...
  },
};

// 쓰기 예시
customerData[customerID].usages[year][month] = amount;

// 읽기 예시
function compareUsage(customerID, year, month) {
  const later = customerData[customerID].usages[year][month];
  const earlier = customerData[customerID].usages[year - 1][month];

  return { later, earlier };
}

중첩된 레코드를 캡슐화하는 것도 앞에서 한 것과 같이 변수 캡슐화로 시작한다.

function getRawDataOfCustomers() {
  return customerData;
}

function setRawDataOfCustomers(args) {
  customerData = args;
}

그 다음 전체 데이터 구조를 표현하는 클래스를 정의하고, 이 클래스를 반환하는 함수를 새로 만든다.

class CustomerData {
  constructor(data) {
    this._data = data;
  }
}

const customer = new CustomerData(customerData);

function getCustomerData() {
  return customer;
}

// 클래스를 반환
function getRawDataOfCustomers() {
  return customerData._data;
}

function setRawDataOfCustomers(args) {
  customerData = new CustomerData(args);
}

// 값 변환
getRawDataOfCustomers()[customerID].usages[year][month] = amount;

현재 고객 객체에는 세터가 없어서 값 변환을 할 때 객체 데이터 깊숙히 들어가서 값을 바꿔야 한다. 이 부분을 함수 추출로 만들어내면 좋다.

function setUsage(customerID, year, month, amount) {
  getRawDataOfCustomers()[customerID].usages[year][month] = amount;
}

// 사용 예시
setUsage(customerID, year, month, amount);

그 다음 위의 함수를 고객 데이터 클래스로 옮긴다.

class CustomerData {
  constructor(data) {
    this._data = data;
  }

  setUsage(customerID, year, month, amount) {
    this._data[customerID].usages[year][month] = amount;
  }
}

const customer = new CustomerData(customerData);
customer.setUsage("1920", "2016", "1", 100);

console.log(customer._data["1920"].usages["2016"]["1"]);
// 100

캡슐화에서는 값을 수정하는 부분을 명확하게 드러내고 한 곳에 모아두는 일이 굉장히 중요하다.

그리고 수정하다보면 빠진 건 없는지 궁금해질 것이다. 이럴 때는 객체를 깊은 복사하여 반환하거나, 테스트 코드를 작성 또는 프락시를 반환하는 방법도 있다.

Proxy - MDN



프락시 사용 예시

function reactive(target) {
  const proxy = new Proxy(target, {
    get: (target, key) => {
      const res = Reflect.get(target, key);
      return res;
    },
    set: (target, key, value) => {
      const oldValue = target[key];
      const res = Reflect.set(target, key, value);

      if (oldValue !== res) {
        throw new Error(`ayaan이 아니야!! ${value}, 넌 누구야!!`);
      }

      return res;
    },
  });

  return proxy;
}

const me = {
  name: "ayaan",
  job: "front-end",
};

try {
  const myData = reactive(me);
  myData.name = "todd";
} catch (err) {
  console.error(err);
}

// Error: 'ayaan이 아니야!! todd, 넌 누구야!!'


2. 컬렉션 캡슐화하기

리팩터링 2판 저자는 가변 데이터를 모두 캡슐화하는 편이라고 한다. 그러면 데이터 구조가 언제 어떻게 수정되는지 파악하기 쉬워서 필요한 시점에 데이터 구조를 변경하기도 쉬워지기 때문이라고 한다.


  1. 아직 컬렉션을 캡슐화하지 않았다면 변수 캡슐화하기부터 한다.
class Person {
  constuctor(name) {
    this._name = name;
    this._courses = [];
  }

  get name() {
    return this._name;
  }

  get courses() {
    return this._courses;
  }
}

class Course {
  constructor(name, isAdvanced) {
    this._name = name;
    this._isAdvanced = isAdvanced;
  }

  get name() {
    return this._name;
  }

  get isAdvanced() {
    return this._isAdvanced;
  }
}

모든 필드가 접근자 메서드로 부터 보호받고 있으니 캡슐화가 완벽히 되었다고 생각할 수 있다. 하지만 세터를 통해 충분히 컬렉션을 마음대로 수정할 수 있기 때문에 제대로 캡슐화가 되었다고 보기 힘들다.

  1. 제대로 캡슐화를 하기 위해 먼저 수업을 하나씩 추가하고 제거하는 메서드를 Person 에 추가하자.
class Person {
  constuctor(name) {
    this._name = name;
    this._courses = [];
  }

  addCourse(aCourse) {
    this._courses.push(aCourse);
  }

  removeCourse(aCourse) {
    const index = this._courses.indexOf(aCourse);

    if (index === -1) {
      return "error";
    }

    this._courses.splice(index, 1);
  }
}

  1. 테스트
describe("Test person course num", () => {
  beforeEach(() => {
    aPerson = new Person(person);
  });

  it("Add person course", () => {
    aPerson.addCourse("js");
    aPerson.addCourse("react");

    expect(aPerson.coureses()).toHaveLength(2);
  });

  it("Remove person course", () => {
    aPerson.removeCourse("js");

    expect(aPerson.coureses()).toHaveLength(1);
    expect(aPerson.removeCourse("java")).toHaveBeenCalledWith("error");
  });
});

  1. 그 다음 컬렉션의 변경자를 직접 호출하던 코드를 모두 찾아서 방금 추가한 메서드를 사용하도록 수정한다.
const basicCourses = ["js", "react"];

const aPerson = new Course(person);

for (const name of basicCourses) {
  aPerson.addCourse(name);
}

이렇게 개별 원소(컬렉션)을 사용하면 set을 사용할 일은 없다. 그러므로 클래스내에 있는 set들은 제거해주면 된다. 그리고 컬렉션을 사용하면서 아무도 클래스 목록을 변경할 수 없게 만드려면 다음과 같이 사용하면 된다.

get courses() {
	return this._courses.slice();
}


3. 기본형을 객체로 바꾸기

개발 초기 단계에서는 단순한 정보를 위해 넘버나 문자열 타입과 같은 간단한 구조로 데이터를 사용하곤 한다. 하지만 앱이 점점 커짐에 따라 단순했던 데이터가 복잡해지는 순간이 찾아온다.

책 저자는 단순한 출력 이상의 기능이 필요해지는 순간 그 데이터를 클래스로 정의하는 편이라고 한다.

  1. 변경해야할 변수를 캡슐화한다.
const order = { priority: "high" };

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

  // 캡슐화
  get priority() {
    return this._priority;
  }

  set priority(args) {
    this._priority = args;
  }
}

  1. 다음으로 우선순위 속성을 표현하는 값 클래스 Priority 를 만든다. 이 클래스는 표현할 값을 받는 생성자와 그 값을 문자열로 반환하는 변환 함수로 구성된다.
class Priority {
  constructor(value) {
    this._value = value;
  }

  getValue() {
    return this._value;
  }
}

속성 자체를 받은 것이 아니라, 해당 속성을 다른 값으로 표현한 값(가공된 값)을 요청했기 때문에 getValue 라는 컬렉션을 사용한다.


  1. 그 다음 방금 만든 Priority 클래스를 사용하도록 접근자를 수정한다.
class Order {
  constructor(data) {
    this._priority = data.priority;
  }

  get priority() {
    return this._priority.getValue();
  }

  set priority(args) {
    this._priority = new Priority(args);
  }
}

이렇게 표현하게 되면 Order 클래스에 있는 게터가 이상해진다. 이 게터가 반환하는 값은 우선순위 자체가 아니라 우선순위를 표현하는 값이기 때문이다.

그렇기 때문에 네이밍을 수정한다.

get priorityValue() {
    return this._priority.getValue();
  }

더 가다듬기

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

  // priority 객체를 여기서 반환하기
  get priority() {
    return this._priority;
  }

  get priorityValue() {
    return this._priority.getValue();
  }

  set priority(args) {
    this._priority = new Priority(args);
  }
}


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

함수 안에서 어떤 코드의 결괏값을 뒤에서 다시 참조할 목적으로 임시 변수를 사용한다. 임시 변수를 사용하면 코드가 반복되는 것을 줄일 수 있고, 값의 의미를 설명할 수도 있어서 좋다.

// 예시
const basePrice = quantity * itemPrice;

if (basePrice > 100) {
  return basePrice * 100;
} else {
  return basePrice * 10;
}

긴 함수의 한 부분을 별도 함수로 추출하고자 할 때 먼저 변수들을 각각의 함수로 만들면 일이 수월해진다. 추출한 함수에 변수를 따로 전달할 필요가 없어지기 때문이다.

이번 리팩터링은 특히 클래스 안에서 적용할 때 효과가 가장 큰데, 클래스는 추출할 메서드들에 공유 컨텍스트를 제공하기 때문이다.

임시 변수는 값을 계산하고 그 뒤로는 오직 읽기 전용으로만 사용해야 한다. 변수에 값을 한 번 대입한 뒤 더 복잡한 코드 덩어리에서 여러 차례 다시 대입해야 한다면 반드시 질의 함수로 바꿔야 한다.

// 코드 예제
class Order {
  constructor(quantity, item) {
    this._quantity = quantity;
    this._item = item;
  }

  get price() {
    // const를 통해 읽기 전용 변수를 만든다.
    const basePrice = this._quantity * this._item;
    const discount = 0.98;

    if (basePrice > 1000) discount -= 0.03;
    return basePrice * discount;
  }
}

읽기 전용 변수를 만들었다면 그 변수를 다시 게터로 빼낸다.

get price() {
  // const를 통해 읽기 전용 변수를 만든다.
  const basePrice = this.basePrice;
  let discount = 0.98;

  if (basePrice > 1000) discount -= 0.03;
  return basePrice * discount
}

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

테스트한다.

describe("order class", () => {
  beforeEach(() => {
    const order = new Order(1000, 10);
  });

  it("base price", () => {
    expect(order.basePrice).toBe(10000);
  });

  it("price discount", () => {
    expect(order.price).toBe(9500);
  });
});

이후 변수를 인라인한다.

get price() {
  // const를 통해 읽기 전용 변수를 만든다.
  ~~const basePrice = this.basePrice;~~
  ~~let discount = 0.98;~~

  return this.basePrice * this.discount
}

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

get discount() {
  let discount = 0.98;

  if (this.basePrice > 1000) discount -= 0.03;
  return this.basePrice * discount
}


5. 클래스 추출하기

// 아래 예시 클래스를 통해 클래스를 나눠보자.
class Person {
  get name() {
    return this._name;
  }
  set name(arg) {
    this._name = arg;
  }

  get telephoneNumber() {
    return `(${this.officeAreaCode} ${this.officeAreaNumber})`;
  }

  get officeAreaCode() {
    return this._officeAreaCode;
  }

  set officeAreaCode(arg) {
    this._officeAreaCode = arg;
  }

  get officeAreaNumber() {
    return this._officeAreaNumber;
  }

  set officeAreaNumber(arg) {
    this._officeAreaNumber = arg;
  }
}

먼저 전화번호 관련 동작을 별도 클래스로 뽑아보자.

class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
}

// 전화번호 관련 클래스 추출
class TelephoneNumber {
  get officeAreaCode() {
    return this._officeAreaCode;
  }

  get officeAreaNumber() {
    return this._officeAreaNumber;
  }
}

그 다음 필드들을 하나씩 새 클래스로 옮긴다.

class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }

  get officeAreaCode() {
    ~~return this._officeAreaCode;~~
    return this._telephoneNumber.officeAreaCode;
  }

  set officeAreaCode(arg) {
    ~~this._officeAreaCode = arg;~~
    this._telephoneNumber.officeAreaCode = arg;
  }
}

그리고 메서드들을 적절한 이름으로 바꿔주면 끝이다.

class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }

  get officeAreaCode() {
    ~~return this._officeAreaCode;~~
    return this._telephoneNumber.areaCode;
  }

  set officeAreaCode(arg) {
    ~~this._officeAreaCode = arg;~~
    this._telephoneNumber.areaCode = arg;
  }

  get officeAreaNumber() {
    ~~return this.officeAreaNumber;~~
    return this._telephoneNumber.areaNumber;
  }

  set officeAreaNumber(arg) {
    ~~this.officeAreaNumber = arg;~~
    this._telephoneNumber.areaNumber = arg;
  }
}

// 클래스 추출
class TelephoneNumber {
	get areaCode() {
    return this._areaCode;
  }

  get areaNumber() {
    return this._areaNumber;
  }

  set areaCode(arg) {
    this._areaCode = arg;
  }

  set areaNumber(arg) {
    this._areaNumber = arg;
  }
}

마지막으로 사람이 읽기 좋은 포맷으로 출력하는 것도 만들어준다.

class TelephoneNumber {
  // 생략...
  getNumber() {
    return `${this.areaCode} ${this.number}`;
  }
}

class Person {
  // 생략...
  get phoneNumber() {
    return this._thelephoneNumber.getNumber();
  }
}


6. 클래스 인라인하기

클래스 인라인하기는 클래스 추출하기의 반대이며, 더이상 역할을 할 수 없어서 그대로 두면 안되는 클래스를 인라인할 때 사용한다.

역할을 옮기는 리팩터링을 했더니 특정 클래스에 남은 역할이 없게 되면 다시 인라인을 통해 클래스로 흡수하는 방식이다. 또는 두 클래스의 기능 배분을 다르게 배분하고 싶을 때도 인라인하기를 사용한다.

class TrackingInformation {
  get shippingCompany() {
    return this._shippingCompany;
  }
  set shippingCompany(arg) {
    this._shippingCompany = arg;
  }

  get trackingNumber() {
    return this._trackingNumber;
  }
  set shippingCompany(arg) {
    this._shippingCompany = arg;
  }

  get display() {
    return `${this_shippingCompany} ${this._trackingNumber}`;
  }
}

// 위의 클래스는 Shipment의 일부처럼 사용 중이다.

class Shipment {
  get trackingInfo() {
    return this._trackingInformation.display;
  }

  get trackingInformation() {
    return this._trackingInformation;
  }

  set trackingInformation(aTrackingInformation) {
    this._trackingInformation = aTrackingInformation;
  }
}

하지만 TrackingInformation 클래스가 더 이상 제 역할을 못하고 있다. 해당 클래스의 메서드를 제대로 사용하고 있지 않기 때문이다. 이럴때 클래스 인라인하기를 하면 된다. TrackingInfomation 클래스를 Shipment 클래스로 합쳐준다.

class Shipment {
  // 생략...

  set shippingCompany(arg) {
    this._trackingInformation.shippingCompany = arg
  }
}

class TrackingInformation {
  // 생략...

  ~~get shippingCompany() {
    return this._shippingCompany;
  }~~
}

이런식으로 분리했던 클래스의 메서드들을 하나씩 합쳐가면서 인라인해주면 된다.

결과

class Shipment {
  get trackingInfo() {
    return `${this.shippingCompany}: ${this.trackingNumber}`;
  }

  get shippingCompany() {
    return this._shippingCompany;
  }

  set shippingCompany(arg) {
    this._shippingCompany = arg;
  }

  get trackingNumber() {
    return this._trackingNumber;
  }
  set shippingCompany(arg) {
    this._shippingCompany = arg;
  }
}


7. 위임 숨기기

모듈화 설계를 제대로 하는 핵심은 캡슐화다. 캡슐화가 잘 되어 있다면 무언가를 변경해야 할 때 함께 고려해야 할 모듈 수가 적어져서 코드를 변경하기가 수월하다.

class Person {
  constructor(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }

  get department() {
    return this._department;
  }

  set department(arg) {
    this._department = arg;
  }
}

class Department {
  get chargeCode() {
    return this._chargeCode;
  }
  set chargeCode(arg) {
    this.chargeCode = arg;
  }

  get manager() {
    return this._manager;
  }
  set manager(arg) {
    this.manager = arg;
  }
}

위의 예시 코드는 사람(Person)과 사람이 속한 부서(Department)를 정의했다. 여기서 어떤 사람이 속한 부서의 관리자를 알고 싶다고 하자. 그러면 부서 객체 부터 얻어와야 한다.

manager = aPerson.department.manager;

특정 사람이 속한 부서의 관리자를 알려면 부서 클래스가 관리자 정보를 제공한다는 사실을 먼저 알아야 한다. 부서 클래스의 의존성이 있다.

이럴 때는 부서 클래스의 의존성을 줄이기 위해 부서 클래스를 볼 수 없게 숨기고, 사람 클래스에 간단한 위임 메서드를 만들면 된다.

class Person {
  // 생략...

  get manager() {
    return this._department.manager;
  }
}

그럼 아까 manager 변수는 다시 이렇게 사용할 수 있다.

manager = aPerson.~~department~~.manager;

class Person {
  constructor(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }

  get manager() {
    return this._department.manager;
  }

  // manager 게터가 추가되면서 아래 게터는 필요없어졌다.
  ~~get department() {
    return this._department;
  }~~
}



8. 중개자 제거하기

방금 설명한 위임 숨기기의 반대 개념이다. 위임 숨기기를 통해 캡슐화의 장점을 알 수 있었다. 하지만 위임이 항상 좋은 것은 아니다. 위임 객체의 또 다른 기능을 사용하고 싶을 때 마다 메서드를 추가해야하는 번거로움이 생길 수도 있다. 바로 아래 처럼.

class Person {
  constructor(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }

  get manager() {
    return this._department.manager;
  }

  get chargeCode() {
    return this._department.chargeCode;
  }

  // 계속 메서드 추가...
}

이렇게 전달만 하는 위임 메서드들은 추가할수록 번거로워진다. 그러면 서버 클래스(Person)는 그저 중개자 역할로 전락하여, 차라리 직접 위임 객체를 호출하는게 나을 수도 있다.

중개자(Department)를 제거하기 위해 먼저 위임 객체를 얻는 게터를 만들자.

 class Person {
  // 생략...

  ~~get manager() {
    return this._department.manager;
  }~~

  get department() {
    return this._department;
  }
}

manager = aPerson.department.manager;

// Department 클래스 제거


9. 알고리즘 교체하기

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] === "Kent") return "Kent";
  }

  return "";
}

// 아래 방식으로 수정
function fonudPerson(people) {
  const candidates = ["Don", "John", "Kent"];
  return people.find((p) => candidates.includes(p)) || "";
}

절차

  • 교체할 코드를 함수 하나에 모은다.
  • 이 함수만을 이용해 동작을 검증하는 테스트를 마련한다.
  • 대체할 알고리즘을 준비한다.
  • 정적 검사를 수행한다.
  • 테스트 or 디버깅