개발/리팩터링

파이브 라인스 오브 코드 - 타입 코드 처리하기

DinoDev 2023. 7. 9. 19:23
728x90
반응형

독립된 if 문은 검사(check)를 담당하고, if-else 문은 의사결정(decision)으로 간주합니다. 

한 가지 예를 들면, 숫자 배열에서 평균을 구하는 함수에서 배열의 크기가 0이면 오류가 발생하고 그렇지 않으면 계산한 값을 반환하는 함수가 있습니다.

function average(arr: number[]) {
  if (size(arr) === 0)
    throw "Empty array not allowed"
  else
    return sum(arr) / size(arr)
}

이 함수에서 배열의 크기가 0인지 아닌지로 의사결정을 하기 보다는 검사를 담당하는 것으로 보입니다. 그럼 이것은 이렇게 변경 할 수 있습니다.

function assertNotEmpty(arr: number[]) {
  if (size(arr) === 0)
    throw "Empty array not allowed"
}

function average(arr: number[]) {
  assertNotEmpty(arr)
  return sum(arr) / size(arr);
}

리팩터링 패턴: 클래스로 타입 코드 대체

열거형인 enum을 interface로 변환하고 열거형의 값들은 클래스가 됩니다. 그렇게 하면 각 값에 속성을 추가하고 해당 특정 값과 관련된 기능을 특성에 맞게 만들 수 있습니다.

예제로 신호등의 신호를 가리키는 열거형과 차가 지나가도 되는지 여부를 결정하는 함수가 있습니다.

enum TrafficLight {
  RED, YELLOW, GREEN
}

const CYCLE = [TrafficLight.RED, TrafficLight.YELLOW, TrafficLight.GREEN];
function updateCarForLight(current: TrafficLight) {
  if (current == TrafficLight.RED)
    car.stop();
  else
    car.drive();
}

TrafficLight를 interface로 변경하고 함수를 통해서 if 문을 제어 할 수 있습니다.

interface TrafficLight {
  isRed(): boolean;
  isYellow(): boolean;
  isGreen(): boolean;
}

class Red implements TrafficLight {
  isRed() { return true; }
  isYellow() { return false; }
  isGreen() { return false; }
}

class Yellow implements TrafficLight {
  isRed() { return false; }
  isYellow() { return true; }
  isGreen() { return false; }
}

class Green implements TrafficLight {
  isRed() { return false; }
  isYellow() { return false; }
  isGreen() { return true; }
}

function updateCarForLight(current: TrafficLight) {
  if (current.isRed())
    car.stop();
  else
    car.drive();
}

const CYCLE = [
  new Red(),
  new Yellow(),
  new Green()
];

리팩터링 패턴: 클래스로의 코드 이관

기능을 클래스로 옮기기 때문에 클래스로 타입 코드 대체 패턴의 자연스러운 연장입니다. 결과적으로 if 구문이 제거되고 기능이 데이터에 더 가까이 이동하게 됩니다.

interface TrafficLight {
  // ...
  updateCar(): void;
}

class Red implements TrafficLight {
  // ..
  updateCar() {
    if (this.isRed())
      car.stop();
    else
      car.drive();
  }
}

class Yellow implements TrafficLight {
  // ..
  updateCar() {
    if (this.isRed())
      car.stop();
    else
      car.drive();
  }
}

class Green implements TrafficLight {
  // ..
  updateCar() {
    if (this.isRed())
      car.stop();
    else
      car.drive();
  }
}

updateCar() 내부에 있는 isRed() 함수를 전부 inline 처리를 해봅니다.

interface TrafficLight {
  // ...
  updateCar(): void;
}

class Red implements TrafficLight {
  // ..
  updateCar() {
    if (true)
      car.stop();
    else
      car.drive();
  }
}

class Yellow implements TrafficLight {
  // ..
  updateCar() {
    if (false)
      car.stop();
    else
      car.drive();
  }
}

class Green implements TrafficLight {
  // ..
  updateCar() {
    if (false)
      car.stop();
    else
      car.drive();
  }
}

if안에 true false가 고정이니까 다시 한번 변경할 수 있습니다.

interface TrafficLight {
  // ...
  updateCar(): void;
}

class Red implements TrafficLight {
  // ..
  updateCar() {
    car.stop();
  }
}

class Yellow implements TrafficLight {
  // ..
  updateCar() {
    car.drive();
  }
}

class Green implements TrafficLight {
  // ..
  updateCar() {
    car.drive();
  }
}

car를 stop하고 drive하는 로직이 class안으로 들어왔기 때문에 호출하고 있던 본문도 수정할 수 있습니다.

function updateCarForLight(current: TrafficLight) {
  if (current.isRed())
    car.stop();
  else
    car.drive();
}

function updateCarForLight(current: TrafficLight) {
  current.updateCar();
}

 

결국 밖에 있는 로직들을 클래스 내부로 이동함으로 써 if 대신 class 타입으로 기능 구현을 하는게 이번 장에서 이야기하는 리팩토링입니다.

728x90
반응형