本手册为go语言开发人员,为提升编码效率和为满足安全编码规范的随手参考书。内容条目以安全编码为主和常易忽视的编码细节为主,来自于以下三个方面的总结:
1、gosec、govet等go语言安全编码扫描工具,的规则的解读;
2、字节跳动、uber等使用go语言较成熟的大厂,的go语言编码规范的一些提取;
3、我们平时工作中对go语言代码走查,达成一致意见的总结。
本手册每个条目,包含条目的目的,为什么这么做以及正例和反例。条目分类开头的关键字,说明如下:
G开头: 表示go语言基本要求的编码规范;
S开头: 表示来自于安全类问题规范;
P开头: 表示来自于性能累问题的规范。
本手册分为上篇和下篇,下篇暂不发布,内容为对上篇的补充和增加。
G01-handle error
基本要求。
json.Unmarshal(b, &xxxx) // warning: Errors unhandled.
err := json.Unmarshal(b, &xxxx) // or : _ = json.Unmarshal(b, &xxxx)
if err != nil {
return errors.Wrap(err, "Unmarshal xxxx failed, jsonStr")
}
G02-nil是一个有效的长度为0的slice
基本要求。
if x == "" {
return []int{}
}
if x == "" {
return nil
}
S01-命令执行exec.Command的正确姿势
命令和参数需要分离,将后面的参数使用append([]string{}, args...)。参见gosec的G204
out, err := exec.Command("docker","run", "-d", "--net=host", "--entrypoint=/usr/local/bin/h2load", "gohttp2/curl")...).Output() // Subprocess launched with variable
if err != nil {
t.Skipf("Failed to run h2load in docker: %v, %s", err, out)
}
out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "--entrypoint=/usr/local/bin/h2load", "gohttp2/curl"}, args...)...).Output()
if err != nil {
t.Skipf("Failed to run h2load in docker: %v, %s", err, out)
}
S02-SQL语句使用format且关键字小写
format可以防止SQL注入,需要小写的关键字有:SELECT FROM、 WHERE、 INSERT INTO、 UPDATE、 DELETE FROM等。也可参见gosec的G201
fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)",schema.Name, head, value) // warnning
fmt.Sprintf("insert into %s (%s) values (%s)",schema.Name, head, value)
S03-文件的权限chmod的正确姿势
创建文件夹和文件时注意权限和非root运行,降低权限,如果必须要0664,可以将0664改为0600|0064。原因参见gosec的G301和G302
os.OpenFile(logName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0664) // Expect file permissions to be 0600 or less
os.OpenFile(logName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600|0064)
S04-对文件路径对输入检验
将路径/文件参数用path.Clean()或filepath.Clean()进行校验,注意必须要in line,参见gosec的G304
func File2lines(filePath string) ([]string, error) {
f, err := os.Open(filePath) //Warning here
if err != nil {
return nil, err
}
defer f.Close()
return linesFromReader(f)
}
func File2lines(filePath string) ([]string, error) {
f, err := os.Open(filepath.Clean(filePath)) //check file path
if err != nil {
return nil, err
}
defer f.Close()
return linesFromReader(f)
}
P01-strconv比fmt效率高
将原语转换为字符串或从字符串转换时,strconv比fmt效率高。
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
P02-字符串转化字节效率低
不要反复从固定字符串创建字节slice。相反,请执行一次转换并捕获结果。
for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}