독립된 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 타입으로 기능 구현을 하는게 이번 장에서 이야기하는 리팩토링입니다.
'개발 > 리팩터링' 카테고리의 다른 글
파이브 라인스 오브 코드 - 긴 코드 조각내기 (0) | 2023.07.03 |
---|---|
파이브 라인스 오브 코드 - 리팩터링 깊게 들여다보기 (0) | 2023.06.19 |
파이브 라인스 오브 코드 - 리팩터링 리팩터링하기 (0) | 2023.06.11 |