代码质量在「内卷时代」的重要性

共 4533字,需浏览 10分钟

 ·

2020-12-27 09:21

这里是Z哥的个人公众号

每周五11:45 按时送达

当然了,也会时不时加个餐~

我的第「173」篇原创敬上



大家好,我是Z哥。

提到代码质量,不知道你的脑海中浮现出的第一个词是什么?规范?可读性?优雅?

在我的心中,好的代码质量 = 舒服。看着舒服,接手这样的项目感觉舒服,在其中找问题和改代码舒服。


软件开发这个行业是一个年轻的行业,如果在十几年前谈代码质量,可能还算是个比较高级的问题。但是在大家都认为越来越内卷的当下,注重和提升代码质量我认为是每个程序员的必修课。说的严重一些,它影响了你在团队中的价值,说的表面一些,它是你在团队中的“面子”。

假象一下,你接手了两个项目,一个项目代码干干净净、非常整洁,另一个随处可见的相似代码以及杂乱无章的分类摆放。你对这两个项目的前负责人是什么想法?未来你更愿意和谁合作?我想答案是显而易见的。


在我看来要写出舒服的代码并不需要对那些代码规范背的滚瓜烂熟,其实只要掌握一个六字核心原则:高内聚低耦合。如此写出的代码至少能在60分以上。

可以回想一下,当你在做一个简单项目的时候,使用那些成熟的框架和工具可以轻松地完成大部分工作。甚至会感觉有点无聊,因为感觉都是在CRUD。这就是应用了经过精心设计的高内聚低耦合的框架和工具所具有的效果,让事情变简单。

讲句题外话,「高内聚低耦合」在软件开发领域真是一个黄金原则,在哪都适用,大到一个分布式系统的设计,小到一个class的设计。如果我的脑子只能记住一条原则的话,毫不犹豫会选择它。


那么如何让自己也能写出高内聚低耦合的代码呢?我们要对「高内聚低耦合」有更深入地理解,而不是仅仅停留在这6个字上。

葡萄牙马德拉大学精确科学与工程中心的教授,被认为是计算机领域先驱者之一的赖瑞·康斯坦丁带队对内聚性和耦合性做了深入的研究和分析,对内聚性和耦合性的强弱关系进行了梳理,得到了以下结论。(摘自于维基百科)

内聚性的分类如下,强度由低到高排列:

偶然内聚性:是指模块中的机能只是刚好放在一起,模块中各机能之间唯一的关系是其位在同一个模块中(例如:“工具”模块)。

逻辑内聚性:是只要机能只要在逻辑上分为同一类,不论各机能的本质是否有很大差异,就将这些机能放在同一模块中(例如将所有的鼠标和键盘都放在输入处理副程序中)。模块内执行几个逻辑上相似的功能,通过参数确定该模块完成哪一个功能。

时间性内聚性:是指将相近时间点运行的程序,放在同一个模块中(例如在捕捉到一个异常后调用一函数,在函数中关闭已开启的文件、产生错误日志、并告知用户)。

程序内聚性:是指依一组会依照固定顺序运行的程序放在同一个模块中(例如一个函数检查文件的权限,之后开启文件)。

联系内聚性/信息内聚/通信内聚:是指模块中的机能因为处理相同的资料或者指各处理使用相同的输入数据或者产生相同的输出数据,因此放在同一个模块中(例如一个模块中的许多机能都访问同一个记录)。

依序内聚性/顺序内聚:是指模块中的各机能彼此的输入及输出资料相关,一模块的输出资料是另一个模块的输入,类似工厂的生产线(例如一个模块先读取文件中的资料,之后再处理资料)。

功能内聚性:是指模块中的各机能是因为它们都对模块中单一明确定义的任务有贡献(例如XML字符串的词法分析)。


耦合性的分类如下,强度由高到低排列:

内容耦合:也称为病态耦合当一个模块直接使用另一个模块的内部数据,或通过非正常入口而转入另一个模块内部。

共享耦合/公共耦合:也称为全局耦合指通过一个公共数据环境相互作用的那些模块间的耦合。公共耦合的复杂程度随耦合模块的个数增加而增加。

外部耦合:发生在二个模块共享一个外加的资料格式、通信协议或是设备界面,基本上和模块和外部工具及设备的沟通有关。

控制耦合:指一个模块调用另一个模块时,传递的是控制变量(如开关、标志等),被调模块通过该控制变量的值有选择地执行块内某一功能;

特征耦合/标记耦合:也称为数据结构耦合,是指几个模块共享一个复杂的数据结构,如高级语言中的数组名、记录名、文件名等这些名字即标记,其实传递的是这个数据结构的地址;

资料耦合/数据耦合:是指模块借由传入值共享资料,每一个资料都是最基本的资料,而且只分享这些资料(例如传递一个整数给计算平方根的函数)。

消息耦合:可以借由以下二个方式达成:状态的去中心化(例如在对象中),组件间利用传入值或消息传递 (计算机科学)来通信。

无耦合:模块完全不和其他模块交换信息。


如果你代码写的还不够多,上面有些差异还无法很好的感知。但是你不需要把这些概念一字一句背下来,只要平时在写代码的时候多思考一下:“当前的代码设计是属于哪种类型?”。如果不能确定的话回头来看这篇文章:D。慢慢地,通过写更多的代码,你会对耦合和内聚的强弱,有更敏感的感知力。

根据上面的这些概念,写出高质量代码的思路就很清晰了。method的归类、class的归类能根据功能内聚性归类的绝不用顺序内聚,能根据顺序内聚性归类的绝不用更弱的。耦合也是同样的,能不耦合的就不耦合,能用消息耦合的绝不用数据耦合。

但是想要保证代码按照这个设想去发展,还是需要通过做一些具体的事情作为抓手。这些事不需要全部做,但每一项都有助于提高代码质量。


/01  执行代码规范+Code Review/

在Z哥看来,执行代码规范,最重要的价值并不是非得让100%的代码符合这个规范,而是让所有人一起养成一种意识,意识到我的代码会被别人看到,被评价。这样才能在写代码的时候,不仅仅是为了实现功能。

所以具体代码规范是什么样,并没那么重要,可以是自己定义的,也可以是参考大厂的。当然我更推荐前者,大厂的规范虽好,但是你要全部照搬,这个执行成本可不小。

如果你想提高代码质量,但又不想做很多事。那么执行代码规范+Code Review可以是你的唯一选择。如果你是一位个人开发者,那么可以让身边你认为代码写的最好的人帮你做CodeReview,以他的规范作为代码规范即可。


很多人觉得代码规范是一种约束,会降低开发效率。其实不会,最多在初期因为自己并不习惯一些规范,所以花了很多时间在修正代码。一旦走上正轨后,代码规范反而会提高开发效率,因为节省了很多阅读代码的时间以及同事之间沟通的时间。

就算它真的降低了开发效率,但你要提升效率也不应该降低代码质量,而是通过其它方式去提效。


/02  写单元测试/

写单元测试之所以能提高代码质量,是因为如果不是高内聚低耦合的代码,你会发现单元测试非常难写。

比如,你只想测一下方法A,但是发现里面的依赖错综复杂,好吧,都stub掉。最后发现测一个方法写了几十个stub,这种操作我亲眼看到过……。这就是前面提到的「内容耦合」过多了。

所以,能轻松地写出单元测试,并且将其养成一种习惯,你的代码质量必然不会差。


/03  设计先行/

虽然设计不出完美的代码,但是优先考虑设计可以让你多思考“我应该怎么写这段代码”,而不是直接抄起家伙就写,写到哪算到哪。

毕竟大多数功能都不可能一步到位,需要多次迭代。这种情况下最初的设计就显得尤为重要,毕竟大部分人遇到不舒服的代码不会推翻重写,最多就是修修补补,甚至是直接在这之上叠加新的代码。


/04  项目与团队”微服务化”/

保证一个几万行代码的项目质量和几百万行代码的项目必然难度不同。所以,如果合适的话可以将项目拆小,并且由专门的团队负责。这样可以提高团队把控代码质量意愿,并降低其难度。


/05  利用相关的工具/

主流的编程语言或多或少都有一些静态代码分析工具、单元测试覆盖率统计工具,这些要充分利用起来。它们可以快速的帮助避免一些低级的代码坏味道,节约大量时间。


/06  几个代码层面的小建议/  

01  勿过度使用链式编程

很多人会追求极致少的代码行数,恰好链式编程能投其所好。的确在很多时候链式编程可以提高代码的可读性,但是它带来的弊端也是显而易见的,

  1. 调试的时候观察变量变得很不方便。

  2. 容易在当前方法里处理不应该在这里处理的业务逻辑。毕竟很多class的方法和属性是public的,相比单独做一层封装再调用,“点”出来直接用多香啊~所以在使用链式编程的时候也得遵守「得墨忒耳定律」。


得墨忒耳定律:
每个单元对于其他的单元只能拥有有限的知识:只是与当前单元紧密联系的单元;
每个单元只能和它的朋友交谈:不能和陌生单元交谈;
只和自己直接的朋友交谈。
维基百科


02  避免随处可见的try-catch

Try-catch虽好,切勿贪杯。很多人喜欢写try-catch然后通过一个单独封装的通用返回模型告知调用方出现了什么异常。

这种方法的目的最初是为了避免上层调用者没有做异常捕获导致程序崩溃,但是弊端也是显而易见的,如果调用方没有正确的判断返回模型里的异常相关属性,会导致程序在错误的状态下继续执行,这个后果就不可预知了。

所以我认为通过try-catch封装异常应该出现在更上层的代码里,越底层的代码越不应该封装异常。


03  认真编写访问修饰符

很多编程语言都有多个访问修饰符,我们在编写的代码的时候应该尽可能的选择最严格的修饰符,而不是什么都是public。

因为public会导致很多变量在不知道什么情况下就被外部修改了,导致bug层出不穷、排查困难,项目质量堪忧。

访问修饰符的过于宽松也是前面提到的链式编程被过度使用的推手之一。

访问修饰符的目的是为了防止程序员在无意间误用不应该使用的方法和属性,毕竟代码往往不只有一个人写。


04  慎用继承

继承的确挺香的,可以少写很多代码。但是使用不当会破坏封装的效果,造成访问修饰符的失效。

继承的正确使用姿势应该传达的是“子父”的关系,而不是“相似”的关系。比如“汽车”可以继承于“交通工具”,但是不应该继承于“自行车”,虽然它们都有轮子。

像汽车和自行车的这种情况要复用的话,可以抽象提炼出相同的部分,然后通过「组合」的方式进行。


最后,如果你对代码质量有更高的追求,想修炼和强化“内功”,那必须不能错过这本经典书籍。(之前的黄皮版本更新成这本灰皮了)


好了,总结一下。

这篇呢,Z哥和你分享了我对代码质量这件事的看法。在行业越来越内卷的趋势下,注重“质”总是没错的。

Z哥认为想要提高代码质量最核心的原则就是:高内聚低耦合。文中给你罗列了赖瑞·康斯坦丁教授提炼了不同的内聚性和耦合性原则来表达关系的强弱。

基于对内聚性和耦合性原则的理解,再通过以下抓手进行代码质量的提升工作:

  1. 执行代码规范+Code Review

  2. 写单元测试

  3. 设计先行

  4. 项目与团队”微服务化”

  5. 利用相关的工具


最后还分享了几个代码层面的建议:

  1. 勿过度使用链式编程

  2. 避免随处可见的try-catch

  3. 认真编写访问修饰符

  4. 慎用继承


希望对你有所启发。



推荐阅读:


原创不易,如果你觉得这篇文章还不错,就「在看」或者「分享」一下吧。鼓励我的创作 :)


如果你有关于软件架构、分布式系统、产品、运营的困惑

可以试试点击「阅读原文

浏览 11
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报