请不要写令人困惑的条件

2025-06-07

请不要写令人困惑的条件

你是否曾盯着同一行代码超过几秒钟?你是否经常发现一些难以解析的条件语句?在本文中,我们将探讨代码中令人困惑的条件语句是如何体现的。此外,我们还将探索不同的重构技巧,以提升条件控制流的可读性。

双重否定

当我们否定一个变量的名称本身就带有非肯定语气时,就会出现双重否定。请考虑以下示例。

// Non-affirmative Naming Convention
const isNotReady = doSomething();
const isForbidden = doSomething();
const cannotJoinRoom = doSomething();
const hasNoPermission = doSomething();

// Negating once results in an awkward double negative situation.
console.log(!isNotReady);
console.log(!isForbidden);
console.log(!cannotJoinRoom);
console.log(!hasNoPermission);
Enter fullscreen mode Exit fullscreen mode

这与肯定式风格相反,肯定式风格需要的认知负荷较小,因为解析时无需费力否定变量名称来提取其逻辑的本质。换句话说,肯定式变量背后的意图可以直接解读——无需任何繁琐步骤!请考虑对上例进行以下修改。

// Affirmative Naming Convention
const isReady = doSomething();
const isAllowed = doSomething();
const canJoinRoom = doSomething();
const hasPermission = doSomething();

// Negation actually makes sense here!
console.log(!isReady);
console.log(!isAllowed);
console.log(!canJoinRoom);
console.log(!hasPermission);
Enter fullscreen mode Exit fullscreen mode

不幸的是,由于标准和向后兼容性,有时非肯定性的命名约定是不可避免的。以HTML DOM APIHTMLInputElement#disabled的属性为例。标签存在该属性会告诉浏览器(在视觉和字面上)禁用元素的表单控件。否则,该属性的缺失会导致其显示其默认行为,即接受用户输入。这是 HTML 人机工程学的一个不幸的副作用。disabled<input><input>

<!-- Normal Checkbox (Default) -->
<input type="checkbox" />

<!-- Disabled Checkbox -->
<input type="checkbox" disabled />
Enter fullscreen mode Exit fullscreen mode

尽管如此,我们仍然应该尽可能地追求肯定的命名约定。它们更容易阅读和解析,因为不需要总是在心里否定变量名。这条规则适用于变量名和函数名。

非肯定性控制流

双重否定的下一种形式稍微微妙一些。

// Suppose that we _do_ follow affirmative naming conventions.
const isAllowed = checkSomething();
if (!isAllowed) {
    // There is something funny about this...
    doError();
} else {
    // Notice how the `else` block is practically a double negation?
    doSuccess();
}
Enter fullscreen mode Exit fullscreen mode

如上所示,非肯定风格也适用于条件控制流。回想一下,else代码块实际上是对相应条件的否定if。因此,我们必须在这里扩展肯定风格。解决方法其实相当简单。

// Just invert the logic!
const isAllowed = checkSomething();
if (isAllowed) {
    doSuccess();
} else {
    doError();
}
Enter fullscreen mode Exit fullscreen mode

同样的规则也适用于平等和不平等的检查。

// ❌ Don't do this!
if (value !== 0) {
    doError();
} else {
    doSuccess();
}

// ✅ Prefer this instead.
if (value === 0) {
    doSuccess();
} else {
    doError();
}
Enter fullscreen mode Exit fullscreen mode

有些人甚至会为了用肯定句式否定某个条件而让条件块留空。虽然我并不提倡每个人都这么做,但我明白为什么这样对某些人来说可能更具可读性。instanceof例如,运算符如果没有括号就很难被否定。

if (obj instanceof Animal) {
    // Intentionally left blank.
} else {
    // Do actual work here (in the negation).
    doSomething();
}
Enter fullscreen mode Exit fullscreen mode

提前退货的例外情况

顺便提一下,对于提前返回的条件控制流,存在一些特殊例外。在这种情况下,可能需要进行否定。

if (!isAllowed) {
    // Return early here.
    doError();
    return;
}

// Otherwise, proceed with the success branch.
doSuccess();
Enter fullscreen mode Exit fullscreen mode

但是,只要有可能,我们仍然应该尝试反转逻辑,如果它可以减少嵌套、减少缩进级别并提高肯定样式的可读性。

// Prefer affirmative early returns.
if (isAllowed) {
    doSuccess();
    return;
}

// If we did not invert the logic, this would have been
// nested inside the `!isAllowed` conditional block.
if (!hasPermission) {
    doPermissionError();
    return;
}

// When all else fails, do something else.
doSomethingElse();
return;
Enter fullscreen mode Exit fullscreen mode

以肯定的方式(没有提前返回)表达相同控制流的另一种方法如下。

// Hooray for the affirmative style!
if (isAllowed) {
    doSuccess();
} else if (hasPermission) {
    doSomethingElse();
} else {
    doPermissionError();
}
return;
Enter fullscreen mode Exit fullscreen mode

当然,还有很多其他方法可以交换、反转和重构代码——每种方法的优缺点完全是主观的。因此,保留肯定性惯例就成了某种艺术形式。无论如何,只要我们坚持肯定性风格的一般准则,代码的可读性就会不断提高。

复合条件

AND有了诸如and 之类的逻辑运算符,情况就变得稍微复杂一些了OR。例如,我们如何以更肯定的风格重构下面的代码?

// This is fine... but there has to be a better way,
// right? There are just too many negations here!
if (!isUser || !isGuest) {
    doSomething();
} else {
    doAnotherThing();
}
Enter fullscreen mode Exit fullscreen mode

对于复合条件,我们引入布尔代数中最被低估的定律:德摩根定律

// Suppose that these are **any** two Boolean variables.
let a: boolean;
let b: boolean;

// The following assertions will **always** hold for any
// possible pairing of values for `a` and `b`.
!(a && b) === !a || !b;
!(a || b) === !a && !b;
Enter fullscreen mode Exit fullscreen mode

感谢德摩根定律,我们现在有办法在条件中“分配”否定,然后“翻转”其运算符(从&&||,反之亦然)。

虽然以下示例仅涉及二元比较(即两个元素),但只要我们遵循运算符优先级,德·摩根定律就适用于任意数量的条件变量。也就是说,&&运算符总是先于||运算符求值。

// By De Morgan's Laws, we can "factor out" the negation as follows.
if (!(isUser && isGuest)) {
    doSomething();
} else {
    doAnotherThing();
}
Enter fullscreen mode Exit fullscreen mode
// Then we simply invert the logic as we did in the previous section.
if (isUser && isGuest) {
    doAnotherThing();
} else {
    doSomething();
}
Enter fullscreen mode Exit fullscreen mode

现在,是不是可读性更强了?利用德摩根定律,我们可以清理掉那些“否定太多”的条件句。

结论

至此,整体主题应该已经清晰了。我们应该尽可能避免编写那些迫使读者费力费力(不必要地)的代码。在本文中,我们讨论了以下技巧:

  1. 鼓励肯定的命名约定。
    • 避免使用否定词/前缀,nonot、、、dis-mal-
    • 更喜欢积极的等价物。
  2. 反转条件控制流(如果可能)以适应肯定风格。
    • 交换、反转和重构分支时可以随意尝试。
    • 提前回报可能需要否定。
  3. 使用布尔代数的一些技巧来反转条件。
    • 德摩根定律对于重构来说是特别强大的工具!

现在就行动起来,用更清洁的条件来祝福世界吧!

文章来源:https://dev.to/somedood/please-dont-write-confusing-conditionals-2n32
PREV
使用 React 构建时你应该知道的库
NEXT
请不要嵌套承诺多个异步操作更好的方法异步/等待方法结论