为什么业务代码看起来总是不够清晰直观

好代码不仅仅关乎风格和习惯,更关乎对技术和设计的理解。


引子

写出若干行好代码,并不比做出一个好的设计方案更加容易。我个人认为,代码的最高境界是清晰、简洁、设计优雅。能够做到这一点的业务代码,实在是极少。很多开发人员是逻辑感不错,但表达水平糟糕,而程序设计是一项逻辑、设计与表达结合的活动,三者缺一不可,而业界目前仍然更注重逻辑和技术层面(面试考察的主要偏重于此)。

很多程序员在其开发生涯中接触到的大多是业务系统,写下来的也是业务代码。然而,放眼望去,很多业务代码看上去很不够直观清晰,很难一眼就能理解代码说的是什么。 为什么会这样? 本文探讨其中的原因。

原因探讨

缺乏清晰的业务语义和顶层视图

这是很多很多业务代码的通病。 放眼望去,只有一段段字符的“飞流直下三千尺”,很难清晰直观地看出其业务语义和顶层视图。

  • 业务语义: 这段代码到底完成的是什么业务意图?为什么要做这件事 ?Explain what and why , not merely how ;
  • 顶层视图: 整个业务流程包括哪些步骤,能否在十行代码里一眼就能看清楚,比如 STEP1—STEP10 。原则上,入口方法就应当只包括十行代码,就是这若干个步骤的业务语义;而每个业务语义都是一个子函数。

大多数业务代码无非是做如下事情:

  • VCRU:即校验数据(Validate)、查询数据(Retrieve),插入数据( Create ),更新数据(Update) 。通过这四件事的组合来实现业务意图,其中尤以 R 和 U 居多;
  • Send/Receive: 即发送消息(Send)、接收消息(Receive)。

是否能够用声明式的可编排的方式来清晰地展示业务意图呢?

要想写出清晰直观的业务代码,有若干建议:

  • 遵循“设计先行”原则,从设计上把握整体和全局;
  • 遵循“自顶向下”和“意图导航”的程序设计和编写法则;
  • 高层展现语义,底层呈现细节;
  • 业务关注点抽离成业务组件,做成可编排的业务流程。

业务中掺杂技术细节

为什么业务代码总是显得不够清晰 ? 因为开发人员总喜欢把业务与技术细节掺杂在一起。如下所示:

要改善这样的代码,需要将技术细节提取成可复用的基础库,而业务则可写成声明式的。

先创建一个 StreamUtil 工具类

public class StreamUtil {
  public static <T,R> List<R> map(List<T> data, Function<T, R> mapFunc) {
    if (CollectionUtil.isEmpty(data)) { return new ArrayList();  }
    return data.stream().map(mapFunc).collect(Collectors.toList());
  }

  public static <T> List<T> filter(List<T> data, Predicate<T> filterFunc) {
    if (CollectionUtil.isEmpty(data)) { return new ArrayList();  }
    return data.stream().filter(filterFunc).collect(Collectors.toList());
  }

  public static <T,R> List<R> filterAndMap(List<T> data, Predicate<T> filterFunc, Function<T, R> mapFunc) {
    if (CollectionUtil.isEmpty(data)) { return new ArrayList();  }
    return data.stream().filter(filterFunc).map(mapFunc).collect(Collectors.toList());
  }
}

以上就可以写成如下方式,实现了类声明式编程。

rules = StreamUtil.filter(rules, rule -> BaselineUtils.matchPlatform(rule, platforms));
baseLineRules = StreamUtil.filter(rules, rule -> rule.getFamily() == BaselineRule.FAMILY_SYSTEM);

重复模板代码

业务中往往充斥着不少重复代码,其原因在于:

  • 第一个人没有意识到其潜在的可复用性,没有良好地抽象出来;后面的人则只好 CVM(Copy-Paste-Modify)。
  • 没有将(技术和业务)关注点分离出来;许多关注点混杂在一起,难以复用。

要避免重复模板代码,有若干建议:

  • 分离通用和差异部分;
  • 对于通用部分,考虑可复用性和可扩展性;
  • 考虑模板方法模式和函数式编程来解耦通用和差异;
  • 放在 Service 层, 而非 Controller 层。

多重嵌套的条件语句

多重嵌套的条件语句,也是导致业务代码“不堪卒读”的重要原因之一。通常是因为:

  • 写代码的人没有仔细理顺其中的逻辑,按照思维中的逻辑顺着写下来了;
  • 多个业务逐渐累积上去,而初始又缺乏设计,导致后面的人效仿而堆砌。

如何解耦多重条件语句呢?

  • 使用 Map 结构替代 Switch ;
  • 使用 if-return 的卫述句避免头重脚轻的 if-else ,尽早返回 ;
  • 使用策略模式分离大段的功能相似的业务逻辑;
  • 重新理顺整个的逻辑,理解关键点,持续小步重构,用更清晰的方式表达出来。

代码坏味的影响

一些不良的风格和习惯导致的代码坏味,也会对代码的意图解读造成阻碍。

代码坏味可阅:“代码的味道”


小结

可以看到,第一个人写下的代码是很重要的。如果第一个人没有很好地设计和抽象,而是写成了逻辑流,那么后面的人就会效仿,逐渐形成“代码堆砌”。可谓是“始作俑者”。要远离这些“罪过”,怎么办呢?

代码即设计。

  • 代码应当能够凸显出业务语义和设计意图,而不是需要人通过代码去推断出来;
  • 代码的业务部分与技术细节应当分离,避免业务被技术细节淹没;技术部分是可复用的;
  • 将原子的业务关注点分离出来;原子的业务关注点是可以复用和扩展的;
  • 多个动作的语句,拆成多行;放在一行不利于后续改动,且容易潜藏 BUG ,不易定位和排查。

有一个很简单的代码技巧,却非常管用: 时刻注意抽离出可测试的子函数,避免混杂在主流程里。


posted @ 2021-03-25 07:40  琴水玉  阅读(438)  评论(0编辑  收藏  举报