7

何谓代码质量?

代码是给人看的

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 这些关键词还会联想到数据结构中,很容易打断思维。

避免赘述

具有包含关系或从属关系的时候,不要重复,不要表达累赘的语境。

关注作用域和生命周期

当一个变量的作用域很窄,或生命周期很短的时候,可以用单字母命名,一般来讲,单字母意味着临时使用,读者在了解逻辑的时候可以不用关注这部分。

变量要接近使用的地方声明,不要开头声明一堆变量,隔几十行才使用。

写有用的注释

“好的代码是自描述的” 即读代码就和读文章一样。注释不应该用来解释代码逻辑,而应该是用来说明为什么这么写。

写什么样的注释

  1. 公共的、全局的变量和常量:说明用在哪,提供给谁用。
  2. 函数,方法:说明函数功能是什么。
  3. 行注释: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

测试驱动开发:写一个函数之前先考虑写出来之后能不能测试,好不好测试。

实现方式

1

第一步:先写单元测试。不必关心如何实现函数功能。
第二步:写目标函数,以刚好能通过单元测试的逻辑代码为目的。
第三步:重构函数,合理命名,优化结构,抽象设计。

如此循环,保证每次改动代码都能完好地通过所有测试用例。

这样做的目的是让错误尽早的暴露出来,在 10 行代码中解决 bug 要比在 100 行代码中解决 bug 更加容易和快速。

理想与现实

看起来 TDD 的理论和可操作性还不错,但实际开发中,如果真的严格按照此理论去开发。对开发效率是一个比较大的影响。
而且一旦形成惯性思维和盲从依赖,会降低对代码的灵感和熟练度。
测试用例跑过了就没问题了吗?不一定。因为测试用例也是人写的,隐藏的坑才最致命。
其实,当做到易阅读和易修改之后,易测试就是水到渠成的事情。

IoC 模式

将对象、接口、非固定值(如系统时间、随机数)等作为依赖注入,先构造条件,再执行函数。而不是由函数内部去构造。

全局变量单一写入方

当有两个以上的函数控制同一个全局变量的时候,会相互影响,即局部形成了一个状态机,使测试难度陡升。

封装外部依赖

有外部依赖的,尤其涉及 IO 通信的,要单独封装,哪怕只有几行代码也要封装成一个独立的函数,不要把对外部依赖的调用混合在自身的逻辑代码中。

最后

阅读 → 修改 → 测试

这是一个递进的关系,环环相扣,并且前一步做好了都能有利于后一步的完善。


ronniesong
2.1k 声望418 粉丝

Seek the truth!