保持if语句无副作用
摘要
文章建议保持if语句无副作用以提高代码可读性,通过示例说明常见陷阱和更清晰的替代方案。
<p><a href="https://lobste.rs/s/buroqq/keep_if_clauses_side_effect_free">评论</a></p>
查看缓存全文
缓存时间: 2026/09/26 05:21
# 保持if子句无副作用
来源:https://www.teamten.com/lawrence/programming/keep-if-clauses-side-effect-free.html
避免编写带有副作用的`if`子句:
````
if (enqueueMessage(message)) {
...
}
````
`if`语句的唯一功能是测试条件是否为真,而非作为测试的副作用来执行代码。直接使用返回值(如上例)的一个问题是返回值的*含义*不明确:`enqueueMessage()`返回true是表示消息已入队,还是表示队列已满?请通过变量使其显式化:
````
boolean success = enqueueMessage(message);
if (success) {
...
}
````
以上代码更符合自然语言表达:"如果操作成功,..."。无副作用的方法(我们希望)能通过返回值清晰表达意图,例如`isEmpty()`。这种情况不仅限于布尔值方法。以下代码不够清晰:
````
if (flushQueue() == 0) {
...
}
````
而改写为:
````
int itemsFlushed = flushQueue();
if (itemsFlushed == 0) {
...
}
````
在`if`语句中调用有副作用方法的另一个缺点是:快速浏览代码的读者可能完全忽略该调用。对比上述两个`flushQueue()`示例:第一个可能被误认为是查询队列属性的函数;第二个则清晰分为两部分——先执行操作,再进行测试。考虑我在线上环境中见过的这段代码:
````
if (!categorySeen.add(categoryID)) continue;
````
我最初无法理解循环体中向集合添加元素的逻辑。由于预期`if`语句内容不应产生副作用,我将这行代码误读为:
````
if (!categorySeen.contains(categoryID)) continue;
````
即使注意到`add()`方法,我也未能理解其功能——您能理解吗?(根据`Set`的Java文档,`add()`方法在集合未包含指定元素时返回`true`)。注意其中因`continue`导致的复杂逻辑(参见避免使用continue:https://www.teamten.com/lawrence/programming/avoid-continue.html):只有当`categoryID`*并非*已见过时才会执行后续代码——这里包含三重否定:`continue`的否定、`!`运算符的否定,以及API描述中的"不包含"含义。多么混乱!不如这样改写:
````
boolean isNewCategory = categorySeen.add(categoryID);
if (isNewCategory) {
...
}
````
以下是副作用方法与短路求值滥用的危险组合:
````
if (queueNeedsFlushing() && flushQueue() == 0) {
...
}
````
第二个调用极易被忽略。短路求值本应用于保护无副作用语句的求值错误,例如:
````
if (count > 0 && total/count >= MIN_AVERAGE) {
...
}
````
或:
````
if (name != null && name.endsWith(".png")) {
...
}
````
切勿利用此机制规避有副作用的方法调用。这正是`if`语句的诞生初衷:
````
if (queueNeedsFlushing()) {
int itemsFlushed = flushQueue();
if (itemsFlushed == 0) {
...
}
}
````
若认为简洁版本优于上述三行代码,实则是对你自身和未来读者的损害。当后续因频繁遗漏重要方法调用而导致代码追踪困难时,多写两行代码是微不足道的代价。
~ 查看所有编程文章 ~ (https://www.teamten.com/lawrence/programming/)
相似文章
通过一个“无用”的if语句将代码性能提升四倍
一篇博客文章展示了添加一个看似无用的条件检查如何通过允许CPU的分支预测器消除数据依赖,从而显著提升循环性能,在特定的压缩算法中实现了高达4倍的加速。
捕获子句作为效果
本文探讨了Rust中显式捕获子句作为move表达式的替代方案,提出了一种将引用转换为所有权值的一等语言特性,并分析了多种闭包捕获模式。
像人类会维护它一样编写代码
文章警告说,依赖LLM编写代码而不保持良好模式,会教会AI不良习惯,导致代码库充满重复逻辑,代码质量不断恶化。
宁可重复,也不要错误的抽象(2016)
文章认为,重复比错误的抽象代价更低,并建议开发者避免强行引入抽象,以免后续因条件判断而变得复杂。
Prolog编程的陷阱
关于Prolog编程中常见陷阱的指南,强调使用纯声明式构造而非不纯的构造,如cut、全局状态和低级I/O。