三、高质量编程与性能调优实践
3.1 编码规范
3.1.1代码格式:
-- dofmt(golang内置);-- goimports(gofmt基础上增加了依赖包管理)
3.1.2 注释:
解释代码功能、实现过程、为什么这么做(外部因素,提供额外上下文)、什么情况会出错(限制条件)
-
包中声明的每个公共符号(变量、常量、函数、结构)
- 例外:不需要注释实现接口的方法
- 不明显、不简短的公共功能
- 库中的任何函数
3.1.3 命名规范:
-
变量
- 缩略词全大写,但当位于变量开头且不需要导出时,使用全小写:如ServerHTTP,而不是ServerHttp
- 变量距离被使用的地方越远,要携带越多上下文信息,对外提供时需要更多信息量
- 尽量简洁
-
function
- 不携带包名的上下文信息,因为包名和函数名总是携带出现的
- 简短
- 函数返回类型名字同包名时省略类型信息,使无歧义;不同时可在函数名中加入类型信息
-
package
- 只用小写字母组成
- 简洁并包含一定上下文
- 不与标准库和常用变量名重名
- 使用单数而不是复数
- 谨慎地使用缩写,fmt > format
3.1.4 控制流程:
线性原理,尽量走直线,避免嵌套分支
- 避免嵌套:if-else中若都包含return语句,去除冗余的else
//bad
if foo{
return x
}else{
return nil
}
//good
if foo{
return x
}//若有新增情况,直接在此插入
return nil
- 尽量保持正常代码路径为最小缩进:优先处理错误情况或特殊情况,尽早返回或继续循环来减少嵌套
//Good
func OneFunc() error{
if err := doSomething; err != nil{
return err
}
if err := doAnotherThing; err != nil{
return err
}
//若有新增情况,直接在此插入
return nil // normal case
}
3.1.5 异常处理
-
简单错误:仅出现一次的错误,且在其他地方不需要捕捉该错误
- 优先使用 errors.New 来创建匿名变量来直接表示简单错误,如有格式化需求,使用fmt.Errorf
func defaultCheckRedirect(req *Request,via []*Request) error{ if len(via) >= 10{ return errors.New("stopped after 10 redirects") } return nil } -
错误的 Wrap 和 Unwrap:
- 错误的 Wrap 提供了一个 error 嵌套另一个 error的能力 ,从而生成一个 error 的跟踪链
- 在 fmt.Errorf 中使用 %w 将一个错误关联至错误链中
list,_,err:=c.GetBytes(cache.Subkey(a.actionID,"srcfiles")) if err != nil{ return fmt.Errorf("reading srcfiles list: %w",err) } -
错误判定:
- 判定是否为特定错误:errors.is ;不同于 == ,可判定错误链上的所有错误是否含有特定错误
- 从错误链上获取特定种类错误的内容:errors.As 。方便定位分析问题
if _,err := os.Open("non-existing");err != nil{ var pathError *fs.PathError if errors.As(err,&pathError){ fmt.Println("Failed at path:",pathError.Path) }else{ fmt.Println(err) } } -
panic (更严重,程序无法工作):
-
不建议在业务代码中使用
- 调用函数不包含 recover 会造成程序崩溃
- 若问题可以被屏蔽或解决,建议使用 error 代替 panic
-
当程序启动阶段发生不可逆错误时,可以在 init 或 main 函数中使用 panic
-
-
recover:
- 引用的库出现 bug 时