[转载] 布尔表达式是不是太长、太复杂了?


原文出处: 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);
}