这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记
这一节课将了解以下知识:
- 如何编写更简洁清晰的代码
- 常用的Go语言程序优化手段
- 熟悉Go程序性能分析工具
- 了解工程中性能优化的原则和流程
以下是记录了前半部分课程的笔记,关于高质量编程的定义、如何编写高质量的代码。
1、高质量编程
编程首先要完成基本的功能,那什么是高质量编程?有哪些实践规范?常见的性能优化建议有哪些?
1.1 高质量编程的定义
编写的代码能够达到**正确可靠**、**简洁清晰**的目标可称之为高质量代码。
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
1.2 编程原则
简单性
消除多余的复杂性,以简单清晰的逻辑编写代码。
不理解的代码无法进行修复改进!!!!
可读性
代码时写给人看的,不是机器!
编写可维护代码的第一步是确保代码可读
生产力
团队整体工作效率非常重要
(编码是在整个项目开发链路中的一个节点,遵循规范,避免常见缺陷的代码能够降低后续联调、测试、验证、上线等各个节点出现问题的概率,就算出现问题也能很快排查)
1.3 如何编写高质量的Go代码
代码格式
注释
命名规范
控制流程
错误和异常处理
1.3.1 代码格式
推荐使用gofmt自动格式代码
gofmt:是GO官方提供的工具,能自动格式化GO语言代码为官方统一风格
goimports:也是Go官方提供的工具=gofmt+依赖包管理;自动增删依赖的包引用,将依赖包按字母排序并分类
如下是在Goland里面配置,开启之后就可以在保存文件时自动格式化;
我是直接在setting->Tools->File Watches添加了go fmt、go import,然后调用的时候goimports会报错找不到文件,然后点invoke安装即可。
在工具栏选择'Tools -> Go Tools'就能看到有gofmt和goimports工具,点击就可以应用。
例子:
自动格式化:
依赖包管理:
使用go imports之后的结果:
1.3.2 注释
注释的目的:
- 解释代码作用
·适合注释公共符号,如对外提供函数注释描述其功能和用途,只有函数功能简洁明显时才可省略。
- 解释代码如何做的
·对代码中复杂的,并不明显的逻辑进行说明
- 解释代码实现的原因
·可以解释代码的外部因素,这些因素脱离上下文后通常很难理解
- 解释代码什么情况会出错
·提醒使用者一些潜在的限制条件或者会无法处理的情况
小结:
- 代码是最好的注释
- 注释应该提供代码未表达出的上下文信息
1.3.2 命名规范
variable
简洁胜于冗长
缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写:如ServeHTTP而不是ServeHttp
变量距离其被使用的地方越远,需要携带越多的上下文信息,使用的时候能够对应到具体含义
function
函数名不携带包名的上下文信息
函数名尽量简短
当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
当名为foo的包某个函数返回类型T时(不是Foo),可以在函数名中加入类型信息
package
只由小写字母组成
简短并包含一定的上下文信息,如schema、task
不要与标准库同名,如sync、strings
小结
- 核心是降低阅读理解代码的成本
- 重点考虑上下文信息,设计简洁清晰的名称
1.3.3 控制流程
避免嵌套,保持正常流程清晰
尽量保持正常代码路径为最小缩进
·优先处理错误情况|特殊情况,尽早返回或继续循环来减少嵌套
1.3.4 错误和异常处理
简单错误:仅出现一次的错误,优先使用errors.New来创建匿名变量来直接表示
对于复杂的错误,有时候并不能简单描述,应该如何处理?
·错误的包装提供了一个error嵌套另一个error的能力,生成一个error的跟踪链,同时结合错误的判定方法来确认调用链中是否有关注的错误出现。这个能力的好处是每一层调用方可以补充自己对应的上下文,方便跟踪排查问题,确定问题的根本原因在哪里
·在fmt.Errorf中使用:%w关键字来将一个错误wrap至其错误链中
错误判定:
判断是否为特定错误用errors.Is
在错误链上获取特定种类的错误用errors.As
panic
不建议在业务中使用
调用函数不包含recover会造成程序崩溃
若问题可以被屏蔽或解决,建议使用error代替
当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
recover
只能在被defer的函数中使用
嵌套无法生效
只在当前goroutine生效
defer的语句是后进先出(栈)
如果需要更多的上下文信息,可以在recover后在log中记录当前的调用栈
1.4 性能优化建议
性能优化的前提是满足正确可靠、简洁清晰等质量因素
性能优化是综合评估,有时候时间效率和空间效率可能对立
1.4.1 benchmark
go test -bench=. -benchmem
1.4.2 Slice
slice预分配内存,尽可能在使用make()初始化切片时提供容量信息
1.4.3 Map
map预分配内存
不断向map中添加元素的操作会触发map的扩容
提前分配好空间可以减少内存拷贝和rehash的消耗
建议根据实际需求提前预估好需要的空间
1.4.4 字符串处理
使用string.Builder
使用+拼接性能最差,string.Builder,bytes.Buffer相近,前者更快
1.4.5 空结构体
使用空结构体节省空间
struct{}不占据任何内存,可作为各种场景下的占位符使用
1.4.6 atomic包
锁的实现通过操作系统实现,是系统调用
atomic操作时通过硬件实现,效率比锁高
sync.Mutex应该用于保护一段逻辑,不仅仅用于包含一个变量
对于非数值操作,可以使用atomic.Value,能承载一个interface{}
总结:通过学习,进一步了解了在编程中如何提高代码质量。之前写代码有点随意,不太规范,之后要将代码规范实际应用起来,可以用一些工具作为辅助,写出更简洁清晰的代码,特别是代码注释这一方面,不写注释,只要时间够久,就会不记得当初写的是个什么玩意,很难理解。