我们常说应该如何设计软件系统,确保我们的系统长期保持稳定。一般一个新项目初始阶段的代码架构都还是比较清晰的,为什么随着版本快速迭代,系统会变得越来越不稳定呢?今天我们用逆向思维从反面来看我们如何把系统写得稀烂,然后我们从流程上怎么保证团队的开发质量呢?
一定要迷之自信
什么?自信不是好事吗?是的,在很多领域绝对的自信都是一个褒义词。但是以个人多年经验看,它并不适用于编程领域。因为绝对的自信,往往会出现下面几个场景:
- 我的代码一定不会有bug,如果出现bug,一定是其他人的问题;
- 我的代码不需要任何注释,所见即所得,但往往一些常见单词也会拼错;
- 你的代码这儿好像有点问题。什么?不可能,肯定是你看错了,恨不得跟对方争一个输赢。
因为绝对自信这种心态,我们经常说迷之自信,这种人往往自我定位不是很清晰,总觉得自己是最厉害的。他们常常会对一些已知的问题视而不见,不愿意接受其他人的指导建议,完全活在自己的世界里。所以要让我们系统变得很烂,一定要拥有迷之自信,先把自己封闭起来,整个世界与我无关,这样你就成功了一半了。
不需要添加注释
正确做法:当然是上述场景我们都要添加详细的注释说明,毕竟人的记忆是有时长限制的,自己写的代码经常过几个月就不太记得了,尤其是一些技术细节处,当时为什么这么写,如果有对应的注释说明,那对于后续的维护是百利无一害的。
不需要考虑可读性
功能实现就行了,至于代码可读性who care。我们的功能实现代码最好都放在一个方法里面,不用去拆分,只要最终上线就行。最好写的更有难度些,我们可以把功能数据结构写的再分散些,最好写到不同的文件中去,这样接盘侠肯定就找不到我们的功能数据了。是的,反正没人关注我的代码,最后实在写不下去了大不了拍拍屁股走人,找下家霍霍去。
正确做法:按个人理解,一个方法超过100行都要进行自我思考:是不是我设计上存在什么漏洞导致代码变复杂了,有没有更优美的实现方式呢?正所谓条条道路通罗马,那有没有一条这样的路径呢:路途风景既优美(可读性强),还是一条比其他道路更轻松的捷径(高扩展)呢?经过充分设计的代码当然可以达到这个目的。
不需要考虑边界问题
我之前从来没有考虑过边界问题,功能不是实现的好好的吗?这完全是多余的。这话说得好像也没啥毛病,毕竟功能都成功上线了。但是若是不考虑边界问题,往往会使我们的系统快速恶化,变得极难维护。
那什么是边界呢?软件系统内很少有哪个模块是独立存在的,模块之间往往需要通信,那么通信的过程中就伴随着数据的传递。是的,我们在模块与模块之间的通信交互的地方就叫做边界。
拿国家来说,中国和印度相邻(把中国和印度看作是两个模块),那领土分界线就是两国的边界。中国商人在中国境内(中国模块内部)使用人民币(数据结构)进行交易,商人的贸易发展到印度去了,然后在边境处和印度人进行交易,对方不接受人民币了呀,所以我们需要把人民币先转换为卢比, 然后在印度境内(模块内部)就以卢比(数据结构)进行交易。注意:这里的交易就是模块提供的行为能力。
我们再看一个常见的UI更新的场景,客户端先从服务器拉取Json格式的数据,然后在UI上层进行展示。有多少人会这么设计流程:直接将后台的Json格式数据传递到UI层进行展示处理了。
这就是典型的没有考虑边界情况。这么做虽然功能可以实现,但是至少有下面几个问题:
1. UI层代码存在大量的Json数据操作,业务层可能到处充斥着Json解析和相关Key的操作代码,会导致代码很难读。
2. 后台Json数据的相关变更后续可能会直接影响上层UI的展示。这是我们不期望看到的。
那我们需要怎么做呢?其实也比较简单,我们在每个模块通信时进行一次数据格式的转换就可以了,就像我们去印度交易一样,需要先将人民币兑换成卢比才能交易一样。最后优化后的通信流程如下图所示:模块与模块之间通信需要有一层数据转换器。
不需要做代码设计
是的,我们的代码不需要任何设计。我们写的代码就跟产品的策划流程一模一样,你看我这叫100%还原产品流程。这也叫做迷之自信,我愿称该行为是最快速使我们系统腐败的原因,没有之一。
写的代码跟产品策划流程一模一样有什么问题呢?
1. 首先会造成大量的冗余,甚至功能遗漏。比如我们项目中的某个场景,对消息需要做分发处理,在分发前需要判断是否满足分发条件,但是这个条件有几十个之多,之前代码是在每个条件不满足时做对应的逻辑处理,比如上 ,将消息设为已读等等。那这个上 和将消息设为已读的业务代码足足重复写了几十份。
2. 其次,会导致功能不一致的问题。继续说上面的例子,在每个不满足分发的场景时,都要做同样的上 策略,因为人员变更,其他人并不知道需要做上 等动作,就导致行为不一致,从而引发一系列的数据问题。
3. 最后,系统会变得极难扩展。若是在不满足分发时需要提供其他能力呢?那需要在几十处场景全部新增相关功能,因为改动场景太多,这就给系统稳定性带来极大风险。
所以,我愿称该行为对系统的破坏威力是最大的。关键点就在于它还能正常上线,可能需要过很长一段时间才会发现其带来的严重后果。
以个人经验来看,我们常用的就有模版+策略模式能够解决80%的场景问题。模版用于将主体流程抽象化,其一般是个抽象类,策略用于延后做具体行为,一般具体实现模版中定义的抽象方法。所以,我给的建议是不要拿到需求就直接写代码,先想想下面因素:
1. 该需求涉及到哪些模块?
2. 模块之间是如何通信的?
3. 通信数据结构有没有设计好?没有就要加上。
4. 模块通信之间是否有耦合?有的话我们要设计独立接口出来,让其依赖我们的接口,而不是直接引用我们的实例。
5. 该需求的具体流程是什么样的?是否具有多个场景下的不同行为?有的话那我们就要设计不同场景对象来满足要求,而非设计一个工具类一个静态方法,里面一堆if-else 的代码。
6. 该需求是否涉及存储?我们架构中是否已经存在独立的存储模块?若不存在,则需要增加一个独立的存储模块。
从以上可以看出,我们在写代码之前要做的事情很多很多,我们按照上面的方法来写新需求的话,我们的新功能完全不会影响到老的代码业务,同时我们还将相关模块进行通信优化,模块解耦,这样我们的项目会越来越好。
当然,影响系统软件质量的因素方方面面,我在上面仅仅列出了个人觉得影响最大的几个方面:
- 人的问题。心态不摆正,很难写出高质量代码
- 维护问题。注释不清楚就导致后面维护的人想改也不知道怎么改。
- 边界问题。合理的边界约束可以很好控制系统各个模块的独立关系。
- 设计问题。这个是核心,没有一个合理设计的系统注定走不长远,随着快速迭代,系统会快速腐烂。
应对策略
每个团队成员的技术水平肯定都有高低之分,那么我们怎么保证团队的整体开发质量呢?这里就需要我们开发前和开发后严格把关:
概要设计
很多团队是没有这个流程的,比如我之前在腾讯的团队就没有这个流程,完全靠开发人员自己把控开发质量。但是即使技术再牛,在具体一些业务上始终存在一些自己理解不到位的地方,所以在这个过程中是发现方案漏洞的最好时机。
同时在概要设计阶段还是代码框架设计体现的最好时机。很多同学在概要设计阶段并没有体现出技术细节点,仅仅只是把产品的策划流程重新写了一遍而已,这就失去了技术方案讨论的初衷了。这里我们需要具体体现技术细节,如何体现呢?就是上面代码设计环节中提到的相关点:
上面的细节都属于技术设计环节的内容,需要我们在编码之前确定清楚,这样可以极大概率的避免后续的方案变更。我们可以通过下面2个行为来推动执行
1. 这里我们可以输出一份概要设计模版,让团队成员都按照模版步骤进行概要设计的编写。
2. 跟团队成员约定,开发过程中若是有方案变更,需要第一时间同步,确保leader知道方案变更缘由。
代码Review
那么我们在review的时候一般会看些什么呢?其实就是看设计阶段的内容是否有实现。重点关注模块解耦、数据结构和代码实现逻辑是否合理、可读性、可测试性等方面。
若是在概要设计阶段做的比较详细的话,在review 代码阶段就会轻松很多,在这个阶段我们只需要关注代码实现是否是按照我们原定方案实现即可。这里有2点需要注意:
1. 团队中必须要有至少一个对整个系统都比较熟悉的人认真的参与到代码review中来。若是大家都只是对各自模块比较熟悉的话,那就不能从整体上把握代码细节了。
2. 跟团队成员约定,开发过程中若是有方案变更,需要第一时间同步,确保leader知道方案变更缘由。
总结
今天我们聊到了软件系统不稳定的核心因素,包括开发人员盲目自信,不注重代码注释,不注重代码边界问题,不注重代码设计等等。
我们可以在开发前和开发后对代码质量进行严格的把关,开发前可以在概要设计阶段对功能设计和相关技术细节做严格控制,一定要符合要求才能进入到开发阶段;开发后我们通过集体review代码的方式来把控代码质量。
纸上得来终觉浅,绝知此事要躬行。希望大家在项目中多实践,同时提升自身编码技巧和项目软件质量。
欢迎大家留言讨论跟软件质量的方方面面。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!