高质量编程 | 青训营

101 阅读5分钟

前言

本文主要整理青训营高质量与性能调优实战课程部分关于高质量编程的规范以及性能优化笔记。结合自身实践,给出部分编码建议。

正文

何为高质量编程

编码目标为正确可靠、简洁清晰。需要考虑完备的边界条件;保证程序的异常处理;提高代码易读性以及易维护性。

高质量编程遵循着一些相通的原则:

  1. 简单性。消除不必要的复杂逻辑;
  2. 可读性。确保代码寓意清晰,可读性强;
  3. 生产力。保证团队开发效率。

编码规范

主要从代码格式、注释、命名规范、控制流程以及错误与异常处理方面进行介绍。

代码格式

推荐使用Go语言官方提供的gofmt自动格式化代码为官方统一风格。 最近我分别使用VSCode和Goland编写Go语言代码时,代码保存后都会自动调整格式,应该是开发工具或者开发环境插件中进行了自动配置。

注释

注释的功能包括以下几点:

  1. 解释代码作用。尤其对于开放给外部访问的代码块,解释代码作用的注释应该放在开头;
  2. 解释代码如何实现该功能。即为代码实际的执行逻辑;
  3. 解释代码为什么这样实现。根据代码的上下文情况解释代码这样实现的原因;
  4. 解释代码什么情况下会出错。表明代码的限制条件;

包中声明的公共符号始终要注释,便于外部调用。注释应该提供代码未表达出的上下文信息。

我之前看的一个将高质量编码的外国视频建议代码不写注释,更加注重代码本身的编写逻辑以及命名规范,主张好的编码无需注释。目前我认为必要的注释更有利于提高代码质量。

命名规范

变量命名

  1. 简洁胜于冗长,例如无特殊意义的循环索引;
  2. 缩略词全大写,但当其位于变量开头其不需要导出时,使用全小写;
  3. 变量距离被使用的地方越远,则需要携带越多的上下文信息;

函数命名

  1. 函数名不携带包名信息,因为包名和函数名总是成对出现;
  2. 函数名尽量简短;
  3. 当名为foo的包某个函数返回类型foo时,可以省略类型信息避免导致歧义;
  4. 当名为foo的包某个函数返回类型非foo时,可以在函数名中加入类型信息。

包命名

  1. 只有小写字母组成;
  2. 简短并包含一定的上下文信息;
  3. 避免与标准库同名。

命名规范的核心目标是降低阅读理解代码的成本,需要考虑上下文信息设计简洁清晰的名称。

控制流程

  1. 避免嵌套,保证正常流程清晰;
  2. 尽量保持正常代码路径为最小缩进,优先处理错误、异常情况并返来减少嵌套。

错误与异常处理

简单错误

指仅出现一次的错误,且其他地方不需要捕获该错误,优先使用errors.New来创建匿名变量来直接表示简答错误,如果有格式化的需求使用fmt.Errorf。

错误的Wrap和Unwrap

错误的Wrap提供一个error嵌套另一个error的能力,从而生成一个error的跟踪链,在fmt.Errorf中使用"%w"关键字来将一个错误关联至错误链中。

错误判定

判定一个错误是否为特定错误使用errors.ls,不同于使用"==",该方法可以判定错误链上的所有错误是否含有特定的错误。

在错误链上获取特定种类的错误,使用errors.As。

panic

不建议在业务代码中使用panic,调用函数不包含recover会造成程序崩溃,若问题可以被屏蔽或解决则应该用error代替panic。

recover

只能在被defer的函数中使用,嵌套无法生效,只在当前goroutine生效,多个defer语句遵循后进先出。可以recover后在log中记录当前调用栈以获取更多上下文信息。

error的作用是提供上下文信息链以定位问题,panic用于真正异常的情况,recover在当前goroutine的被defer的函数中生效。

性能优化建议

性能优化的前提是满足正确可靠,简洁清晰等质量因素。

benchmark基准测试工具

性能表现需要实际数据衡量,使用命令为go test -bench = . -benchmem。

slice

  1. 预分配内存。尽可能使用make()初始化切片时提供容量信息。切片本质是一个数组片段的描述,切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组。
  2. 大内存未释放。大切片基础上新建小切片,小切片复用大切片元素,所以底层数组不会被释放。

Map

预分配内存。map添加元素的操作可能触发扩容,提前分配空间可以减少内存拷贝和Rehash的消耗。

字符串处理

使用"+"拼接性能最差,strings.Builder、bytes.Buffer相近,strings.Buffer更快。这由字符串在Go语言中为不可变类型决定,使用"+"每次都会重新分配内存。

该操作和Java中类似,Java中字符串拼接也是优先采用StringBuilder类而不是"+"。

空结构体

空结构体struct{}示例不占据任何内存空间,可作为各种场景下的占位符使用。

atomic包

锁的实现通过系统调用,atomic操作通过硬件实现,效率更高。加锁操作更适合保护一段逻辑而不是单个变量。

对于非数值操作,可以使用atomic.Value,能承载一个interface{}。

结语

高质量编程不局限于使用的编程语言,很多技巧是通用的。日常代码实践中应该优先保证代码逻辑清晰与正确性,在此基础上追求性能优化。