编程原则
简单性
-
消除“多余的复杂性”,以简单清晰的逻辑编写代码
-
不理解的代码无法修复改进
可读性
-
代码是写给人看的,而不是机器
-
编写可维护代码的第一步是确保代码可读
生产力
- 团队整体工作效率非常重要
编码规范 - 注释
注释应该解释代码作用
每个函数应该用注释解释它的行为、功能、作用,包括预期结果、异常等。但是,如果通过函数名、参数名,能够大概知道函数的作用时,就没有必要再编写重复描述的注释了。
注释应该解释代码如何做的
对于一个函数或代码块,应当用适当的注释解释是如何实现的。
注释应该解释代码实现的原因
对于某个目的不太明确的代码块,应当提供额外的上下文注释,以便于解释这段代码块出现的外部因素。
注释应该解释代码什么情况会出错
如果某个函数对于输入参数有一定的约束条件时,应当编写函数可能出错的情况。
公共符号是重要注释
-
包中声明的每个公共的符号:变量、常量、函数以及结构都需要添加注释。
-
任何既不明显也不简短的公共功能必须予以注释。
-
无论长度或或复杂程度如何,对库中的任何函数都必须进行注释。
编码规范 - 命名规范
变量
-
简洁胜于冗长
-
缩略词全大写,但其位于变量开头且不需要导出时,使用全小写。例如使用 ServeHTTP 而不是 ServeHttp 。
-
变量距离其被使用的地方越远,则需要携带更多的上下文信息。
函数
-
函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的。
-
函数名尽量简短。
-
当名为 foo 的包某个函数返回类型为 Foo 时,可以省略类型信息。
-
当名为 foo 的包某个函数返回类型为 T 时( T 并不是 Foo ),可以在函数名中加入类型信息。
包
-
只由小写字母组成。不包含大写字母和下划线等字符。
-
简短并包含一定的上下文信息。
-
不要与标准库同名。例如,不要使用 sync 或者 strings
-
不适用常用变量名作为包名。例如使用 bufio 而不是 buf
-
使用单数而不是复数。
编程规范 - 流量控制
避免嵌套,保持正常流量清晰
例如,如果两个分支都会 return ,则可以去除冗余的 else 。
// Bad
if foo {
return x
} else {
return nil
}
// Good
if foo {
return x
}
return nill
尽量保证正常代码路径为最小缩进
优先处理错误/特殊情况,尽早返回或继续循环来减少嵌套。例如,用多个嵌套的 if 语句来处理正常返回情况是不好的。
// Bad
func OneFunc() error {
err := doSomething()
if err == nil {
err := doAnotherThing()
if err == nil {
return nil // normal case
}
return err
}
return err
}
// Good
func OneFunc() error {
if err := doSomething(); err != nil {
return err
}
if err := doAnotherThing(); err != nil {
return err
}
return nil // normal case
}
编程规范 - 错误和异常处理
简单错误
-
简单错误指的是仅出现一次的错误,不会在其他地方捕获该错误。
-
有限使用 errors.New 来创建匿名变量来直接表示简单错误。
-
如果有格式化的需求,使用 fmt.Errorf
错误的 Wrap 和 Unwrap
-
错误的 Wrap 实际上是提供了一个 error 嵌套另一个 error 的能力,从而生成一个 error 的跟踪链。
-
在 fmt.Errorf 中使用 %w 关键字来实现将一个错误关联至错误链中。
错误判定
-
使用 errors.is 来判定错误是否为特定错误。
-
不同于使用 == , 该方法可以判定错误链上的所有错误。
-
在错误链上获取特定种类的错误,使用 errors.As 。
性能优化建议
测试工具使用
使用指令go test -bench=. -benchmem来测试评估某个函数的性能。测试指标包括执行时间、占用内存大小等。
性能优化建议 - Slice
-
slice 预分配内存,尽可能在使用 make() 初始化切片时提供容量信息。
-
在已有的切片基础上创建新的切片,不会创建新的底层数组。因此,使用 copy 代替直接 re-slice , 以便于释放原 slice 。
origin := make([]int, 0, 10000)
result := origin[:3] // 不会创建新的 slice
copy(result, origin[:3]) // 会创建新的 slice
性能优化建议 - Map
- 同 slice 建议预分配内存,这是因为不断向 map 中添加元素的操作会触发 map 的扩容。
性能优化建议 - 字符串
-
字符串在 Go 语言中是不可变类型,占用内存大小是固定的
-
使用 + 每次都会重新分配内存。
-
strings.Builder , bytes.Buffer 底层都是 []byte 数组。
-
内存扩容策略,不需要每次拼接都重新分配内存。
性能优化建议 - 空结构体
-
空结构体本身不占用内存
-
实现 Set , 可以考虑用 map[T]struct{} 来代替。
-
只用到 map 的键。
-
即使将 map 的值设置为 bool 类型,也会多占用 1 个字节空间。
总结
本次课程主要介绍了代码格式、注释、命名规范、控制流程、错误和异常处理等方面的常见编码规范,以确保代码的可读性、可维护性、可扩展性。此外,本节课还提出了有关提高程序运行效率的性能优化建议,包括 slice, map, string, empty struct 等方面的性能优化建议。同时,通过指令go test -bench=. -benchmem来测试评估程序的性能。