何谓代码质量?
代码是给人看的
1. 书写规范:遵照自己公司制定的编程语言书写规范。
2. 易阅读。
3. 易修改。
4. 易测试。
代码是给机器运行的
1. 安全
2. 快速
3. 稳定
代码质量的标准?
对于机器来说,标准是恒定的,但不可兼得。
比如:
锁机制:
- 安全、慢
指针:
- 快、不稳定
改内存地址:
- 快、不安全
总的来说,这就像 CAP 理论一样,不同场景下的需求不一样,根据当下业务需求去做出取舍即可。
对于人来说,标准是变化的,因为习惯不同、工期不同、目的不同。
易阅读
表意明确
名词要准确
类、结构体、变量、常量等名词要能直观地描述这是个什么东西,一般 1-5 个单词组成为宜。
英文单词里没有官方缩写的就尽量不用缩写,像 result 就 6 个字母,也有人给缩写成 res、ret,temp 缩写成 tmp,更有甚者写 cnt,它到底是 count 还是 content 呢?除了歧义,完全没有任何好处。
动词要精简
方法名、函数名等动词要能保证只做一件事。一个方法不写太长的前提是它的功能本身就不多。
形容词要归约
属性、校验等形容词要归约为方法,业务逻辑关键点大多在判断上,预留扩展点会让阅读难度不随着加需求而快速增大。
单词统一
不要有歧义,因为人是有思维惯性的,比如同样的业务逻辑,有的写 add,有的写 append,有的写 insert,会严重影响阅读效率。
描述业务
有意义的名字要专注于描述业务,使读者通过阅读代码理解业务逻辑。不要在起名中掺杂数据结构。
例如这么几个场景:列表(集合)、配置映射
- good case:users(用户集合),articles(文章列表)、siteNameToSiteId(映射)
- bad case:userSet、articleList、siteMap
加上类型,并不会对理解业务逻辑有帮助,读者看到 list,map 这些关键词还会联想到数据结构中,很容易打断思维。
避免赘述
具有包含关系或从属关系的时候,不要重复,不要表达累赘的语境。
关注作用域和生命周期
当一个变量的作用域很窄,或生命周期很短的时候,可以用单字母命名,一般来讲,单字母意味着临时使用,读者在了解逻辑的时候可以不用关注这部分。
变量要接近使用的地方声明,不要开头声明一堆变量,隔几十行才使用。
写有用的注释
“好的代码是自描述的” 即读代码就和读文章一样。注释不应该用来解释代码逻辑,而应该是用来说明为什么这么写。
写什么样的注释
- 公共的、全局的变量和常量:说明用在哪,提供给谁用。
- 函数,方法:说明函数功能是什么。
- 行注释:xx 产品在 x 年 x 月 x 日提出什么需求,做此修改。
注释不是用来删代码的!!!
代码不用了就彻底删除,怕以后还有用就从 git 里找回来,如果一个函数 100 行,其中 50 行都被注释掉了,这种会很容易分散读者的注意力。
易修改
一值一用
不要把一个变量重复赋值使用。虽然类型一样,但这样做会让修改的人非常头疼,所谓牵一发而动全身。例如:
- bad case:
result, err := a.Get()
result, err = b.Get()
- good case:
resultA, err := a.Get()
resultB, err = b.Get()
少写参数
当你发现一个函数的功能需要传入七八个参数才能完成的时候,一定是函数干的事太多了,逻辑写太长了。需要适当拆分。
正确使用逻辑运算符
&&、||、!这些逻辑运算符是用来做逻辑判断的,不是用来控制执行流程的。
例如这样一段逻辑:
if (isA()) {
doB()
}
不要写成 isA() && doB(),尽管结果是一样的。
适当化简
if ((condition1() && condition2()) || !condition1()) {
return true
}
return false
取反后化简为:
if(condition1() && !condition2()) {
return false
}
return true
降低圈复杂度
圈复杂度的定义:https://zh.wikipedia.org/wiki/循环复杂度
增加圈复杂度的关键词:
if、else、while、for、case、||、&& 等
圈复杂度的合格标准:
大部分标准在 10-20 之间。
这也是一个平均值,不是要求每一个函数都在 20 以下。
个别超标,是可以接受的。
如何降低
1. 提炼函数
2. 抽象配置,使用 map
3. 合并返回值相同的函数
多写函数少写变量
实现同样的功能,并不是代码越少越好。
因为代码越少往往意味着耦合度越高,修改扩展起来会更麻烦,就是爽了自己,给别人留坑。
但是,每一行代码都要有价值。
如果说逻辑节点之间需要一个东西来充当桥梁,变量就是独木桥,函数像隧道。防杠精:有人说要考虑性能开销啊,多写一个函数比一个栈内的变量开销大啊,我觉得业务代码不差这一星半点的,自行斟酌。
易测试
TDD
测试驱动开发:写一个函数之前先考虑写出来之后能不能测试,好不好测试。
实现方式
第一步:先写单元测试。不必关心如何实现函数功能。
第二步:写目标函数,以刚好能通过单元测试的逻辑代码为目的。
第三步:重构函数,合理命名,优化结构,抽象设计。
如此循环,保证每次改动代码都能完好地通过所有测试用例。
这样做的目的是让错误尽早的暴露出来,在 10 行代码中解决 bug 要比在 100 行代码中解决 bug 更加容易和快速。
理想与现实
看起来 TDD 的理论和可操作性还不错,但实际开发中,如果真的严格按照此理论去开发。对开发效率是一个比较大的影响。
而且一旦形成惯性思维和盲从依赖,会降低对代码的灵感和熟练度。
测试用例跑过了就没问题了吗?不一定。因为测试用例也是人写的,隐藏的坑才最致命。
其实,当做到易阅读和易修改之后,易测试就是水到渠成的事情。
IoC 模式
将对象、接口、非固定值(如系统时间、随机数)等作为依赖注入,先构造条件,再执行函数。而不是由函数内部去构造。
全局变量单一写入方
当有两个以上的函数控制同一个全局变量的时候,会相互影响,即局部形成了一个状态机,使测试难度陡升。
封装外部依赖
有外部依赖的,尤其涉及 IO 通信的,要单独封装,哪怕只有几行代码也要封装成一个独立的函数,不要把对外部依赖的调用混合在自身的逻辑代码中。
最后
阅读 → 修改 → 测试
这是一个递进的关系,环环相扣,并且前一步做好了都能有利于后一步的完善。
有疑问加站长微信联系(非本文作者)