布尔表达式又长又复杂[译]


这是我们 代码健康 系列中的另一篇文章。此文章的一个版本最初作为“Google 马桶测试” 系列的一集出现在全球的 Google 浴室中。您可以下载 适合打印的版本[1] 以在办公室中显示。

作者 孙一鸣

您可能在代码库中遇到过一些复杂、难以阅读的布尔表达式,并希望它们更容易理解。例如,假设我们想要确定披萨是否美味:

1
2
3
4
5
6
7
// Decide whether this pizza is fantastic.
// 判断这个披萨是否美味。

if ((!pepperoniService.empty() || sausages.size() > 0)
&& (useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO)) && hasCheese()) {
...
}

改进这一点的第一步是将条件表达式提取到一个命名良好的变量中:

1
2
3
4
5
6
7
boolean isPizzaFantastic =
(!pepperoniService.empty() || sausages.size() > 0)
&& (useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO)) && hasCheese();

if (isPizzaFantastic) {
...
}

然而,布尔表达式仍然太复杂。从给定的一组输入计算 isPizzaFantastic 的值可能会令人困惑。您可能需要拿起笔和纸,或者在本地启动服务器并设置断点以调试代码。

取而代之的是,尝试将细节分组到提供有意义、抽象的中间布尔值中。下面的每个布尔值代表一个明确定义的条件,您可以避免在一个表达式中混合使用 && 和 || 操作符。在不更改业务逻辑的情况下,您可以更轻松地了解布尔值之间的相互关系:

1
2
3
boolean hasGoodMeat = !pepperoniService.empty() || sausages.size() > 0;
boolean hasGoodVeggies = useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO);
boolean isPizzaFantastic = hasGoodMeat && hasGoodVeggies && hasCheese();

另一种选择是将逻辑隐藏在单独的方法中。这也提供了使用 保护子句 提前返回的可能性,进一步减少了跟踪中间状态的需要:

1
2
3
4
5
6
7
8
9
10
11
boolean isPizzaFantastic() {
if (!hasCheese()) {
return false;
}

if (pepperoniService.empty() && sausages.size() == 0) {
return false;
}

return useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO);
}

延伸阅读

Google 厕所文化:在马桶上测试程序

参考文档

本文是 isBooleanTooLongAndComplex 的中文翻译,感谢原文作者。更多信息请点击链接获取。

以下内容为译者注


  1. 1.Google 的马桶声名在外,不仅仅因为技术含量高,还因为从 2006 年开始,它们就被用来确保 Google 的产品完美无瑕,工程师们还会将软件测试题打印出来贴在厕所里,这是被内部员工称为 “马桶上的测试” 的企业文化。
  2. 2."guard clause(保护子句)" 是指在方法的开头使用条件语句来提前返回,从而避免继续执行后续代码。通过这种方式,可以在满足某些条件时立即退出方法,而不需要处理剩余的逻辑。这种方法可以简化代码结构,减少对中间状态的跟踪需求,提高代码的可读性和维护性。