十年业务开发总结,如何做好高效高质量的价值交付
作者:杨博林 阿里大淘宝场景金融团队
软件交付是一个非常复杂的过程和体系,需要保障好每个阶段的质量和效率才能保障最终的质量和效率。本文将尝试从需求交付的前、中、后三个环节来阐述一下如何做高效高质量的价值交付。
一、背景
转眼间已经做了十年的业务开发,这十年大部分时间都是在承接各种各样的业务需求和项目,在需求评审环节,我们有些东西误以为双方达成共识了,上线后发现和 PD 想的不一样;代码发布后,总会出现一些问题,有些问题看着很简单,但是上线前我们就是没考虑到,甚至上线前我们不知道原来系统中还存在这样一个逻辑;代码发运行一段时间后,还会出现一些意料之外的问题,有可能我们写的代码只考虑了当时的逻辑,未来别人改了一行,别人的需求满足了,原来的需求满足不了,原来需求没满足我们可能还没法及时发现,因为是不同的开发做的需求,等发现的时候已经产生了资损或者对客影响。
另外,组织和业务是多变且复杂的,组织调整也是个常态,今天 A 团队 a 同学负责这个模块或者系统,由于组织变化,明天 B 团队 b 同学会接手,我们通常会组织交接(据我观察好像还有不交接的直接给过去的呢),如果有些逻辑没有交接好的话,新团队承接需求的过程中才能发现,甚至发现的时候已经造成了资损或者对客的影响。最近也是在忙于分析和解决各种线上问题,也引起了自己的思考,作为一名开发同学,有没有一些方法论来保障我们做高效高质量的交付呢?我自己也在不断分析思考这些问题,结合自己的经历,我尝试从需求交付的前、中、后三个环节来阐述一下到底如何做高效高质量的价值交付。
为了方便大家理解,我定义一下需求交付前、需求交付中、需求交付后的概念。需求交付前,这个阶段我定义为 PRD 评审完成前的阶段;需求交付中,这个阶段我定义为 PRD 评审通过后技术开始做方案设计阶段;需求交付后,这个阶段我定义为代码发布上线后的阶段。
二、需求交付前
需求交付前这个阶段,我分为业务方案讨论环节和 PRD 评审环节来阐述。
2.1 方案讨论
业务侧想做一件事情,一般先会做一些分析,基本上都是从问题出发,当下用户或者产品面临什么问题,我们要用什么方法去解这个问题,这个方法预计会带来什么效果,这些问题一般在业务方案讨论阶段业务同学会搞明白,如果方案较大周期较长,会找有经验的开发来粗略估算一下工作量,因为业务侧要找老板立项,老板可能要看 ROI,这个时候还没有完整的 PRD,可能只有几句话,我们如何去估算大致工作量,这个时候就需要我们有经验的架构师来做评审,这个时候架构师很关键,架构师需要精确理解业务的方案,同时对全域架构非常熟悉,也要非常熟悉整个项目的运作机制,这样才能给出一个相对准确同时又可解释的工作量,这个工作量有可能也会成为影响这个项目能否立项通过的关键因素,在经营责任制下面,每个老板都会考虑投入产出比。
这里技术侧比较关键的就是工作量预估,怎么预估准确客观就很重要,个人建议一定要拆模块去评估,如果模块拆不清楚,要么架构师不理解当下系统设计,要么当下业务方案无可复用的系统或者模块,要么当下业务方案技术侧不可行,千万不要随便评估一个工作量扔给业务,如果实在评估不清楚,可以参考类似项目,特别是跨部门跨团队跨公司合作的项目,一定要预留好联调的时间,同时也要提前把一些可预知风险反馈出来。
在业务方案讨论阶段,个人觉得团队的架构师非常关键,所以建议技术团队都需要有熟悉全链路的架构师,因为这个阶段你没法把上下游拉起来一起评估。如果自己想成为团队架构师,就需要不断去参与项目不断自己写代码,同时也要不断去看别人的代码,上下游的系统,对自己不熟悉的代码和领域一定要熟悉它的好奇心,这样我们才能帮业务做好方案讨论阶段的评估以及风险发现。另外,我也不觉得只有 TL 任命我是架构师,我才干架构师的事情,每个同学都可以尝试去扩充自己的知识域,学习自己的盲区,主动成为团队的架构师。
2.2 需求评审
需求评审,估计是业务开发最常去的一个会议了。主要是 PD 开始围绕 PRD 把对客表达以及业务逻辑和前端、后端、UED、测试、运营、风控、CCO、财务、法务等同学过一遍。需求评审经常一次评不完、评不过,评着评着讨论起来了,评着评着吵起来了,评着评着有人没跟上,评着评着发现到收尾的时候才到我这,评着评着发现逻辑走不通,一些逻辑我们觉得和 PD 达成共识了后面发现并没有达成共识,一些逻辑完全没评审到写代码的时候才发现等等,这些问题是我们经常会遇到的问题,那么我们到底该怎么做高效高质量的需求评审呢。那么我们如何做高效高质量的需求评审呢?
首先,我觉得需求评审会不能开成需求讨论会,一些关键点,需要在评审前先对好,不能在再会上讨论用什么方案,如果展开讨论的话,那么需求评审会大概率一时半会完结不了,所以评审前把所有逻辑都确定好,一些大规模的项目也先请团队架构师在评审前做一些预判,是否存在一些明显无法做到的逻辑,提前和 PD 讨论。
其次,我们要尊重 PD 这个角色,如果我们因为一些逻辑这样做也行,那样做也行,技术也说服不了 PD,PD 也说服不技术,我们选择采用 PD 的方案,我们相信 PD 同学是经过成熟思考后给出的方案,上线后可以一起关注用户的反应,不用在 A 方案也行,B 方案也行时争执不下。
虽然前期我们做足了准备,依然还会存在一些意料之外的逻辑或者没考虑到的点,当出现这些问题的时候有明显解法的立即决策并且写入会议纪要,没有明显解法的写入会议纪要里面的待办,经常发现一屋子人评完以后没有会议纪要,需求评审的会议纪要非常关键,本次需求评审达成了那些重要结论,有哪些悬而未决的问题,这些点都必须留痕到会议纪要里面去,千万不要开没有会议纪要的需求评审,做会议纪要的同学对需求的理解深度和不做会议纪要的同学对需求的理解深度不一样。如果一个需求评审了一次,还有遗留问题没解决的话,第二次评审就可以直接针对遗留问题做评审,这样不断迭代,需求总会评审通过。
在需求逻辑评审的时候,一些正常的逻辑可能大家都能想得到,异常的边界往往会被忽略;针对 PRD 里面的需求逻辑,我们需要做正向的评审,我们也需要做异常的评审,比如需求里面说要创建一个交易订单,我们不能只考虑订单创建成功的逻辑,还的考虑订单创建失败的逻辑,以及订单创建既不是失败也不是成功的逻辑,成功和失败,一般都会考虑到,既不是成功也不是失败往往会被忽略,但是客户不会忽略。所以我自己在需求评审时有时候会问一些很细节的问题,可能有些 PD 会比较反感,但是我个人觉得需求评审就得把正常和异常情况都评审到,我们不能假设用户的操作是畅通无阻的,系统和系统之间的交互永远是成功的。所以在需求评审时,除了考虑正常逻辑,一定要考虑异常边界逻辑,这个点很关键。当然也可能存在 PD 不理解你说的异常,举个栗子,某 PD 说了一个逻辑,开发说这里接口调用可能会失败,要考虑异常分支,PD 会反问开发什么是接口,这不是个玩笑,这是实际发生的现场对话。确实我们的 PD 不见得都是完全理解技术那套话术,所以有时候不能过渡依赖 PRD 的描述,这个也是我们为什么要做需求评审的很重要的一个原因。
在需求评审时,也经常会出现两个同学讲的不是一个问题,A 同学在说一件事情,B 同学在说另外一件事情,而且都很激动,但是又相互说不到对应的点子上,你一句我一句,反复几个来回,总是绕不到一起去。关于这点我想说在需求评审的时候,先尝试站在对方的角度去思考一下对方是处于什么样的背景什么样的原因说了这句话,然后自己再补充,这样往往你可能只要说两三个字就能把问题说清楚,简单的说就是先理解清楚对方要表达的意思再接话。这么多年的需求评审我越来越发现一个规律,人与人之间的沟通,不在于你讲了多少,而在于对方听了多少,讲的人需要组织好语言,听的人也需要站在讲的人的视角去理解他讲的话,这样沟通就顺畅很多。
在需求评审时,开发也经常会说 PD 的方案不行,你这方案怎么设计成这样,有时候 PD 会觉得很委屈,明明这是当下最优解了,为啥还说我方案不行呢,我觉得否定别人方案之前,先思考一下自己有没有比当下方案更好的方案,如果我们没有更好的方案就不要轻易去否定别人。每个人的认知范围知识域是不一样的,换位思考一下,你否定别人的方案,又给不出更好的方案,就有点不讲道理,不能凭感觉去评审,千万不要既提出了不明确的问题,又给不出更好的方案,同时还说别人方案不行。
最后,我觉得一份好的 PRD 真的很重要,那什么样的 PRD 才算一份好的 PRD 呢?对技术同学而言,首先 PRD 是一个结构化的文档,不管怎么写,都必须包含这三部分,为什么要做这件事情、做这件事情要达到什么样的效果、怎么做这件事情,这三部分我理解是缺一不可的。
为什么要做这件事情,这个点至关重要,首先要介绍清楚,不然越往后评问题会越来越多,需要通过为什么要做这件事情把大家的意识拉齐,觉得这件事情是非做不可的,比如客户非常想要某个工单、不做这件事情业务就无法发展、不做这件事情资金供给就跟不上、不做这件事业务会收到监管的检查......,这个必须交代清楚,方便大家理解后续的方案。
做这件事情要到达什么样的效果,把大家聚在一起做一件没有目标的事情太痛苦了,很难判断这件事情的价值以及 ROI,所以目标必须要有。怎么做这件事情,就是我们的 PRD 逻辑部分了,这部分到底该怎么写?有些需求就两句话,有些需求就一张图,有些需求边评审边补充.....,这些都不建议,PRD 必须要严谨,后续项目启动后所有的依据来源都是 PRD。我个人觉得写 PRD 逻辑这部分没有秘诀,就是按照功能模块把每个逻辑分支都描述清楚,不清楚现有逻辑的情况下,建议先把现有逻辑梳理清楚再写。
三、需求交付中
3.1 交互评审
一般 PRD 评审结束后,如果涉及对客交互或者表达的,就需要 UED 介入做设计,UED 根据 PRD 评审的结论给出专业的交互设计,建议 UED 把日常交互的规范定下来并且和 PD 达成一致,按钮形状、弹框样式、颜色、字体等等,针对规范达成共识,避免一些重复的讨论,下次需求来了,不用设计复用现有组件即可。
另外,如果有些方案 UED 说服不了别人,别人也说服不了 UED,听 UED 的,上线后观察用户数据,避免无休无止的讨论下去。在交互评审阶段,不建议 UED 输出多套方案,然后让 PD 选择,建议 UED 综合自己的专业判断给出一套方案,不要决策转移,避免决策环节展开讨论。交互评审也是我们对需求逻辑理解的一个验证过程,有对客表达的一定要做交互评审,同时必须有产品经理在场,避免评完后,产品经理说不符合自己的预期,需要推倒重来,交互和前端是紧密配合的,所以交互评审前端也是必须要在的。
3.2 技术评审
技术评审的内容也是一个文档,在此我强烈建议大家都按照统一的规范和结构去写设计文档,如果没法大团队保持一致,小团队尽量保持统一的设计文档规范(对外接口消息要有描述、系统交互时序图、领域模型以及状态机、监控报警、资损防控、熔断机制、容量评估等等都需要包含)。
技术评审这个环节不能省,设计文档也不能省,如果不做设计直接去改代码,你可能会发现越改越多,影响自己做需求的信心,因为这些未知的改动点会对自己带来恐惧,通过设计文档的编写以及技术评审可以消除这些恐惧。所以,先做设计评审,在做设计的时候如果发现有些东西自己不熟悉,就先看历史代码,把这些东西都搞清楚然后接着做设计,完善好设计文档后就准备找同学一起做评审。评审时也把 PD 叫过来,这也是印证技术对需求理解是否准确的一个过程,同时项目组同学都应该参加技术评审,信息拉齐,避免大家对方案的理解错配。
技术评审也需要一些有经验的同学参加,他们可以发现你不知道的东西,帮助避免这些他们踩过的坑,所以强烈建议技术评审邀请组内的老同事、架构师参加,哪怕他不理解业务逻辑,通过你的讲述他也能够提出一些问题帮助完善自己的设计方案。
现在很多同学都觉得写设计文档很麻烦,我想说的是解决线上问题比写设计文档更麻烦,压力更大,所以大家就都好好写好设计文档,做好设计评审。关于怎么做架构设计,这是个很大的命题,也是有非常多的方法论,我自己也回顾了这么多年做设计的历程,总结了一些设计的经验。
3.2.1 不懂现有业务逻辑别着急去设计
对当前系统和业务逻辑还是一知半解时,不建议直接去做设计,先花点时间去研究一下历史的逻辑,历史的架构设计,避免自己设计出来的架构和历史的架构逻辑发生剧烈的冲突,解决这些冲突的成本会比我们研究历史逻辑和架构的成本高很多,有些冲突后续几乎是无解的,有些方案后续几乎是无法回头的。
所以不懂现有业务逻辑和系统时,先静下来心来好好研究。时间越是不允许时就越是要慎重,很多方案往往都是时间不允许产生了临时方案导致各种冲突无解。
3.2.2 分布式锁能不用就别用
很多设计文档里都提到说我要用 TAIR 写个分布式锁,解决并发更新的问题。在引入分布式锁前,先思考一下自己设计的单据、状态机、数据库乐观锁更新能否解这个问题,如果能解,别用分布式锁。为啥呢?首先和缓存交互是一个网络节点,分布式系统中多一个网络节点就多一个风险,MySQL 无论如何都要引入,何必不直接用呢?据我观察大部分分布式锁都能通过单据状态机或者单据本身来解决;其次,当出现分布式锁获取失败、没释放,排查问题成本很高,分布式锁的获取和释放加重了代码的逻辑复杂度。
3.2.3 一事一地
相同的业务概念放在同一个域里面,不要一个业务逻辑多个系统控制。分布式系统下,一个业务逻辑由多个系统控制,数据一致性如何保障?所以不要把一些概念错放、重复放,这点非常关键,如果一个概念没系统承接或者没有可用的域,那我们需要看看是否新建域,还是暂时存在某一个域里面放着,千万不要一事多地。
3.2.4 核心业务动作要有对应模型和单据
如果当下业务动作是个核心动作,就要有配套的模型和单据去承接。比如说贷款,合约单据就必须要存在,围绕着合约有还款,那还款单据也必须要有。所以抽象核心业务动作要有单据,通过其他取巧手段也能满足业务诉求,但遇到一些线上问题、非功能需求、数据统计时就会发现这个核心动作的用户数、金额无法统计。
3.2.5 核心模型的数据变迁要可追溯
核心的模型,在系统中必须得能够追溯出当前数据是怎么变化成这样的,避免我们只能看到核心模型的当前数据,无法推导出当前数据是怎么来,或者只能靠一些猜测,而没法断言当前数据的变迁过程。如果无法追溯,面临一些复杂的线上问题就无法快速定位清楚。
要怎么可追溯呢?核心模型的数据变更前,需要把变更事件存储下来,就是什么事件要驱动核心模型发生什么样的数据变更,把每一个驱动核心模型数据变更的时间存储下来后,我们就可以根据这一系列事件推导出当前模型数据的变迁过程。这些事件用什么样的模型,我觉得不是很重要,想用统一的事件模型就用统一的事件模型,你想用一些更加贴合业务语义的单据就用业务单据,只要可追溯就可以。
3.2.6 单据职责要明确不要放大
系统中的单据职责要明确,千万不要指望系统中存在一个万能的单据,所有的业务动作都通过这个万能的单据来体现。如果一个单据职责过大,这个单据更新的代码要做各种各样的协调处理。设计这个模型时有些数据字段没有考虑进来,后续开发就不断通过一些扩展属性打标的方式来描述业务行为,导致扩展属性泛滥,扩展属性解释不清,扩展属性被错误更改,甚至还要做个系统来管理扩展属性的申请以及语义。一些重要的业务动作,非常不建议通过扩展属性打标来实现,尽量通过一些职责清楚的业务单据来实现。
3.2.7 系统 owner 请熟悉你的系统
我们每个系统都有自己的 owner,有些系统的 owner 就是一开始的原作者,有些 owner 时常变化。不管当前系统 owner 是第几作者,当前系统里的每一个单据、每一行代码至少都看过一遍,能够说清楚的代码逻辑,架构设计意图,上下游依赖和交互,对于自己所负责的系统能说清楚是系统 owner 的职责。
3.2.8 架构师反馈机制
团队内的架构师如果要参加技术设计评审,建议要有反馈机制,通过别人对设计评审的阐述,你自己的判断是什么?你自己坚持的点是什么?要有自己的观点,要能够去理解团队同学做设计背后的原因,作为架构师,必须要检出给一些反馈。无法给出反馈时要么是自己听不懂,要么不了解相关的模块,要么没有一些明确的原则,需要自己进行反思。
3.2.9 领域模型状态机
面向复杂的业务逻辑,我们设计了哪些模型,每个模型都有哪些状态,这些状态变迁是怎么变迁的,还有可能是要对现有模型和状态机做调整。业务系统的精髓我觉得就是在模型和状态机上,模型和状态机设计的好,就能够不断支持未来各种需求的演变,就能通过模型和状态机反推出业务形态,所以我们再设计文档里面需要重点阐述我们的模型和状态机。
这个点很关键,也是架构师必须要关注的,为什么要建立这模型,这个模型里面为什么有这么多字段,这些字段的含义是否都明确没有二义性,这个状态机里面为什么有这么多状态,每个状态到状态之间的变迁是否合理,每个状态的设计是否合理等等。所以每个写设计文档的同学,需要重点关注自己的领域模型和状态机。
3.2.10 数值、状态等多变的字段建议不要跨单据冗余
A 单据上有个金额字段,B 单据就不要冗余这个金额字段,冗余好 A 单据的编号就行,一旦两个单据上都冗余了金额,所有金额更新的逻辑里面都需要更新 A 单据和 B 单据,带来不必要的麻烦,甚至有些地方会查询 A 单据上的金额,然后去更新 B 单据上的金额,如何保证你查询到的金额一定是最新的呢?除非这两个单据上的金额在同一个事务里面去更新,否则数据一致性问题随时可能发生,甚至会让业务逻辑崩溃。状态字段也一样,一些多变字段不建议冗余。
3.2.11 对客表达和系统实现解耦
我们的系统实现是确定的,面对千差万别的客户诉求,一定要做好转换和抽象。抽象就是所谓的领域模型,这里重点提一下转换,用户侧的表达基于系统里的模型数据,我们没有必要把用户看到的一些点 100%落到系统实现里去(这里不是指用户看到的数据和我们的系统数据不一致),比如用户看到页面上有个业务动作是处理中,我们模型里面有多个状态对客而言都是处理中,这个时候就需要把这些状态统一对客描述成处理中即可,不用说我们的状态机里面必须要有一个处理中的状态,因为这个处理中的状态可能涵盖了太多的情况,会导致我们系统实现时非常复杂。
3.2.12 架构让代码组织更有序
在写设计文档的时候,我们也要考虑相关业务逻辑的代码在编码阶段写到哪里,也需要提炼一些原则,就像画系统架构图时把系统划分成几个框,那些代码写到框里,这也是我们在设计评审阶段要考虑到的,避免代码放得乱七八糟,公共的代码放哪里,差异化的代码放哪里,相信每一个业务开发都喜欢有秩序的代码结构,而不是随意的代码结构。
3.2.13 离线在线切分开
在线的业务逻辑和批处理任务需要切分开,避免跑批任务导致机器负载高影响在线接口的响应。如果批任务本身就是异步的,失败了还可以重复跑,但一些在线响应中断或者无法在指定时间内反馈就会有客诉。业务逻辑不建议强依赖离线数据,分布式系统中引入网络节点越少系统稳定性越好,过多的网络节点引入会让系统复杂度、数据一致性保障的难度加剧。
3.2.14 前端重点关注交互和渲染即可
很多产品需要有对客交互和操作,不建议把太多的业务逻辑放在前端。对客的页面渲染和用户操作的交互逻辑放在前端,流程的控制全部放在后端。如果前端有很多业务逻辑,则运行情况难以监控,一旦出问题需要去看客户端的运行情况,较难发现。最重要的是无法通过后端链路看清楚整体的业务逻辑,出现到底哪些业务逻辑放前端哪些业务逻辑放后端的讨论(一些常规的表单校验这种放前端没有问题)。
前端后端交互时,不要把整个后端的领域模型给前端,按需返回前端做交互和渲染的数据,返回一个字段容易,下掉或者更改一个字段就很难。
3.2.15 新技术新框架的引入要慎重
作为技术同学,我们渴望把自己学到的新技术新框架在项目中去落地使用,这本身没有什么问题。但是在引入新技术新框架时,请仔细评估一下新技术新框架的引入要解决什么问题?当下业务形态面临的问题能否被解决?如果只是为了引入新技术新框架炫技,大可不必,因为这些新技术新框架没有被广泛使用,可能会出现一些问题短时间内无法解决,有些问题运行一段时间才会暴露,暴露的时候发现引入的新技术和新框架已经不再维护了,只能自己去研究底层源码解决问题。
重要的业务系统千万不要为了追随潮流第一时间引入新技术和框架,用最成熟的技术和框架去解业务需求。当然,我们就是为了要尝试新技术新框架,可以建议在一些非核心链路去尝试,等这些新技术新框架广泛推广开后再逐步引入到核心系统核心链路中。
3.2.16 离线数据的依赖需要评估
我们经常会调整一些单据的状态,比如原来有三个状态,由于后续一些需求无法满足需要新增一个状态,这个时候一般对代码里的逻辑做兼容性处理,评估的时候也需要考虑离线的数据是否有其他下游依赖,避免离线数据的下游依赖没有及时感知到状态的变化而产生错误的离线数据,对业务分析、离线业务逻辑产生意料之外的影响。
3.2.17 跨团队跨部门的交互要非常细致
如果项目有跨团队跨部门的协作,那么设计文档要非常细致,定义好每个接口的出入参以及里面的每个字段,要定义好消息的 TOPIC 和 TYPE 以及消息体的每个字段和含义,以及系统之间在哪个节点交互,同步还是异步等等。这些一定要在设计阶段明确清楚并且形成文档,这样各团队和各部门就可以各自按照约定并行开发并行内部联调,如果这阶段约定不清楚或者不详细,后续协调收尾的成本会非常高。
3.3 项目计划
做完设计评审后,需要把明确的项目计划同步到需求发起人,当前在设计评审前可能已经预估了大概的计划,但是设计评审后,我们的研发计划会更加清楚,需要及时反馈给需求发起人,让他们对需求的交付时间有一个明确的认知,如果在交付过程中发现计划有变,或者有更高优先级的事时,要第一时间和需求发起人商量计划变更的事情,千万不要自己默默改变了计划,需求发起人不知道计划变了,也不知道计划为啥变了。
大的项目一般都有非常正式的项目推进过程,计划有变都会通过项目周报体现出来,一些小的需求也需要把计划的变更及时同步,往往这些小的需求计划是容易被忽略的,导致业务方可能对我们交付过程不满意。所以不管大项目还是小需求,一旦自己的承诺发生不可抗力的变化后,要及时同步出来,避免别人误解。
3.4 代码编写
能够安安静静写一天代码对开发同学来讲真得是一件很幸福的事情,没人打扰自己,相关业务逻辑也是一气呵成不中断,能够全身心的沉浸到代码和逻辑里面,但是我们的环境好像挺难做到的,写着写着旁边同学问你个问题,写着写着业务找你讨论个问题,写着写着有个突发的线上报警需要看,貌似上班时间一直都是写会代码,中断一会,中断后继续写,虽然感觉这不是很好,但是没有更好的办法,我这么多年貌似只有周六周末才能有这样安静的环境。做好设计评审后,我们如何做高效高质量的代码编写呢?
3.4.1 代码写出了 PRD 里面没考虑到的逻辑
代码写着写着发现有些逻辑在 PRD 里面没考虑到,这时候千万不要自己默默承担下所有,优先找 PD 看要不要做一些需求上的变更,和项目组或者需求涉及到的同学一起沟通,决策要不要调整逻辑重新开发还是保持现状,这个点很关键,做法也很简单。但大家经常自己默默抗下了所有,或者自己和 PD 默默扛下来了所有,没有其他人知道,这是非常不可取的做法,没考虑到的逻辑还是需要和项目组所有同学达成共识并且留好痕迹,一些对工期影响较大的变更,需要做好正式的需求变更,把信息让更多的干系人知道。
3.4.2 没把握不要重构别人的代码
不太建议在项目开发过程,没有做设计评审就直接重构别人的代码。有时候写着写着,发现别人写了一堆 if/else,或者日志乱打,或者代码结构不清晰,非常有想重构的冲动,这种想法每个开发都会有,而且对代码越有追求的开发越有重构的冲动。重构是好事情,但是不要做冲动式的重构,要有准备的重构。怎么才算有准备,设计评审时我们提出了要重构、对应的代码有单测做保障、已经提前和测试打好招呼说要重构。一时冲动的重构往往会带来各种问题,需要自己去收场,甚至会产生线上问题和资损,所以大家要做有准备的重构。
3.4.3 follow 现有系统中的代码风格
在开发时经常发现当下系统里面的代码风格和自己的代码风格不匹配,是坚持自己的代码风格还是学着去匹配当下系统的代码风格呢?个人不太建议一个系统中,代码风格有多种,如果无法把当前系统代码组织形式做重构,那就匹配当前代码的组织形式,不要创造第二种风格。避免系统里面的代码看上去无法理解为什么这么写,带来后续理解的成本。
3.4.4 断言式的校验不要忘
在日常开发过程中,一定要具备悲观意识,大家经常提的一个词就是「兜底逻辑」。我们除了通过代码去实现正常的业务逻辑外,还需要考虑一些兜底的逻辑校验,比如在执行某些逻辑之前,有一些金额恒等式、前置状态等等,先做断言式的校验,然后再开始写正常业务逻辑,不要觉得有些断言是显而易见的,代码里面不用多此一举,这些显而易见的逻辑就是系统里面的断言,这些断言无论如何都要保障,一旦断言被击穿,就需要立即中断后续业务逻辑的执行,因为系统中的一些规则被打破了,在继续执行只会在错误的基础上继续错误,这些断言往往就是我们救命的稻草。
3.4.5 先写模型状态机再写数据存储层
一般复杂的业务逻辑中我们会抽象很多模型,同时会有很多单据出来,有些开发同学喜欢先把 DAO 层写好,再组织业务逻辑,其实这是不可取的,因为这层是最简单的一层,如果你先把最简单的一层写好,再写业务逻辑层,这相对比较复杂,会发现你的数据存储层要么字段不全、要么方法不全,就会来回改 DAO 层,导致不断修改和反复,最合理的方式就是先把模型状态机写好,再把业务逻辑写好,这时候存储层就是一些接口,等这些逻辑稳定后再去写存储层的代码,存储层的代码以及 SQL 语句非常不建议用工具生成,有些工具生成了大的 update 语句,一行 update 下去你都不知道更新了啥,要写符合业务逻辑需求的 DAO 层,每个数据存储层要更新的字段都是明确的,甚至在更新前对前一个值要有约束的,不是任何情况下都可以更新的,相关金额的更新一般建议都走增量更新,不要全量覆盖。
3.4.6 唯一性约束
我们抽象好模型后,要做一些数据存储,每个存储的单据必须要有唯一性约束字段,这个非常重要,一些核心的单据在数据库里面重复出现会出现一些意想不到的问题,如果已经出现了,解决起来非常麻烦,所以单据要存储的时候必须要考虑好自己的唯一性约束是什么,不允许存在没有唯一性约束的单据。
3.4.7 本地事务
我们的代码有时候需要更新多个单据,不要心存侥幸,更新多个单据的时候请务必开启事务,不然线上出现数据不一致的问题时需要耗费很大的精力去查。不建议通过注解的方式开启,建议通过代码的方式开启,这样更加直观。另外事务里不要嵌套事务,嵌套的事务出现线上问题时,有时候很难想清楚到底有没有提交,特别是遇到一些紧张的线上问题时,同时又是嵌套事务,排查成本就更高了,能简单解决的问题不要引入复杂解法。
开启事务前,请对数据的隔离级别有一个清晰的认知,不要想当然的认为数据库的隔离级别是什么,自己通过 SQL 查询一下。在事务中不要包含远程调用(涉及到通过网络的交互),一旦事务中包含了远程调用就不纯粹是个事务了,一来远程调用 RT 过大会导致事务长时间无法提交,导致 DB 链接长时间占用,二来本地事务回滚后,远程调用可能已经发生,跨系统的数据不一致性就这么产生了。曾和别人联调项目时遇到一个逻辑分支,监听到别人 MQ 消息后去反查单据,在日常环境有时候能反查到,有时候反查不到,重试还总能查到,非常神奇,前前后后不断地定位,不断地 DEBUG,问题无法复现,查来查去发现上游的 MQ 消息是在事务中发的,单据是在事务中创建的,那问题就很明显了,有时候 MQ 消息投递后事务提交了,那就能查到,有时候 MQ 消息投递时,事务没提交,那就查不到了。
3.4.8 幂等重试
在日常代码编写过程中为了完成一段业务逻辑,除了本地操作,有时候还需要发起远程调用,这里幂等非常关键,幂等不仅仅要考虑本地业务逻辑执行的幂等,还需要考虑自己调用的远程接口是否也幂等,是否支持重试,我们代码在执行时完全可能遇到系统宕机、网络遇阻等等,导致一些逻辑执行了一部分,当系统恢复的时候这些已经执行了的部分逻辑还能否继续执行,这是每个开发都要在写代码时要考虑的问题。
任何一个接口调用,都有可能出现三种情况,一个是明确的成功,一种是明确的失败,一种无法判断成功还是失败,明确的成功与失败都好处理,无法判断的成功和失败都有可能需要我们做重试,因此一定要思考代码重复执行时能否正确地执行下去,在分布式系统中幂等和重试非常关键,写代码时需要时刻考虑,按照分布式系统的 CAP 理论,我们目前的分布式系统大家都很难做到 C,只能做最终一致性,而最终一致性就要靠幂等重试来保障。
3.4.9 批处理任务要隔离环境并且具备单条执行的能力
我们经常会写一些跑批任务,写跑批任务时要注意捞取预发环境产生的数据、线上环境产生的数据,如果数据不区分的话,可能线上会把预发的数据执行掉,导致你在预发测试的时候还要把线上的任务停下来;另外,有可能测试需要执行单条数据去观察相关单据的情况,所以批处理任务需要具备能够执行单条任务的能力,不然全量执行可能会污染测试构造的好的一些用例。
3.4.10 基于查询结果做满足条件的更新
经常会写先查询某个值,如果满足一定的条件再去做更新,需要思考一下去更新的时候,更新前查询到的那个值还是原来的样子吗?有没有可能在查询后、更新前被别的线程更新掉,导致查询到的值和实际持久化存储的值不一样。这个点很关键,写代码时一定要考虑,不然遇到类似问题排查验证成本非常高。如果查询后值有可能变化,而且还要把这个变化的值冗余到其他单据上,这个是非常危险的动作,会导致冗余的值和实际的值不一样,产生各种各样奇怪的问题。
3.4.11 单元测试
单元测试,这又是老话题了,写单测需要时间,那就把写单测的时间评估到项目计划里去,这是软件工程发展的经验,单元测试是针对自己所负责的模块以及系统内部的逻辑做测试,怎么保证自己的所负责的模块和系统没问题,就是靠丰富的单测,确保程序的输入和输出都符合设计时的约定,然后再和别人去集成,避免多人集成时,调用到自己负责的代码时,一调一个 BUG,一修修半天,严重阻塞项目的联调进度,给别人也会留下不好的影响,单测是自己保障自己代码质量的重要手段。
另外,自己写完代码同时有单测保障,过了一个月后,自己又需要修改一些代码的时候,修改完跑过了所有的单测用例,你会觉得就算出问题也不是什么大的问题,别人改你代码的时候,可以通过单测去熟悉你的逻辑,改完之后,你的代码也自带保障机制,因为有单测覆盖。通过单测,你可能会构造出非常丰富的用例,最后测试同学测试的用例可能都没有你构造的丰富。所以,写单测是一个开发工程师良好的修养,也是为自己未来再去修改代码奠定了良好的基础和保障,也是正常业务逻辑的一个免疫体系,无非就是会多花一些时间,但是这些时间是值得的,毕竟现在用程序证明程序的正确性也只有写测试用例这一条路,写程序很爽,写单测证明自己的写的程序没问题也很爽。
有时候写写单测,发现自己写的代码单测一把过,这种成就感会让自己写代码的信心也会越来越强,越来越觉得自己写的代码就是高质量的,有时候写完单测调用一下,发现有问题,自己也反过来需要思考一下为啥写代码时没有注意到,通过这种小闭环的思考机制,让自己的写的代码经常一把过,也会让自己的信心和成就感逐步增强,也会让自己非常渴望去用单测验证自己代码的正确性,这就把写单测变成了自己的习惯,甚至下意识。
3.4.12 debug 你的代码
记得刚毕业写代码的时候,写代码经常有时候考虑不全一些逻辑分支,当时就问了一位有经验的同学,说我们怎么保证自己写的代码没问题?他说把自己的写的代码全部 debug 一遍,当时也没多想,我就照做了,结果打开了 debug 的欢乐世界。debug 代码时,当断点停留在那行代码上时,你会发现自己可以 check 很多数据,甚至改变一些数据去看一些异常 case 的执行是否符合预期,后面自己写完代码不管用什么方式我都争取把自己的代码 debug 一遍,确实问题少了很多,有时候基本上没有任何问题,上线时也非常自信,同时也积累了好多 debug 的经验。
3.4.13 代码不会骗人
自己负责的系统,如果有哪些逻辑不确定或者没把握,不要做自信的猜测,要找到代码或者线上运行的数据去证明理解是对的,千万不要猜一些逻辑,知识通过人传人会失真,但是线上代码一直在哪里,不会骗人。
3.5 项目联调
一个复杂的项目,往往是需要跨团队跨部门去协作完成的,这时就经常需要进行大规模的联调,那怎么做好高效高质量的联调呢?
3.5.1 定好计划及时报风险
在联调前先约定好要联调的链路以及计划,什么时候需要联调完哪些用例哪些 case,如果没有按时间联调完,需要及时同步风险。个人认为联调阶段需要靠日报来保证联调的进度,今天要联调哪些 case,今天结束后需要做个总结,实际联调了哪些内容,卡点是什么,需要哪些帮助,预计产生的影响是什么,这些都需要在每天联调的日报里面体现出来,避免调了两周一个 case 还没调通,为啥没调通呢,原因也一时半会说不出来,所以联调阶段的计划以及日报很关键,特别是跨团队跨部门的联调,想推进联调进度,没有其他更好的办法。
3.5.2 先内部联调再外部联调
在跨团队跨部门联调时,尽量先把内部的逻辑都调完,避免跨团队或者跨部门等待,有可能我们一个 case 不通,上游团队只能一直等待我们定位问题、修复问题,这样联调的进度会被拉的很长很长。设计评审阶段对于跨团队跨部门的描述一定要讲清楚,这样在内部联调阶段就可以按照当时的约定做联调。
3.6 代码评审
自己写完代码后,早点找团队内有经验的同学做代码评审,就像设计评审一样,需要一些他人视角的补充,帮自己保障线上的质量,补充自己的盲区,那代码评审该怎么做呢?
3.6.1 尽早发起代码评审
别要发布上线了,突然找到相关同学说帮自己做个 CR,CR 也需要时间,CR 给出来的建议是改还是不改呢?改的话测试要重测,不改的话,CR 的同学说你这不符合规范,也有潜在风险,自己很为难,CR 的同学也很为难,所以尽早发起代码评审。
3.6.2 代码评审别秒过
如果别人找自己做代码评审,不要啥也不看直接过,一些明显的业务逻辑漏洞就出现在了生产环境,所以评审的同学一定要注意评审的质量,当然可能有同学担心代码评审浪费自己的时间去保障别人代码的质量,有点得不偿失,这么想我觉得肯定做不好 CR,CR 也是自我学习的一个方法,有些业务逻辑是其他同学写的,我通过 CR 也学到了这块业务逻辑;自己是这么写代码,通过 CR 别人的代码,发现了更优雅的写法;通过 CR 能够发现别人代码里面的问题,那你自己写代码肯定也不会出类似的问题,别人会一直相信你的 CR 质量,在团队内的影响力不一样。
3.7 用例评审
一般项目和一些大型需求,我们都会做所谓的 TC 评审,TC 评审主要是测试同学主导,那怎么做好用例编写和 TC 评审呢?
3.7.1 对客用例来自于自己对 PRD 的理解
我们写用例的时候经常会写用户做了什么操作,页面上要看到什么,希望这些用例是自己通过需求评审自己总结出来的,不是开发告诉测试的,如果开发告诉测试,那没有意义,因为开发是按照自己的理解写代码,测试按照开发的理解写用例,这是有逻辑盲区的,万一开发理解错了呢?所以写测试用例的同学一定要对 PRD 内容有深刻的理解和认知,这样才能写出高质量的对客用例。
3.7.2 系统交互用例来自于自己对设计评审的理解
需求评审结束后,技术一般会做设计评审,有些测试用例就是去验证开发设计的,基于开发的设计自己去设计用例观察领域模型和状态机的变迁,这类用例也不能是开发告知的,测试同学需要站在自己的视角去理解技术方案给出用例。一条用例执行下去,要检查那些数据,不能全依赖开发告诉测试要查哪些数据测试再查哪些数据,因为开发的视角里他可能只关注这些数据,但是有可能有些数据被错误的更新了,开发没关注到。
3.7.3 开发需要对用例做确认和补充
到 TC 阶段,开发同学需要对测试同学的每一条 TC 都做确认,确认是否可测、是否有效;同时也需要提醒测试同学哪些 case 测试的时候要多关注,有哪些 case 自己代码实现了,但是 TC 里面没有,TC 评审千万不要只是测试同学对照自己的 TC 讲了一遍,开发同学也需要重点关注,甚至可以回头看看自己的单测里面是否都有覆盖到测试同学提到的这些 case,如果都覆盖了,那么测试在手工测试代码时几乎就没有 BUG 了。
3.8 项目提测
项目提测也是一个非常重要的里程碑,需要测试同学做好验收,然后再对客发布。那么我们怎么在提测阶段做好高效质量呢?
3.8.1 提测后不要随便改代码
项目交接给测试户,开发同学就不要随意修改代码了,换位思考一下,测试同学在测试,你不断的改代码,他们就不知道自己基于那个版本测,如果非要改,请知会一下测试以及上下游你改了什么,不然你的修改可能会导致人家辛辛苦苦构造的一条测试用例的数据废掉了,需要重头再来。同时你的上下游也可能因为的代码修改导致要做一些逻辑上的调整。
3.8.2 所有逻辑在所有环境都需要覆盖到,不要把 case 留给用户
所有的 TC 都需要能够验证到,不要把 case 留给用户自己去验证,留给用户去验证就是把风险带到线上,我们的每一个变动要确保在对客前能够验证到,不仅仅要在日常环境验、预发 &生产环境都需要验证,特别是对客时,一定要把逻辑分支都验证到,当然有些用例涉及到用户的操作,可能没法验证,这时候可以和产品运营商量,联系客户开验证。测试同学在测试过程,如果有某个用例无法测试,需要及时提出来,大家一起想办法看看怎么测,而不是放过他。
3.8.3 自动化资产的沉淀
手工测试执行很多用例后,测试这边需要思考一下哪些用例可以沉淀成自动化的接口测试或者 UI 测试,然后在项目测试过程中,每天自动执行自动 check,避免一些自己已经测好的用例被开发改代码后又执行不过。项目发布后,下次有小需求,可以通过自动化的测试用例去代替手工验证。
3.8.4 测试接手的需求需要有质量报告
测试是软件工程中非常重要的一个角色,希望每个测试同学接手的需求测试同学都花点时间总结一下质量方面的问题,本次手工测试了哪些 case,沉淀了哪些自动化的用例,有哪些 BUG,以及测试过程中遇到的卡点和经验,每次质量报告的总结都是为了下次的测试更加顺畅。测试同学也可以对开发在质量报告里面提要求,比如说提测后经常改代码、交付代码 BUG 多等等。
3.8.5 不要带着问题发布
在测试环节,如果发现了一些预期之外的问题,需要把这些问题都解决掉再发布,就算是历史问题,那也要把历史问题推到终态,不然需求发布后可能把历史问题继续扩大,不能说反正历史也这样,我们又没有改动它,那就这样吧。也不能因为有些需求必须赶在某个时间点上线,所以我们就带着问题发布,这都是不可取的。
四、需求交付后
4.1 问题处理
4.1.1 冷静面对线上问题
虽然前期我们做好了很多工作,但有时候上线后还是会出现问题,出现问题时不要怕不要慌,积极面对,先不要着急去查问题,先看看怎么止血,第一时间同步 TL,这样 TL 可以组织更多有经验的同学一起来协助,甚至 TL 可以创造更加轻松的排查问题环境,不至于自己过度紧张到连正确的业务逻辑也开始反复质疑,避免问题随着时间拖的越久而被放的越大,线上问题没有必要藏着,同时问题解决完后,自己一定要做总结,看看这个问题出现的原因是什么?前面那么多环节,到底那个环节遗漏了导致这个问题出现在了生产环境。
4.1.2 不要在预发环境跑正式用户的数据
在处理问题过程中,千万不要觉得预发处理起来方便,所以一些有问题的用户数据在预发处理就算了,使用预发处理正式用户数据问题时,请做好仔细地评估,确保你的系统在预发没代码问题。你无法确保下游系统在预发无代码问题,所以不要轻易在预发处理正式用户的问题,如果非要处理,请做好全面评估。
4.2 异常监控
监控是非常重要,发布后需要及时关注监控的有效性,发布后不是价值交付的结束,看看相关的异常能否都被正确的监控,避免用户投诉过来我们才发现线上有问题。特别是项目刚发布上线的这段时间,一定要积极主动去关注一下线上的监控,一些核心业务指标的监控,一些核心系统 RT 的监控,一些慢 SQL 的监控,系统中错误日志量的监控等等,主动观察一段时间。自己可能在本次项目中实现了一些非常复杂的逻辑,虽然测试同学帮忙验证了,自己也需要时刻保持线上的系统运行的关注,看看真正用户的数据进来后程序还能否正常运行。
4.3 业务效果
我们一般承接的都是业务需求,上线后还是要观察一下改需求是否满足了业务的预期,如果没满足,原因是什么?是系统问题还是本身业务方案也存在一些瑕疵,通过关注业务效果,可以慢慢培养自己业务方面的思考,也驱动自己通过技术视角去看业务,去帮助业务分析业务目标没完成的背后原因以及要达到目标我们可能要做哪些事情,技术视角看业务和业务视角看业务会有不一样的思考和碰撞。
当然这里所谓的关注业务效果更多的还是从交付质量方面来说,关注业务效果也是关注我们的价值交付是否满足业务预期,是否真正解决了客户的问题。同时也可以印证一下在需求阶段我们和 PD 争执不下的问题客户是怎么去看的,在交互阶段 PD 和 UED 争执不下的交互线上效果到底怎么样,有些时候量化的数字远远比无休止的争吵更有说服力。
4.4 自我总结
不管项目还是需求,上线后我们自己都需要正式或者非正式做一个总结,方法就是前置法,在软件工程中,一个问题发现的越早解决成本越低,回顾一下整个交付过程中出现的问题以及这个问题出现的阶段,思考一下这些问题能否在前一个阶段发现,比如编码的问题,能否再设计阶段就发现,提测的问题能否在单测阶段就发现,上线的问题能否在日常环境就发现.....这样下次项目中或者需求中就可以逐步在不同阶段收敛类似的问题,让整个价值交付更加高效高质量的发生。
五、总结
软件交付是一个非常复杂的过程和体系,我们需要保障好每个阶段的质量和效率才能保障最终的质量和效率。提升软件交付的质量和效率是一个永恒的话题,不同经历的同学可能有不同的经验和方法论,甚至不同团队会有不同的做法,不管你的做法是什么,我觉得我们永远从一些实际问题出发去思考解法,不断通过一些问题来完善我们做事的方法论和体系总是对的,如果我们面对问题总想放过它,那么我们永远无法做到高效高质量地交付。以上观点仅代表个人观点,有些可能只是一些非常简单朴素的方法,这里做了一些梳理和总结,希望能够帮助到每个想做好高效高质量交付的同学。
版权声明: 本文为 InfoQ 作者【阿里技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/222d2b779c9b06838c4eafd01】。文章转载请联系作者。
评论