重构 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(提前返回)
组织代码的第一种策略是 提前返回(或 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();
}
}
意图阅读(谓词)
// ✅ 意图阅读(谓词)
const podeAlugarCarro = (user) =>
user.age >= 18 && user.hasLicense && !user.suspended;
if (podeAlugarCarro(user)) {
rentCar();
}
if 仍然在,但复杂度已经抽象到一个具备表达性名称的函数中,便于以后阅读。
Switch 作为工厂(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 用于校验,显式的业务规则以及 Switch 充当路由器——代码就不再是被动的,而是声明式的。
你并不是在消除决策,而是在消除噪音。这种代码即使在问题上下文已经不再新鲜时,仍然保持可读性。