这是我参与「第五届青训营」伴学笔记创作活动的第 3 天
注释
一个好的代码注释应当做到以下几点:
- 解释代码作用;
- 解释代码如何做的;
- 解释代码实现的原因;
- 解释代码什么情况会出错;
- 公共符号始终要注释(但是不需要注释实现接口的方法)。
作为 Java 开发者想要吐槽的是,Go 的注释系统实在太简陋了,我认为:
// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
复制代码
从各种意义上讲都更加直观。(当然以上举例并不是想说明 Java 注释写得比 Go 详细,而是想说明 Go 的注释就只是单纯的注释而已,它不支持 Java 的 javadoc 注释那样丰富的富文本和标签支持,这就导致看起来十分模糊)
命名规范
变量
- 简洁胜于冗长;
- 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写(例如使用 ServerHTTP 而不是 ServerHttp,使用 XMLHTTPRequest 或是 xmlHTTPRequest 而不是 XmlHttpRequest);
- 变量距离其被使用的地方越远,则需要携带更多的上下文信息(尤其是全局变量)。
举个例子,在一个经典的三段 for 循环中:
// Bad
for index := 0; index < len(s); index++ {
// do sth.
}
// Good
for i := 0; i < len(s); i++ {
// do sth.
}
复制代码
由于 index 变量作用域仅限于 for 循环内,更长的注释反而对增加阅读的理解没什么作用,所以采用更短的 i 变量。(我个人倒是觉得见仁见智了,虽然如果换我我也会用 i)
// Good
func (c *Client) send(req *Request, deadline time.Time)
// Bad
func (c *Client) send(req *Request, t time.Time)
复制代码
将 deadline 换成 t 降低了变量名的信息量。前者特指截止时间,而后者仅是代表某个时间。
函数
- 函数名不应携带包名的上下文信息,因为两者总是成对出现;
- 函数名应当尽量简短;
- 当名为
foo包的某个函数返回类型为Foo时,可以省略类型信息而不导致歧义; - 当名为
foo包的某个函数返回类型为T而不是Foo时,应该在函数名中加入类型信息。
package http
// Bad
func ServeHTTP(I net.Listener, handler Handler) error
// Good
func Serve(I net.Listener, handler Handler) error
复制代码
包
- 只由小写字母组成。不包含大写字母和下划线等字符;
- 简短并包含一定的上下文信息;
- 不要和标准库同名。
- 不使用常用变量名作为包名。例如使用
bufio而不是buf; - 使用单数而不是复数。例如使用
encoding而不是encodings; - 谨慎的使用缩写。例如使用
fmt在不破坏上下文的情况下比fotmat更简短。
总体来说,Go 的包命名规范也体现了 Go 大道至简的风格。
编码规范
控制流程
- 避免嵌套,保持正常流程清晰;
// Bad
if foo {
return x
} else {
return nil
}
// Good
if foo {
return x
}
return nil
复制代码
- 尽量保持正常代码路径为最小缩进;
错误和异常处理
- 对于简单错误(仅出现一次,在其他地方不需要捕获),优先使用
errors.New创建匿名变量直接表示;如有格式化需求,使用fmt.Errorf; - 在
fmt.Errorf中使用%w将一个错误关联至错误链中; - 使用
errors.Is判定一个错误为特性错误,比起直接使用==的好处是可以判断错误链上的所有错误是否含有特定错误; - 使用
errors.As获取错误链上特定种类的错误; - 只有在程序启动阶段发生不可逆转的错误时才使用
panic(类似于 Javajava.lang.Error的地位,但是 Go 可以使用revover语句来从panic中恢复;
性能优化
- 使用 Benchmark 进行基准测试;
- 尽可能为 slice 和 map 预分配内存(通过在
make时指定容量信息); - 注意为切片创建切片不会创建新的底层数组,这可能会导致内存泄漏发生,此时可用
copy代替 re-slice; - 多个字符串拼接时,使用
strings.Builder比直接使用+或使用bytes.Buffer更快(这和 Java 倒是十分相似,Java 也推荐使用StringBuilder拼接多个字符串;其实他们的底层逻辑都是类似的); - 当需要占位符时,可使用空结构体(
struct{})代替,其不会占据镇和内存空间; - 使用
atomic包代替锁修改变量;