高质量编程与性能调优 | 青训营笔记

104 阅读6分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 4 天

高质量编程与性能调优 | 青训营笔记

今天讲的是高质量编程和性能调优部分,虽然讲的是 Go 语言的,但我觉得其中的很多思想都是全语言通用的。

高质量编程

何为高质量编程,就是使编写的代码能够达到正确可靠,简洁清晰的程度。它有三个简要的原则:

  • 简单性
  • 可读性
  • 生产力

多的理论也不谈,我觉得还是谈谈实践下来该怎么做吧。

编码规范

代码格式

这个说起来难也不难,最重要的就是养成习惯。虽然说 Go 语言有自带的语言格式化工具,好像还是强制的,同一份代码绝不会格式化得不一样。不过这里还是说一下普遍认可的编写格式,毕竟也不是所有的语言或者 IDE 都能自动格式化。

一个非常重要就是加空格,空格的加发也很有讲究,我直到现在也没有研究完,这里就说几个我注意到的:

  • 在运算符的前后添加。比如 c=a+b,我们写成 c = a + b,会更加好一点。

  • 在逗号、冒号、分号后添加。一个经典的例子就是调用函数的时候,把 swap(a,b) 写成 swap(a, b),这样当参数多的时候也不会出现参数挤在一起的情况,更加好分辨。

  • 在一些关键词后添加。比如 for while if 这种后面往往还会跟一个表达式的。

  • 在大括号前添加。这个适用于像 Go 语言这种不换行起大括号的写法,当然如果换行了那肯定没必要了。

  • 还有一个比较随性的点就是,如果觉得写得挤了,那就加个空格

另一个值得注意的就是换行,不过换行的学问倒是比较少,而且一般也有比较明确的约定和习惯。唯一一个值得注意的就是在链式调用或者其他情况的时候,如果一行写得特别长,那么适度换行也是有益于身心健康的。

注释

我对注释比较惭愧,我是深受注释恩泽的,但是写注释的经验确实不多。Go 语言的注释似乎真的仅仅只有 // 而已,感觉相对其他语言差着点了。视频也说的不错,注释应该做的有如下四点:

  • 解释代码作用
  • 解释代码如何做的
  • 解释代码实现的原因
  • 解释代码什么情况会出错

这个说的确实有参考价值,下面讲一下我编写中我认为必须写注释的地方:

  • 函数对外的接口。这个是必须的,不然使用者云里雾里就不好了。按照比较基本的说法来说,有几点是要说的。一个是概述,简要讲讲这个函数的作用,原理也可以讲讲。另一个是参数,主要就是说明参数的意义和用法。还有就是如果有返回值的话可以把返回值的意思也说清楚。

  • 类对外的属性,要把每个属性的作用和意义都说清楚。

再其他的地方比如一些比较难理解的实现逻辑、用到了魔数之类的地方,也当然是要补充一下的。我是觉得注释有总比没有好,还是先考虑有没有再考虑写得好不好吧。

命名规范

这个是我比较纠结的地方,经常有起名困难症。按照 Go 语言的要求,还是尽量简短点为好。不过我之前命名的时候也都比较喜欢长命名来着,顺带一提,我是小驼峰党,小驼峰是真的好看呐,有时候甚至会怀疑自己是不是为了看小驼峰而故意起两个词的名字……

不过我记得其中有一点说的挺好的,就是声明的变量离使用的地方越远,命名就要越清晰。以及要根据上下文信息来适当减少或者加长变量名,防止意义重复或者意思缺失。

控制流程

一个是如果一个 if 分支最后是 return,则可以省去 else 不写。虽然以前写的时候隐隐约约有能 retrunreturn,缩减缩进的想法,但我还以为是奇淫巧技,不入流的。没想到原来是正确的代码编写规范。

另一个受益匪浅的原则就是,尽量保持代码路径为最小缩进。也就是说,尽量先判断错误然后 return,把正确的语句留在下面,最后做到结尾是返回成功运行的值就最好了。这样一般都能优化代码结构,把缩进减到比较小的程度。我曾经听过一个说法就是:如果你的代码的缩进超过了两层以上,就说明的你的代码需要进行进一步的重构和提取了。

错误和异常处理

  • 构造错误链
  • 错误包含和获取
  • 使用 panicrecover 对严重错误进行处理。Go 没有 try/catch 语句,因此 panicrecover 语句组合可以大概实现同样的功能。注:panic 能用尽量少用。recover 在当前 goroutine 的被 defer 的函数中生效

性能优化

主要是一些 tricks:

  • 使用 Benchmark 获得性能优化方向或者建议

  • 切片的切片使用的是原切片,注意因此导致的内存释放问题,可以使用 copy

  • slicemap 等构建时预分配内存会更快,即提前指定大小。

  • 字符串拼接用 string.Buider 更快,具体原理是其底层实现是 []byte,返回时直接将 []byte 转换为字符串

  • 空结构体 struct{} 不占内存,需要占位时用它更省内存,

  • 对某个数进行原子操作时,使用 atomic 包内的函数而不是使用锁会更快。具体原理是锁是系统调用, atomic 操作是硬件实现,效率更高。锁更适合保护一段逻辑。

性能调优

性能调优原则

  • 依靠数据而不是猜测
  • 要抓住最大瓶颈而不是细枝末节
  • 不能过早优化
  • 也不能过度优化。

性能分析工具 pprof

非常强力,而且可以数据可视化。

业务性能调优

具体可以从以下方面入手:

  • 业务服务优化。代码结构优化,整体逻辑简化。

  • 基础库优化。对于使用得多的基础库,要进行合理使用,或者更换更高效的库。

  • Go 语言优化。更换编译器,或者使用优化更深的编译参数。有点像 C++ 编译的时候 O1、O2 优化这样的感觉