这是我参与「第五届青训营 」伴学笔记创作活动的第3天。
0、 前言
本节主要简要介绍了高质量编程的定义和原则,
分享了代码格式、注释、命名规范、控制流程、错误和异常处理五方面的常见编码规范。
课程重点如下:
-
高质量编程
- 编码规范
- 性能优化建议
-
性能调优实战(略)
1、高质量编程
编码规范
-
代码格式:推荐使用gofmt自动格式化代码
-
注释的意义:
- 注释应该解释代码作用
- 注释应该解释代码如何做的
- 注释应该解释代码实现的原因
- 注释应该解释代码什么情况会出错
- 公共符号始终要注释(但是不需要注释实现接口的方法)
-
命名规范:
-
简洁胜于冗长
-
缩略词全大写,但当位于变量开头且不需要导出时,使用全小写
- 使用
ServeHTTP而不是ServeHttp - 使用
XMLHTTPRequest而不是xmlHTTPRequest
- 使用
-
变量距离其被使用的地方越远,则需要携带越多的上下文信息
-
函数名不携带包名的上下文信息且尽量简短
-
函数名应当尽量简短
-
当名为
foo包的某个函数返回类型为Foo时,可以省略类型信息而不导致歧义 -
当名为
foo包的某个函数返回类型为T而不是Foo时,应该在函数名中加入类型信息 -
package名只由小写字母组成
-
简短并包含一定的上下文信息
-
不要和标准库同名
-
不使用常用变量名作为包名。例如使用
bufio而不是buf -
使用单数而不是复数。例如使用
encoding而不是encodings -
谨慎的使用缩写。例如使用
fmt在不破坏上下文的情况下比fotmat更简短。
-
-
控制流程:
- 避免嵌套,保持正常流程清晰,例如去掉不必要的
else - 尽量保持正常代码路径为最小缩进,能对称就对称
- 故障问题的大多出现在复杂的条件语句和循环语句中,尽量化简
- 避免嵌套,保持正常流程清晰,例如去掉不必要的
-
错误和异常处理:
-
简单错误:指仅出现一次的错误,且在其他地方不需要捕获该错误
-
优先使用
errors.New来创建匿名变量来直接表示简单错误 -
如果有格式化需求,请使用
fmt.Errorf -
// 一个例子 func defaultCheckRedirect(req *Request, via []*Request) error { if len(via) >= 10 { // 使用errors.New return errors.New("stopped after 10 redirects.") } return nil // 去掉不必要的else } 复制代码 -
错误的
Wrap与Unnwrap:- 错误的
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.ls,不同于使用==,该方法可以判定错误链上的所有错误是否含有特定的错误// 一个例子 data, err = lockedfile.Read(targ) if errors.Is(err, fs.ErrNotExist) { return []byte{}, nil } return data, err 复制代码 -
在错误链上获取特定种类的错误,使用
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:与panic对应,如果需要更多的上下文信息可以在recover后在log中记录当前的调用栈
生效条件:
-
只能在被defer的函数中使
-
嵌套无法生效
-
只在当前goroutine生效
-
注意defer是一个栈
-
-
性能优化建议
-
使用 Benchmark 进行基准测试;
-
尽可能为 slice 和 map 预分配内存(通过在
make时指定容量信息); -
注意为切片创建切片不会创建新的底层数组,这可能会导致内存泄漏发生,此时可用
copy代替 re-slice; -
多个字符串拼接时,使用
strings.Builder比直接使用+或使用bytes.Buffer更快(这和 Java 倒是十分相似,Java 也推荐使用StringBuilder拼接多个字符串;其实他们的底层逻辑都是类似的); -
当需要占位符时,可使用空结构体(
struct{})代替,其不会占据镇和内存空间; -
使用
atomic包代替锁修改变量;