前言

本手册为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)
}