If를 리팩터링한다고 해서 결정을 없애는 것은 아니다
Source: Dev.to
소개
이미 Object Maps에 대해 이야기한 적이 있습니다. 이는 switch 또는 if/else 체인을 대체하는 강력한 기법으로, 순환 복잡성을 상수 시간 접근(O(1))으로 바꾸어 정적 키 → 값 매핑 시나리오에 이상적입니다.
하지만 실제 소프트웨어 개발은 그리 예측 가능하지 않습니다. 우리는 동적인 비즈니스 규칙, 값 구간, 결합된 검증, 컨텍스트에 의존하는 결정 등을 다루게 되며, 이 경우 단순 매핑만으로는 충분하지 않습니다.
그때 피할 수 없는 의문이 생깁니다:
if, 나는 더러운 코드를 쓸 수밖에 없을까?
답은 아니오입니다. 조건문을 리팩터링하는 것은 코드 라인 수가 적은 사람이 이기는 스포츠가 아니라(그렇다면 미니파이어만 쓰면 되겠죠 :D), 가장 명확한 코드를 만드는 일입니다. 목표는 방어적인 조건문을 쓰는 것을 멈추고 의도를 표현하는 코드를 쓰는 것입니다.
방어적이고 중첩된 코드
// ❌ 방어적이고 중첩된 코드
function processarPedido(pedido) {
if (pedido) {
if (pedido.ativo) {
if (pedido.itens.length > 0) {
if (pedido.saldo >= pedido.total) {
// 마침내, 실제 로직...
console.log("Processando...");
}
}
}
}
}
여기서 문제는 검증 자체가 아니라 인지 부하입니다. 비즈니스 로직을 찾기 위해 여러 줄의 “소음”을 읽어야 합니다.
Guard Clauses (Early Return)
코드를 정리하는 첫 번째 전략은 Early Return(또는 Guard Clauses)입니다. 규칙은 간단합니다: 예외를 먼저 처리합니다. 조건이 코드를 실행하지 못하게 하면 즉시 반환합니다. 이렇게 하면 else가 필요 없어지고 들여쓰기 수준도 줄어듭니다.
// ✅ Guard Clauses: 흐름 정리
function processarPedido(pedido) {
if (!pedido || !pedido.ativo) return;
if (pedido.itens.length === 0) return;
if (pedido.saldo = 18 && user.hasLicense && !user.suspended) {
rentCar();
}
}
의도 읽기 (Predicates)
// ✅ 의도 읽기 (Predicates)
const podeAlugarCarro = (user) =>
user.age >= 18 && user.hasLicense && !user.suspended;
if (podeAlugarCarro(user)) {
rentCar();
}
if는 여전히 존재하지만 복잡성은 의미 있는 이름을 가진 함수로 추상화되어 향후 읽기가 쉬워집니다.
Switch를 Factory(Strategy)로 사용하기
많은 경우 복잡성은 불리언(예/아니오)이 아니라 범주형(타입 A, 타입 B, 타입 C)입니다. 이런 경우 Object Map이 좋지만, 각 타입마다 다른 로직이 필요하면 switch가 스파게티 코드가 될 수 있습니다.
잘못된 사용 (결합된 로직)
// ❌ 결합된 로직
function calcularFrete(tipo, peso) {
switch (tipo) {
case 'SEDEX':
const taxa = obterTaxa();
return (peso * taxa) + 10;
case 'PAC':
if (peso > 30) throw new Error('Peso limite');
return peso * 5;
}
}
올바른 사용 (Factory + Strategy)
// 비즈니스 전략 (Strategy Pattern)
const FreteSedex = { calcular: (p) => (p * 10) + 10 };
const FretePAC = { calcular: (p) => p * 5 };
// Switch를 라우터로 사용 (Factory)
const obterEstrategia = (tipo) => {
switch (tipo) {
case 'SEDEX': return FreteSedex;
case 'PAC': return FretePAC;
default: throw new Error('Tipo inválido');
}
};
// 깔끔한 사용
const estrategia = obterEstrategia('SEDEX');
estrategia.calcular(10);
결론
if를 없애는 것이 목표가 아니라 좋은 설계의 부수 효과가 되어야 합니다. 결정이 잘 분산될 때—정적 매핑을 위한 Object Maps, 검증을 위한 Guard Clauses, 명시적인 비즈니스 규칙, 라우팅 역할을 하는 Switches—코드는 반응형이 아니라 선언형이 됩니다.
결정을 없애는 것이 아니라 잡음을 없애는 것입니다. 이것이 문제의 컨텍스트가 기억에서 사라진 뒤에도 코드를 읽기 쉽게 유지하는 방식입니다.