问:
如何编写更简洁清晰的代码
常用Go语言程序优化手段
熟悉Go程序性能分析工具
了解工程中性能优化的原则和流程
高质量的编程
高质量编程简介
编写的代码能够达到正确可靠、简介清晰的目标,就可以称为高质量代码
- 边界条件考虑得完备吗
- 异常情况处理,稳定性保证
- 易读易维护
- 1逻辑别太复杂,别人不太理解就没法修复改进
- 2提高生产力
编码规范
-
代码格式
- 如gofmt可以自动格式化go语言代码为官方统一风格
- goimports可以自动增删依赖的包引用、将依赖包按字母序列排序并分类
- 自己在使用Goland的时候有过这样的体验,就是
-
注释
-
作用
- 1注释了别人容易看懂
- 2注释了以后自己看的时候,也更容易回忆起来,自己就亲身体验过,以前写的代码后来看不懂
-
注释谁
- 注释公共符号
- 注释实现过程
- 解释代码什么情况会出错
- 小结
- 代码是最好的注释
- 注释应该提供代码未表达出的上下文信息
-
-
命名规范
-
变量名
- 简洁胜冗长
- 变量距离其被使用的地方月圆,则需要携带越多的上下文信息,如全局变量的名字
-
函数名
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
- 比如
- http.Serve
- http.ServeHTTP
- 第二个就太重复了吧
- 比如
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
-
包名
- 只由小写字母组成,不包含大写字母和下划线等字符
- 简短并包含一定的上下文信息
- 不要与标准库同名
- 不要使用常用变量名作为包名(想法,万一后面有个人随手想定义个常用变量,结果这个已经作为包名了,后面可能会造成一些误解。
- 使用单数而不是负数
- 谨慎地使用缩写
-
-
控制流程
-
尽量保持正常代码路径为最小缩进(默认规定这种方式是正常流程,这样别人就不用还花力气判断哪个是正常代码路径了)
-
故障问题大多出现先在复杂的条件语句和循环语句中
-
-
错误处理
-
简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
- 优先使用erros.New来创建匿名变量来直接表示简单错误
- 如果有格式化的需求,使用fmt.Errof
-
错误的Wrap和Unwrap
- 这个是针对复杂一点的错误的,可以从这个链中找到问题的关键
- 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
- 在fmt.Errof中使用%w关键字来讲一个错误关联至错误链中
-
错误判定
- 判定一个错误是否为特定错误的方法:error.Is
-
panic
- 这个是不建议在业务代码中使用的
- 调用函数不包含recover会造成程序崩溃
- 若问题可以被屏蔽或解决,建议使用error代替panci
- 当程序启动阶段,发生不可逆转的错误时,可以在init或者panic函数中使用panic
-
recover
-
只能在被defer的函数中使用
-
嵌套无法生效
-
只在当前goroutine生效
-
defer的语句时后进先出
- 这个可以理解成栈
-
关于什么时recover
-
在 Go 语言中,
recover()函数被用来处理程序的 panic 异常。当程序发生 panic 时,程序会立刻停止执行当前函数的任何代码,然后开始执行 deferred 函数,接着继续向上层函数传递这个 panic,直到某个函数处理了这个 panic 或者整个程序都被迫停止运行。使用
recover()函数可以在当前函数内捕获 panic,并恢复程序的正常执行。recover()必须在 defer 函数内被调用,否则它不会起作用。如果在 defer 函数中调用recover(),并且当前函数发生了 panic,那么recover()将返回 panic 的值,并且阻止程序继续向上层函数传播 panic。如果当前函数没有发生 panic,或者当前函数没有包含 defer 函数,那么recover()将返回 nil。注意,
recover()只能在 defer 的函数体内直接调用,否则会失效。此外,当一个函数中的 panic 被 recover 处理后,整个程序并不会自动恢复正常执行状态。必须手动编写代码来实现恢复程序的正常执行状态。
-
-
-
性能优化建议
- 先把前面的正确可靠的代码搞好
- Benchmark
- 如何使用
- 性能表现需要实际数据衡量
- Go语言提供了支持基准性能测试的benchmark工具
- 性能优化之Slice
- 切片本质是一个数组片段的描述
- 包括数组指针
- 片段的长度
- 片段的容量
- 切片操作并不复制切片指向的元素
- 创建一个新的切片会复用原来切片的底层数组
- 所以如果使用切片的使用冒号来截短原来切片,是会存在复用的,代码会在媛切片基础上新建小切片。而原底层数组在内存中有引用,内存就会得不到释放
- 切片本质是一个数组片段的描述
- 性能分析之map
- map和slice是同理的
- 不断向map中添加元素的操作会触发map的扩容
- 提前分配好空间可以减少内存拷贝和Rehash的消耗
- 所以建议根据实际需求提前预估好需要的空间
- map和slice是同理的
- 字符串处理
- 比较直接使用加号和strings.Builder和ByteBuffer,strings.Builder是时间最少的
- 字符串在Go语言中是不可变类型,占用内存大小是固定的,每次使用加号,都会重新分配内存
- strings.Builder和bytes.Buffer底层都是[]byte数组,内存扩容策略,不需要每次拼接重新分配内存
- 空间优化之空结构体
- 空结构提struct{}实例不占据任何的内存空间
- 可作为各种场景下的占位符使用
- 节省资源
- 空结构提本身具备很强的语义,即这里不需要任何值,仅作为占位符
- atomic包,在时间上优于加锁
- 锁的实现是通过操作系统来实现,属于系统调用
- atomic操作是通过硬件实现,效率比锁高
- sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
- 听人劝
- 避免常见的性能陷阱可以保证大部分程序的性能
- 普通应用代码,不要一味地追求程序的性能
- 越高级的性能优化手段越容易出现问题
- 在满足正确可靠、简介清晰的质量的前提下提高程序性能
- 如何使用