作者:borisyang,腾讯 WXG 应用开发工程师
作为程序员,刚刚开始学会写代码,常常是接过需求就开始撸代码。有时候发现,写完代码,需求变了。更多时候,觉得写业务代码枯燥无聊,没有技术含量。另外一边的事实却是,项目里面研发人数变多了,项目的质量缺却变低了,多人开发也不过是一个个单打独斗的组合而已。
1 研发环境日益成熟
经历过 PC 互联网的不断深入发展,移动互联网的蓬勃生长,互联网进入了成熟繁荣期,研发环境也发生了巨大变化;从原来一个人,一把键盘,写完代码就上线,变成了更加规范的研发体系和更多人参与的共同协作。
研发流程不断加速
为了尽可能提高需求交付速度,跟上市场的变化,我们通过不断提搞软件交付的速度,尽可能的,从需求,编码实现,测试,发布的流程中不断优化,利用 CICD,加速迭代。
多人协作无处不在
现在的软件开发团队,即使再小,也有 2-3 人一起研发,更别提测试,运维,运营,产品人员。一方面是软件产品的竞争日趋激烈,需求日益复杂,堆砌人力成了必然;另外一方面是专业性的要求,精密的行业自然要求精细化的职业划分。
2 困局
研发流程的速度提上去了,团队的人也变多了,但是,需求变更依旧让广大研发同学感到痛苦,项目质量还在日益变差。
需求变更之痛
需求变更的痛苦为难了广大研发同学,前脚刚为了优化性能,采用了 kv 存储,后脚需求就变成了要支持模糊查询;这是一种典型的架构设计不合理,导致业务需求的实现方式受限。
更令人痛苦的,还有产品需求变动多,今天简单实现下,上线看看效果,明天用户脾气很大提了个诉求,再加一个功能上线,产品功能变成补丁加补丁。一方面是研发同学渴望一个完整又严谨的需求,提完需求进入研发阶段就不许改;另一方面是产品同学受到各方面的压力,只希望先把主要问题解决下,细枝末节以后再说。
项目质量变差
项目质量变差,一部分归功于补丁代码的产生,迫于时间受限,先上一个补丁,却打开了破窗的先锋,下一次,下一个同学就更敢于加补丁代码。一个个临时的 if else 不断堆砌,最终导致了整个项目的代码腐烂。我曾经维护过一个代码片段,超过 20 个 if else,中间还有些过时的错误注释夹杂其中,维护起来令人苦不堪言。
项目代码腐烂的另外一个原因是多人协作,团队的人越多,代码反而变得越烂似乎成为了趋势;为什么多人协作没有提高代码质量呢?一方面,多人协作实际上只是分摊的需求实现而已,大多数需求实现的分配中,反而尽可能将协作变少,避免实现受阻。另外一方面是,不同人的代码模块,设计意图和代码风格也截然不同。维护前人代码,如果没有全局视角,了解设计意图,也只能是往里面加补丁代码了。
项目代码腐烂容易导致程序员出现错觉,一是业务代码没什么料在里面,不如搞基础建设;二是业务需求不可能完整又严谨,最终也会变来变去的,最终质量低下的锅,一大半要给提需求的人。
3 怎么办
在写代码之前要进行设计和建模。相比历史短暂的 IT 行业,很多工业,建筑行业的精密性,都离不开前期的设计,在分析设计之后,按照图纸规划施工,写代码也应当如此。
设计建模的有效性源于,一,重新回到业务的跑道,跟业务一致;二,设计建模才能让协作真实有效;
为什么研发实现需求跟业务一致很重要呢?研发和业务需求的摩擦,本质是研发实现跟实际需求不一致,无论是研发走偏了,没有理解需求,还是需求本身不能满足涉众的利益,都会使得最终上线的功能需要回炉重造,折磨项目组的成员。业务项目,需求很重要,是整个项目质量的源头,源头的问题不处理好,会一直发散扩大,问题传递到尾部,甚至到了产品上线,对整体造成的损耗越大。从设计的语言上看,设计的层次有所不同,不仅仅有代码细节上的设计,也有业务上高层次的设计,高层次的设计是用业务的术语去表达,最贴近业务实际情况,也能帮助研发同学发现业务中不合理的点。
设计建模为何能让协作真实有效?我早先体验的协作流程,无非就是各自工作在自己的领域内部,彼此尽量减少要协作的内容,避免过多阻塞。研发侧的协作,因为缺乏设计,不好分工,另外一方面,多人写同一个模块,也会引发冲突,所以更多将协作放在在 code review 上。但 code review 作用范围也有限,一方面,review 成本较高,逐行阅读代码来厘清设计对代码质量要求很高,另一方面,review 时间节点往往发生较晚,临近发布的时候,调整设计也不大可能。进行设计建模能够让协作变得有效,一方面,设计建模前期是沟通和信息对齐,将协作的内容提前,一方面,采用合适的图形化工具,review 的成本是相对较低的。
4 怎么设计和建模
设计和建模分为好几个部分
业务建模,关注业务,不关注具体的实现
系统建模,关注所建设系统的边界,找准在业务中的系统的职责和约束
分析与设计,定位核心领域,找到实际的类,厘清类的职责和类之间的关系,通过设计使得代码抽象复用
业务建模
总体上来说,业务建模主要聚焦于分析涉众利益,厘清业务流程。从工具上来说,主要是用例图,流程图;从内容上来说,主要是找人(利益涉众,系统执行者),找业务实体(其余系统,相关的重要对象)。
分析涉众利益
分析涉众利益之前,需要找到涉众,一般要经历以下步骤:
找到软件产品的愿景,愿景表达了软件产品带来的核心意义
找到利益相关的的涉众和其利益诉求
表格是一个很好的表达方式,我负责的一个商户从第三方商城采购刷脸设备,由仓配系统配送设备的业务,可以表达如下:
愿景:将设备更多更快且准确无误成本低地卖给商户。
涉众 | 利益诉求 |
---|---|
物料组老板 | 在准确无误的情形下,更多更快成本低地将设备卖给商户 |
设备渠道商 | 准确无误且快速地配送设备给商户 |
商户 | 更快地获得自己购买的设备 |
物料运营 | 更加准确的配送设备给商户 |
一般而言,涉众的利益是否被满足直接决定了软件产品的成功与否。而分析涉众利益需要进行详细的调研,研发同学可以根据产品的调研看到对应的涉众,及其利益。
业务用例图
知道了涉众的利益之后,就要分析业务流程,并对现有的流程进行改进。软件产品没诞生之前,业务是如何被处理的,找到原来业务的处理方式则可以梳理出业务用例。
笔者负责的一个业务是向购买设备的商户配送设备,对于业务团队的实际业务来说,用户购买设备有业务价值,业务用例如下:
物流公司和设备渠道商都是辅助购买设备的执行者,因此放到右边。值得注意的是,业务用例要体现价值,虽然在实际业务流程中,商户同时做了很多事情,比如签收设备,但签收设备不能反映业务价值,故而只有一个购买设备的用例。
业务流程分析
了解了涉众的利益并且画出用例之后,需要分析业务流程,找到我们软件系统能够改进的流程片段;完整的业务流程图可能很庞大,需要关注的是其中最有可能影响涉众利益的流程片段,如下为购买设备业务流程中配送设备的流程片段,该片段不大符合涉众利益;
配送设备的业务流程
可以看到,在原来的业务流程中,配送设备的流程是在全部业务流程中较为繁重,人肉工作量大的流程片段,不符合涉众利益;
- 收集商城订单信息不及时,导致配送不及时,影响涉众的利益
- 人作为节点参与处理,成本高,耗时长,也是不符合涉众利益的
所以很明显,我们的系统需要改进流程,替代人的部分工作
新的流程有效地满足了涉众“将设备更多更快且准确无误成本低地卖给商户”的利益诉求。
业务序列图中,每一个箭头代表的是职责,在业务序列图中,需要考虑的是职责的层次问题,过于小的职责放入流程图中,会导致信息过载,忽略最有价值的职责。在上图中,运营核对设备的配送信息是一个很重要的职责,在原来的需求中体现比较弱,研发同学可以借助业务流程的分析来分析需求中不合理的地方,完善需求,避免后期的改动,前期越是完善,后期的损失成本越低。
业务流程分析是一件很复杂的事情,研发同学可以利用需求中的信息,同时加上自己跟涉众的日常沟通和调研,把握核心的涉众利益,业务用例和业务流程,就可以解决大部分在需求上的理解偏差问题。
系统建模
系统建模关注的是系统与外部的边界和系统自身的职责
系统建模需要做的事情
- 画出系统用例
- 写出用例规约
系统用例图
系统用例图是业务流程中,系统执行者与系统发生的有价值的交互。系统执行者可以是人,可以是外部系统,甚至可以是时间。系统用例要体现系统的价值,系统会做很多事情来实现业务价值,我们应当关注业务价值。有些是低层次的职责,没有体系具体价值,如:“获取商城订单信息”是为了配送订单中的设备而发生,应当关注“配送设备”。
如下是仓配系统的系统用例图:
系统用例规约
有了业务流程图和系统用例图,需要根据进一步细化系统边界上的约束,保证系统的稳定性。系统执行者与系统的交互细化了详细的约束,系统的稳定性才能提高,如果没有仔细列出约束,有可能会忽略一些边界条件,导致系统的故障;如:仓配系统不考虑来自第三方商城订单要配送的设备数量限制,则会因为第三方商城出现的错误,导致资产损失。
系统约束来源于系统用例,根据业务的规则,详细地描述了业务流程中的基本路径,扩展路径和约束。
配送设备:
- 系统每小时向第三方商城查询待配送的订单
- 系统验证订单是否已经配送
- 系统验证订单要配送的 SKU 是否合法
- 验证订单中配送的设备数量是否超过最大限制 1000 笔
- 系统向物流系统请求给订单中的用户配送订单中的设备
因为步骤 2,3,4 还有其余可能的路径,称之为扩展路径。
- 订单已经配送
- 系统忽略该订单
这里的约束不是告诉研发同学如何实现功能,这里的约束是业务规则,厘清系统执行者与系统之间的边界,以及边界上的约束。设计评审的时候,可以关注关键路径上的安全规则是否到位,这么做对提高系统的安全稳定有极大的帮助。
类的分析与设计
这可能是大多数研发同学比较熟悉的领域,经典的设计模式,类之间的关系,泛化,组合等。但是类从哪里来呢?是从需求之中凭空产生?又或者突然灵光一闪,有了类的雏形?对类的进行设计与分析之前,需要做的是找到他
识别类
经历过业务流程,系统建模,我们终于来到了系统里面,来寻找类。我们所熟知的类有三种,边界类,控制类和实体类。边界类是外部系统在系统内部的映射,借由边界类,系统和外部系统交互。所以一些接口请求,输入输出都属于边界类的职责。在仓配系统中,商城就是一个边界类,将外部系统转移到内部系统来,屏蔽了接口请求相关的细节。控制类往往是体现用例流程,一般而言,一个用例就是一个控制类。实体类则是系统的核心,实体类良好设计能够提高系统的复用程度,减低系统的复杂性。
找实体名词
知道了有这三个类还是不足够我们识别具体的类,识别具体的类需要去业务流程,系统流程,系统规约中经常出现的名词。在上面的流程图中,订单,商城,设备,物流,用户是反复出现的名称,说明这些类必然存在。
找到这些业务实体,就是找到类的第一步。
找到属性
类的属性也不是凭空产生的,需要对业务实现有价值,用户不一定有姓名,在物流上下文中,用户的属性就只有 ID,收货地址。找到那些对于系统实现必不可少的属性,放到正确的类中。如仓配系统中的订单,包含订单号,商品,用户。用户则有收件地址。
在仓配系统中,用户只有一个地址,在商城的系统中,用户则有多个地址,充分说明了,不同的上下文中,类的属性不是固定的。
找职责
从业务规则和约束中,可以找到一些实体应当有的职责,如订单,就有验证合法性的职责。
有时候,有些对象看起来信息很富裕,但是却没有什么职责,说明他是一个值对象,像上图中的用户和收件地址,我们不关心他的 id,只关心收件地址,收件地址就代表着这个用户。
状态机
找到类和对应的职责,对于一些主要的实体类,还需要设计出他的状态机,清晰的状态机能有效地厘清系统内的一些事件和状态,增强系统整体的健壮性。
仓配中的订单,从用户购买的待发货状态,到通知物流发货,再到实际发货,物流签收有一些列状态的演变。
5 总结
在多人协作的项目中,不断提高项目质量,除了依靠代码之外的工程手段,还要依靠设计建模。
从具体实践的角度来看,设计建模在不同的环境中,调整具体的流程和侧重具体的节点,也能够实现快速高效,不会导致繁琐和低效能的现象。
在写代码之前应该做的几件事情:
找准涉众利益(越是新的项目,越是要分析好,如果是小功能的迭代,则可以从产品需求文档中寻找);
画出原来的业务流程图,和改进之后的业务流程图(业务流程图是对业务理解是有极大帮助,在绝大部分场景中都不应该省略);
分析系统的职责边界,画出系统用例(往系统中添加较小的功能可以考虑不画出来,心中有数即可);
写出具体的用例路径和约束(对系统安全和稳定越是关注,越应该去写出具体路径和约束);
识别主要的类,属性和职责,画出重要实体的状态机(简单功能则直接用代码来表达即可)。