这是我参与「第五届青训营」伴学笔记创作活动的第4天。
前言
今天的学习内容很有意义,熟悉一下Go的语法规范以及一些性能优化上的思路,毕竟Go对比Java较大的两个优势就是并发性和性能了,从我个人角度来说,这也是培养我对Go的编程思维的一个方式,因此简单分享下今日的上课笔记和心得。
知识点内容
1.编码规范
实现高质量编程——即编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码。
1-1.代码格式
使用gofmt(Go官方提供的工具,能自动化格式化Go代码为官方统一风格)自动格式化代码。Goland已经配置好了,会把项目中的代码自动格式化,因而我使用Goland只需要专心编码即可。
1-2.注释
原则:
注释应该解释代码的作用
注释应该解释代码是如何做的
注释应该解释代码实现的原因(提供额外上下文)
注释应该解释代码什么情况会出错
公共符号始终要注释—>
包中声明的每个公共符号:
变量、常量、函数以及结构都需要添加注释;
任何既不明显也不简短的公共功能必须予以注释;
无论长度或复杂程度如何,对库中的任何函数都必须进行注释;有一个例外->不需要注释实现接口的方法。
其实注释这里的规范,大部分语言都一致,因为注释的作用都是一样的,都是为了解释代码而出现的。
1-3.命名规范
变量命名规范
简洁;
缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写;
变量距离被使用的地方越远,命名需要携带更多的上下文信息。
函数命名规范
函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的;
函数名尽量简短;
当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义;
当名为foo的包某个函数返回类型T时(T并不是Foo),可以在函数名加入类型信息。
包命名规范
只由小写字母组成,不包含大写字母和下划线等字符;
简短并包含一定的上下文信息;
不要与标准库同名。
以下规则尽量满足:
不使用常用变量名为包名;
使用单数而不是复数;
谨慎地使用缩写。
1-4.控制流程
避免嵌套,保持正常流程清晰。
尽量保持正常代码路径为最小缩进,这里考虑优先处理错误和特殊情况,尽早返回或继续循环来减少嵌套。
代码示例:
//错误处理
func test() bool { ```
func test() bool {
name := "aa"
if len(name) != 0 {
return true
}
return false
}
//正确处理
func test() bool {
name := "aa"
if len(name) != 0 {
return true
}
return false
}
1-5.错误和异常处理
简单错误(仅出现一次的错误且在其他地方不需要捕获该错误)
优先使用errors.New()来创建匿名变量来直接表示简单错误;
如果有格式化的需求,使用fmt.Errorf()。
复杂错误
使用Wrap和Unwrap,Wrap可以将一个error嵌套到另一个error,从而生成一个error的跟踪链;
在fmt.Errorf()中使用“%w”关键字来将一个错误关联到错误链中;
使用errors.Is()可以判定一个错误是否为特定错误;
使用errors.As()在错误链上获取特定种类的错误。
panic
当程序启动阶段发生不可逆转的错误时,可以在init方法或main函数中使用,若问题可以被屏蔽或解决,建议使用error代替panic。
recover(只能在被defer的函数中使用,嵌套不生效)
defer的语句是后见先出
只在当前goroutine生效
如果需要更多上下文信息,可以让recover后在log中记录当前的调用栈
2.性能优化建议
原则:
要依靠数据不是猜测
要定位最大瓶颈而不是细枝末节
不要过早优化
不要过度优化
2-1.基准性能测试工具benchmark
测试代码时加上以下参数就可以看到当前代码的执行次数、执行时间、每次执行申请的内存大小和每次执行申请的内存次数,就可以根据这些数据分析当前代码的性能。
go test -bench=. -benchmem
2-2.slice预分配内存
尽可能在使用make()初始化切片时提供容量信息。
2-3.字符串处理
使用strings.Builder()拼接字符串。
2-3.空结构体
使用空结构体节省内存,空结构体struct{}实例不占据任何内存空间。
2-4.atomic包
在时间上,它的性能比直接加锁好很多,atomic包通过硬件实现,效率比锁高,因为锁是通过操作系统实现的。
2-5.性能分析工具pprof
用一个小Demo试用一下pprof,这里我选用web的方式查看分析结果:
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
// 性能分析
go func() {
log.Println(http.ListenAndServe(":8080", nil))
}()
// 实际业务代码
for {
Add("test")
}
}
func Add(str string) string {
data := []byte(str)
sData := string(data)
var sum = 0
for i := 0; i < 10000; i++ {
sum += i
}
return sData
}
在浏览器的地址栏输入:http://localhost:8080/debug/pprof/ 就可以使用pprof提供的功能:
各个部分对应的功能上图已标注。
小结
通过今天的学习,小结一下:代码是最好的注释,注释应该提供代码未表达出的上下文信息;命名的核心目标是降低阅读理解代码的成本,重点考虑上下文信息;处理逻辑应尽量走直线,避免复杂的嵌套分支,实际生产中大多数故障问题出现在复杂的语句条件和循环语句中(Ps:这点真是血泪经验啊,曾经实习的时候维护的那个老项目,那一大串的if嵌套出了问题根本无从下手);error尽可能提供简明的上下文信息链,方便定位问题,而panic则用于真正异常的情况;recover在当前goroutine的被defer的函数中生效。最后一句话总结下性能优化这件事:避免常见的性能陷阱就是最好的优化,不要一味地追求性能,毕竟一个系统能稳定运行是最重要的!