我们的代码是因业务而生,如果没有业务也就没有代码,所以,代码首先要反应的就是业务,而实现业务的具体手段则要隐藏在背后。
在继续浏览下面的内容之前,请先回答一个问题,当用IDE首次打开一个陌生的工程后,让你对它产生初始认知的是什么?what?工程名?好吧,前提是这个工程名不是几个单词的首字母缩写。README?你确定每个项目都有这个文件?即使有,里面信息的用处又有多大呢。而我的回答则已经在本文的题目中了。
反例
代码包是我们人民群众喜闻乐见的一种划分代码整体结构的工具,用好它有助于明确业务领域,凸显核心概念,让后来者从业务角度更好更快的理解系统。难以理解的系统修改起来会很困难,且修改的结果难以预料。因此,包的定义也就来自于业务领域或核心概念,这应作为定义包的首要原则。不过,在没有总体把控的情况下,这个原则总有被打破的时候,甚至根本没有被遵守,造成的结果就是如下图这个工程包结构。

上图所示的包结构最大问题是完全没有一个统一的定义原则。比如有的是依据业务领域划分,也就是通常所说的业务模块,如 ad,loan,bill;有的依据功能,而所依据的功能还不只是如 notice ,purchase 这样的业务功能,还有如 bpm,thread这样的技术功能;有的依据调用方式,如remote。这些包的定义可以说是随心所欲,造成的结果就是很难让人在短时间内抓住该系统的核心l。
核心概念命名顶层包
先来看作为顶层包的 ad 和 bill 。这两个包给人的第一感觉就是,它们一定是这个系统中非常重要的业务概念才会放在这个位置。事实上也的确如此,ad(凭证)和bill(单据)是这个系统在业务上的两个最核心概念。虽然这两个包符合以业务领域与核心概念命名原则,不过也不是完全没有问题。bill 很好理解,问题出在 ad 上。ad 这个缩写就很令人费解,如果不点进去查看具体代码,你是无论如何都不可能知道这个包是干什么的。所以类似这样特有的缩写是十分不友好的。缩写当然可以用,不过最好是个通用的概念或常识,否则缩写就不是一个好的选择,如果一定要彰显个性的用一个自创的缩写,那么就请在这个包中加一个package-info.java,对包进行说明并介绍缩写的来历(当然,bill包同样需要package-info)。
隐藏实现手段
再来看这个bpm包(bpm3是对bpm的版本升级),该包下是与流程引擎相关的代码。这个包的存在就是典型的破环包命名原则的例子。流程引擎的根本目的是为业务功能提供支持,属于实现业务的手段,不属于业务概念范畴,所以这个包是不应该存在的,退一步来讲,即使存在也不应与 ad 和 bill 处于同等级别。好的选择是将bpm中的代码与调用它的代码融合在一起(从OO的角度说就是要隐藏实现手段),然后删除这个对体现业务没有任何帮助的包。
典型的技术思维
remote包的问题与bpm完全一样,该包下的代码是调用其他微服务的接口,所以名为remote。这是典型的技术思维,即用实现方式或手段给包命名,先不说业务,就算是开发人员首要关心的也不是什么remote还是local,就算你调用的是半人马座那边的服务器,又跟我有什么关系呢?我们关心的只是目的,而不是手段和方式。所以它的重构方式与bpm包一样。
至于common和util,差不多所有的工程都会有这样的包,只要是开发就知道它们的作用,所以,即使放到与业务核心概念包相同的级别,也不会对理解系统有什么负面影响。不过,更适合的方式是将这些与业务无关的代码放入名为common和util的子模块中。
定义能反应业务的包结构
那么,如何才能给出一个好的包结构,从而可以清晰的反应业务呢?
1.分析系统的核心功能或概念是什么。以上面这个工程为例,它只是一个系统的众多微服务中的一员,系统的核心功能只有两个——1.单据创建和审批,2.为完成审批的单据生成凭证。所以,顶层包其实只需ad(我给这个包重新命名为voucher)和bill两个即可。想象一下,如果上面这个工程的顶层包只有ad(凭证)和bill(单据)会是什么感觉。就算这两个顶层包下仍会有很多包,但当我们第一眼看到这样的包结构时,立刻就会意识到这个系统的核心概念就是它们两个,其他的功能则全部是围绕这两个核心概念来工作的。这对从业务上认识一个陌生的工程会起到很大的帮助作用。当然,这其实也符合人类大脑处理事务能力的上限条件,杂乱无章与井然有序哪个更易处理自不必说。
2.当遇到一个不太容易归纳出核心功能和概念的系统时,比如系统实现的是一个较大的,不连续的,时间跨度较长的流程,流程对任务的完成顺序和时间也没有严格的要求,流程的每个节点上的各个功能又都是必不可少的。这样的系统在归纳核心功能或概念上有一定的难度,因为一个都不能少,无论少了谁,系统都无法向外部提供业务功能。这样的系统则适合根据用例来定义包结构,更直观的体现是,顶层包名对应菜单或是页面,顶层包下的包则可根据功能命名。
上述这种情况,可以参考spring的整体设计和内部包的定义。spring-core作为spring-beans,spring-context,spring-aop等这些直接向外部提供各种功能的基础,负责着类的信息数据的处理,core下的包都是以功能进划分,beans,context,aop当然也是。
以功能模块划分则顶层包也许会比较多,不过这其实并不是什么缺点,因为这些包都是业务领域的投射。包多不是问题,违反包命名原则、混淆视听的包定义才是问题。
当然,包结构和系统架构一样,并不是从开始就能给出一个一成不变的设计。它们都是随业务的发展和开发人员认识的持续加深而进行不断调整的。不过,最初的基调一定要在写下第一行代码时定好,后来者通常会在这个基调的基础上展开工作,但如果最初的基调没有定好,则后面的路就会越走越歪。

文章探讨了代码包命名和结构设计的重要性,强调了以业务领域和核心概念为命名原则。通过反例展示了不遵循原则导致的混乱,并提出核心概念命名顶层包、隐藏实现手段和定义反映业务的包结构等建议。重构技术思维,避免使用实现方式命名包,以提升代码可读性和理解性。

被折叠的 条评论
为什么被折叠?



