如何提升代码质量

何谓代码质量?

代码是给人看的

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 通信的,要单独封装,哪怕只有几行代码也要封装成一个独立的函数,不要把对外部依赖的调用混合在自身的逻辑代码中。

最后

阅读 → 修改 → 测试

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


Golang 攻略
针对 Golang:介绍正确用法;剖析核心设计;总结最佳实践。

Seek the truth!

2.1k 声望
413 粉丝
0 条评论
推荐阅读
一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。知识点详解秒杀系统架构图秒杀流程图秒杀系统设计这篇文章一万多...

王中阳Go34阅读 2.6k评论 1

封面图
前端如何入门 Go 语言
类比法是一种学习方法,它是通过将新知识与已知知识进行比较,从而加深对新知识的理解。在学习 Go 语言的过程中,我发现,通过类比已有的前端知识,可以更好地理解 Go 语言的特性。

robin23阅读 3.3k评论 6

封面图
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀24阅读 58.2k评论 2

年度最佳【golang】map详解
这篇文章主要讲 map 的赋值、删除、查询、扩容的具体执行过程,仍然是从底层的角度展开。结合源码,看完本文一定会彻底明白 map 底层原理。

去去100216阅读 11.6k评论 2

年度最佳【golang】GMP调度详解
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底的. 这篇文章将通过分析...

去去100215阅读 11.9k评论 4

万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide8阅读 1.7k

封面图
数据结构与算法:二分查找
一、常见数据结构简单数据结构(必须理解和掌握)有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小)无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)复杂数据结构树、 堆图二...

白鲸鱼9阅读 5.3k

Seek the truth!

2.1k 声望
413 粉丝
宣传栏