1、失败项目复盘
代码乱》bug 多》排查问题耗时》复用度低》加班 996》士气低落……
症结 1:组件粒度过大、API 泛滥
- 组件臃肿:Service 组件的个数跟领域实体对象个数基本相当,必然造成个别 Service 组件变得非常臃肿——API 非常多,代码行数达到几千行;
- 职责模糊:业务逻辑往往跨多个领域实体,无论放在哪个 Service 都不合适,同样的,要找一个功能的实现逻辑也无法确定在哪个 Service 中;
- 代码重复 or 逻辑纠缠的两难选择:当遇到一个业务逻辑,其中的某个环节在另一个业务逻辑 API 中已经实现,这时如果不想忍受重复实现和代码,就只能去调用那个 API。但这样就造成了业务逻辑组件之间的耦合与依赖,这种耦合与依赖很快会扩散——新的 API 又会被其它业务逻辑依赖,最终形成蜘蛛网一样的复杂依赖甚至循环依赖;
- 复用代码、减少重复虽然是好的,但是复杂耦合依赖的害处也很大——赶走一只狼引来了一只虎。两杯毒酒给你选!
药方 1:倒金字塔结构——业务逻辑组件职责单一、禁止层内依赖
- 业务逻辑层应该被设计成一个个功能非常单一的小组件,所谓小是指 API 数量少、代码行数少;
- 由于职责单一因此必然组件数量多,每一个组件对应一个很具体的业务功能点(或者几个相近的);
- 复用(调用、依赖)只应该发生在相邻的两层之间——上层调用下层的 API 来实现对下层功能的复用;
- 于是系统架构就自然呈现出倒立的金字塔形状:越接近顶层的业务场景组件数量越多,越往下层的复用性高,于是组件数量越少。
症结 2:低内聚、高耦合
- 业界关于“复用性”的认识存在一个误区——认为包括业务逻辑组件在内的任何层面的组件都应该追求最大限度的可复用性;
- 复用当然是好的,但那应该有个前提条件:不增加系统复杂度的情况下的复用,才是好的。
- 什么样的复用会增加系统复杂性、是不好的呢?前面提到的,一个业务逻辑 API 被另一个业务逻辑 API 复用——就是不好的:
损害了稳定性:因为业务逻辑本身是跟现实世界的业务挂钩的,而业务会发生变化;当你复用一个会发生变化的 API,相当于在沙子上建高楼——地基是松动的;
药方 2:复用的两种正确姿势——打造自己的 lib 和 framework
- lib 库是供你(应用程序)调用的,它帮你实现特定的能力(比如日志、数据库驱动、json 序列化、日期计算、http 请求)。
- framework 框架是供你扩展的,它本身就是半个应用程序,定义好了组件划分和交互机制,你需要按照其规则扩展出特定的实现并绑定集成到其中,来完成一个应用程序。
- lib 就是组合方式的复用,framework 则是继承式的复用,继承的 Java 关键字是 extends,所以本质上是扩展。
- 再展开一下:lib 既可以是第三方的(log4j、httpclient、fastjson),也可是你自己工程的(比如你的持久层 Dao、你的 utils);
- framework 同理,既可以是第三方的(springmvc、jpa、springsecurity),也可以是你项目内封装的面向具体业务领域的(比如 report、excel 导出、paging 或任何可复用的算法、流程)。
症结 3:抽象不够、逻辑纠缠——High Level 业务逻辑和 Low Level 实现逻辑纠缠
- 可读性变差:两个维度的复杂性——业务复杂性和底层实现的技术复杂性——被掺杂在了一起,复杂度 1+1>2 剧增,给其他人阅读代码增加很大负担;
- 可维护性差:可维护性通常指排查和解决问题所需花费的代价高低,当两个 level 的逻辑纠缠在一起,会使排查问题变的更困难,修复问题时也更容易出错;
- 可扩展性无从谈起:扩展性通常指为系统增加一个特性所需花费的代价高低,代价越高扩展性越差;与排查修复问题类似,逻辑纠缠显然也会使添加新特性变得困难、一不小心就破坏了已有功能。
药方 3:控制逻辑分离——业务模板 Pattern of NestedBusinessTemplate
解决“逻辑纠缠”最关键是要找到一种隔离机制,把两个 Level 的逻辑分开——控制逻辑分离,分离的好处很多:
- 根据经验,当我们着手维护一段代码时,一定是想先弄清楚它的整体流程、算法和行为,而不是一上来就去研究它的细枝末节;
- 控制逻辑分离后,只需要去看 High Level 部分就能了解到上述内容,阅读代码的负担大幅度降低,代码可读性显著增强;
- 读懂代码是后续一切维护、重构工作的前提,而且一份代码被读的次数远远高于被修改的次数(高一个数量级),因此代码对人的可读性再怎么强调都不为过,可读性增强可以大幅度提高系统可维护性,也是重构的最主要目标。
- 同时,根据我的经验,High Level 业务逻辑的变更往往比 Low Level 实现逻辑变更要来的频繁,毕竟前者跟业务直接对应。当然不同类型项目情况不一样,另外它们发生变更的时间点往往也不同;
- 在这样的背景下,控制逻辑分离的好处就更明显了:每次维护、扩充系统功能只需改动一个 Levle 的代码,另一个 Level 不受影响或影响很小,这会大幅降低修改成本和风险。
设计模式——业务模板 Pattern of NestedBusinessTemplat,可以非常简单、有效的分离两类逻辑
症结 4:无处不在的 if else 牛皮癣
- if else if ...else 以及类似的 switch 控制语句,本质上是一种 hard coding 硬编码行为,如果你同意“magic number 魔法数字”是一种错误的编程习惯,那么同理,if else 也是错误的 hard coding 编程风格;
- hard coding 的问题在于当需求发生改变时,需要到处去修改,很容易遗漏和出错;
药方 4:充血枚举类型——Rich Enum Type
在 enum 枚举类型基础上进一步抽象封装,得到一个所谓的“充血”的枚举类型
enum NOTIFY_TYPE { //1、定义一个包含通知实现机制的“充血”的枚举类型 email("邮件",NotifyMechanismInterface.byEmail()), sms("短信",NotifyMechanismInterface.bySms()), wechat("微信",NotifyMechanismInterface.byWechat()); String memo; NotifyMechanismInterface notifyMechanism; private NOTIFY_TYPE(String memo,NotifyMechanismInterface notifyMechanism){//2、私有构造函数,用于初始化枚举值 this.memo=memo; this.notifyMechanism=notifyMechanism; } //getters ... }
总结:
- 职责单一、小颗粒度、高内聚、低耦合的业务逻辑层组件——倒金字塔结构;
- 打造项目自身的 lib 层和 framework——正确的复用姿势;
- 业务模板 Pattern of NestedBusinessTemplate——控制逻辑分离;
- 充血的枚举类型 Rich Enum Type——消灭硬编码风格的 if else 条件判断;
https://mp.weixin.qq.com/s/msB50Wp1qrehoSastZBEbQ