高质量代码
通用的原则:简单性,可读性,生产力,以及下面强调的正确问题,考虑到多种情况
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
编码规范
开源的编码规范文档,比较重要的下面几点
-
注释:公共符号(变量,常量,函数,结构体)始终要注释,库中公开提供的所有函数都必须注释。有一个例外,不需要注释实现接口的方法
注释应当解释代码的功能,如何实现(尤其是一些看起来有点复杂的逻辑),实现的原因(适合解释代码的外部因素,比如说各种接口的问题状态码之类,提供额外上下文),什么情况下会出错(解释代码的限制条件)
原则:代码是最好的注释,注释应该提供代码未表达出的上下文信息
-
代码格式:这是一个基础,团队的编码格式要统一,gofmt / goimports(gofmt + 依赖包管理,分类字母排序) 官方内置功能
-
命名规范:简洁胜于冗长,最好一眼就能知道啥意思
- 变量命名的规则:缩略词如 HTTP 全大写,但当其位于变量开头且不需要导出时,全小写。具体命名的时候,变量距离它被使用的地方越远,就越要把名称写详细。还有全局变量,这是最要写详细的。有时候也要写短,比如说循环中的 i tmp,它们的作用域小嘛,但比如说如果函数要提供给外部调用,参数就不能写这种 t 啊 i 啊之类的,写明白到底啥意思
- 函数命名的规则:函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的,也由于这个原因,如果返回的类型名和包名差不多,那么可以省略类型信息,但如果返回的是其他类型,可以在函数名中加入返回值类型信息
//举例 package time func Now() Time //调用时 time.Now, 更简洁也清楚 func NowTime() Time //调用时 time.NowTime ``` - 包命名的规则:只有小写字母,不要与标准库同名。尽量满足,不使用常见常量名作为报名,使用单数少用复数,谨慎用英文缩写,得大家公认的才行
-
控制流程:条件和循环
- 避免 if-else 嵌套
- 尽量让缩进最小化。比如说优先处理错误情况和特殊情况,尽早返回或继续循环来减少嵌套
- 控制流程最好走直线,沿屏幕向下移动
-
错误和异常处理:error 尽可能提供简明上下文信息链,真正救不回来的 panic,在外部 panic 需要 recover 的时候请
- 简单错误:仅出现一次,其他地方不需要它,返回错误信息就行。这时候,如果简单描述一下就 errors.New("错误提示"),如果有格式化的需求就 fmt.Errorf(这两个的输出是什么样的?查一下)
- 复杂错误:Wrap Unwrap 嵌套错误,生成错误生成链,每一层都能提供出错的上下文信息。在 fmt.Errorf 中使用 %w 关键字可以将一个错误关联至错误链中
- panic:程序崩溃,最好不要在业务代码中使用,如果问题可以屏蔽或解决,可以用 error 代替 panic。但如果程序启动阶段就遇到了不可逆转的错误,尽早暴露错误,就直接打出日志,panic
- recover:处理 panic 崩溃,方便我们处理外部调用的代码 panic 了的情况。使用规则是 recover 只能在 defer 中生效,只能在当前协程中生效,无法嵌套。在写 defer 时,请注意 defer 语句是先进后出的,以此判断到底哪里的 defer 先调用
- 如果需要更多的上下文信息,可以 recover 后在 log 中记录当前调用栈
//判定错误链中是否含有特定错误 errors.Is(err, fs.ErrNotExist) //在错误链上获取特定种类的错误 var pathError *fs.PathError errors.As(err, &pathError) fmt.Println ... pathError.Path //打印这个种类的错误的内容 //recover defer func() { if e:= recover(); e != nil { f = nil err = fmt.Errorf("gitfs panic: %v\n%s", e, debug.Stack()) }() } ```
学生阶段特别容易忽视这种编码规范,但这些规范又是很重要的,需要多在实践中加以运用呀