[转载] 布尔表达式是不是太长、太复杂了?
原文出处: https://testing.googleblog.com/2024/04/isbooleantoolongandcomplex.html
作者: Yiming Sun
原文发布日期: 2024-04-25
许可协议: 本文为 Google Testing Blog 文章的中文翻译,所有权利归原文作者所有。
本文是 Code Health 系列的另一篇文章。该文曾作为 Testing on the Toilet 的一期,出现在 Google 全球各地的洗手间里。你可以下载便于打印的版本张贴在办公室里。
—— Yiming Sun
你大概在代码库里遇到过一些复杂、难读的布尔表达式,并且希望它们能更容易理解一些。举个例子,假设我们要判断一块披萨是不是「极好的」(fantastic):
// 判断这块披萨是不是极好的。
if ((!pepperoniService.empty() || sausages.size() > 0)
&& (useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO)) && hasCheese()) {
...
}
改善它的第一步,是把条件提取成一个命名得当的变量:
boolean isPizzaFantastic =
(!pepperoniService.empty() || sausages.size() > 0)
&& (useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO)) && hasCheese();
if (isPizzaFantastic) {
...
}
不过这个布尔表达式还是太复杂了。给定一组输入,要计算出 isPizzaFantastic 的值仍然容易让人犯迷糊——你也许得拿支笔找张纸算一算,或者干脆在本地起个服务、打几个断点去跟踪。
换个思路:试着把这些细节归并成若干中间布尔量,让它们各自提供有意义的抽象。下面每一个布尔值都代表一种明确定义的属性,而且你再也不必在同一个表达式里把 && 和 || 混在一起用了。在不改变业务逻辑的前提下,各个布尔量之间的关系就更容易看清了:
boolean hasGoodMeat = !pepperoniService.empty() || sausages.size() > 0;
boolean hasGoodVeggies = useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO);
boolean isPizzaFantastic = hasGoodMeat && hasGoodVeggies && hasCheese();
另一种做法,是把这段逻辑藏进一个单独的方法里。这样做还带来了使用卫语句(guard clause)提前返回的机会,进一步减少了需要在脑子里维护中间状态的需要:
boolean isPizzaFantastic() {
if (!hasCheese()) {
return false;
}
if (pepperoniService.empty() && sausages.size() == 0) {
return false;
}
return useOnionFlag.get() || hasMushroom(ENOKI, PORTOBELLO);
}