我们可以列举出非常多质量差的代码的表现现象,如名字不知所意、超大类、超大方法、重复代码、代码难懂、代码修改困难……其中最为影响代码质量的两个表现是命名名不副实、逻辑可扩展性差,当一个新人阅读代码时,有时发现方法命名与实际逻辑对不上,这就让人感到非常疑惑,这种现象在平时工作并不少见;另一个就是逻辑扩展性差,一个新业务需求提出来后,发现要在多处改动,需要回归的业务逻辑比较多,造成研发效率不高。
对第1节中提到的现象进行问题归纳整理,大致整理出6类问题,分别展开加以说明。
接下来分析下为什么会产生代码差的原因,这个问题有外部原因,也有内部原因。外部原因主要有:项目排期急,没有多少时间去设计;资源短缺,人手不够,只能怎么快怎么来;紧急问题修复,临时方案快速处理……。内部原因主要有:自身技能低,怎么技能没有掌握到,如Lamda表达式、常用的工具类、框架高级用法等;无极致追求的精神,仅仅完成需求就行,稳定性、可扩展性、性能、数据一致性等没有考虑……
笔者认为最为关键的是内部自身的问题,根因就两个:自我要求不高;无反馈通道。如果对自已要求不高,仅仅满足完成需求开发就止步了,很难写出高质量的代码,另外如果没有外部反馈,也难以提高自己的技能。笔者之前的主管非常严厉,对大家写的代码review比较仔细,一个变量名、一段逻辑的写法,反复让修改,这其实是提升技能最快的方法。
提升代码质量的方法,笔者喜欢用三个方法:领域建模、设计原则、设计模式,主要谈下如何使用。
领域建模的入门门槛比较高,包含了一些难理解的概念。本篇文章中并不会讲述如何进行建模(可以私下交流),笔者发现让大家接受领域建模远比知道如何建模更重要,当你知道了领域建模的作用后,自己会想各种办法去学习。下面通过笔者经历的一些实际案例进行阐述,让大家听起来并不感觉到那么空洞。
笔者工作一年后加入到了一家金融公司,当时对金融一无所知,开始接触到标的、债权、债权转让、融资担保、非融资担保等名词后,一时感到无所适从,每天要学习非常多的新内容。
两个月后,我的主管给我们做了一次分享,就拿了一张ppt来讲,它里面包含了领域的实体,以及实体之间的关联关系,一下子我就知道了整个业务是怎么玩转的。模型的作用就是简化人对事物的认识,如果一开始我们就陷入到代码细节中,很难看到业务的全貌,而且代码是为了实现业务能力,当你知道了业务之后,再去看代码就会快得多。
在公司里,有研发、产品、运营、测试……,当我们在一起交流的时候,大家默认的语言是不统一的,开发经常讲怎么操作这张数据库表,产品经常讲业务模式……这就导致大家的认识并不统一。
那是一个晚上,刚和交互同学确认完交互流程后,突然她问了一个问题:把相似的页面让卖家移到同一个文夹中,这个好实现吧?听完后告知不能,交互同学一听说这很合理呀,怎么实现不了?开始给她讲了下现有的系统流程,发现她听得一脸懵逼,马上发现问题了,我是用开发的语言在描述问题,立马换了一种方式,找了一支笔和一张纸,给交互同学画了我们的领域模型是什么,业务实体之间的交互是怎样的,一讲完后,交互同学马上明白了为什么不能实现的原因所在了。
有的同学觉得领域建模偏空洞,比较虚,其实除了能够简化认识和统一认识外,领域建模还可能指导代码设计,比如上面举的店铺导航Tab的例子,笔者就是通过领域建模来设计的,虽然它是一个小的需求,并不妨碍领域建模的运用。在下图中,可以清晰的看到,导航栏包含了若干个Tab,一个Tab包含规格信息和点击操作信息。把这个业务模式画出来之后,对应的代码中也会有上面的概念,现实与代码之间存在映射关系,模型即代码,代码即模型。如果你的模型不能反映现实,模块只能算是一个花架子,范钢老师对此总结了三句话:现实有什么事物,对应有什么对象;现实事物有什么行为,对应对象有什么方法;现实事物有什么联系,对应对象有什么关联。
对于设计原则,一般我们会谈到SOLID,它包含了五个设计原则:
我们对SOLID原则基本上听说过或者了解过,但为什么要有这些设计原则呢?为了回答这个问题,我们从目标往下推导下。软件开发的目标是高内聚、低耦合,这句挂在嘴边的话,发现很难衡量,比如要回答:什么样的叫高内聚?什么样的叫低耦合?高内聚要高到什么程度?低耦合要低到什么程度?这四个问题并不太好回答。
反过来想想,如果我们的代码不是高内聚和低耦合的会怎样?也即是低内聚和高耦合的场景。如果代码是低内聚和高耦合,则会出现修改一个逻辑,会导致多处代码要修改,这个并不是我们希望看到的,尤其在修改原有的逻辑,很容易出现bug,比如笔者之前修改一个问题,改了另外一处的规则,看起来是没有问题,结果影响到了一个业务方,这也是为什么开闭原则提出对修改关闭的原因,修改原有的逻辑是有风险的。
理想的情况是修改只限定在某个局部范围内,这样影响的范围有限,因此我们要求逻辑要单一,不要包含多个职责。再往下思考下:为什么我们要修改呢?除了原有逻辑有bug要修复、代码重构外,一个重要的原因是需求发生了变化,是变化导致我们要对原有的逻辑进行修改。如果没有修改的场景,也就没有所谓的高内聚、低耦合之说了。因此设计原则的底层逻辑就是让软件能够较好地应对变化,降本增效。
设计原则只是一个指导的方针,离落地实践还有很大的一段距离,就像有些同学说设计原则我懂了,但我依然运用不到。实际上这个问题的本质还是对设计原则的底层逻辑没有理解,没有洞察出变化关注点,怎么解决这个问题呢?设计模式给出的答案:找到变化、封装变化。
当调用的接口有不同的实现时(入参、出参、接口都不相同),需要抽象出一层防腐层,怎么去实现呢?接下来分别看2个案例,这2个案例的侧重点不一样,一个是偏行为的抽象,一个是偏结构的抽象。
店铺需要查询店铺品牌信息,然而Lazada和AE的接口是不一样的,怎么抽象防腐层呢?
首先最简单的方案很容易想到,就是定义一个接口,然后有两个实现。它的优点是层次简单,大家基本看了就懂。它的缺点也是明显的,在两个实现类中,职责不一单一,承担了两个职责:一个是实现店铺品牌的查询,另一个是数据转换。
根据方案一提到的缺点,很容易想到使用适配器模式,将之前的类拆成两个类:一个类是调用对应的品牌服务;另一个类做数据适配转换。不过此时的方式还有一个缺点就是在国际化场景下,要考虑多租户之间的隔离,比如Lazada有多个站点,如何实现更细粒度的差异呢?方案三基于这些的思考就产生了。
方案三是引入了多租户框架,能够支撑多租户场景。
有一种"万金油"式开发模式:组装参数、调用接口、解析响应结果,你会发现这种模式太万能了,适合所有的场景,这样的开发模式也即是"事务脚本模式"或者"面条型代码"。
优惠券查询的案例,用领域建模的模式,首先思考有哪些实体。优惠券查询的本质:通过xx条件查询返回满足条件的优惠券集合。对于优惠券来讲,有两类信息至关重要。一个是优惠券的规格信息,如优惠券名称、优惠金额、有效期等;另一个是优惠券的限制条件。在查询的时候,是查店铺优惠券,还是查粉丝优惠券,或者是查询商品优惠券……。因此分开两部分抽象优惠券:一个是优惠券查询请求;另一个是优惠券规格实体。
如果按照这样的设计,有一个缺点是业务方理解复杂度会上升,它是偏底层实现,没有做到使用简单。优惠券偏产品交付而非仅仅功能交付。因此在底层实现之上,再抽象出产品组件,这样业务方使用起来就比较简单。
领取专属 10元无门槛券
私享最新 技术干货