编程 Go 1.28 vet 新增 scannererr / sqlrowserr:Scan 与 Next 循环里的错误不能再默认忽略

2026-09-16 21:04:31

Go 1.28 vet 新增 scannererr / sqlrowserr:Scan 与 Next 循环里的错误不能再默认忽略

Go 1.28 的 vet 增加了两个分析器(见 tip.golang.org/doc/next 草案):

  • scannererr:检查 bufio.ScannerScan 循环之后没有处理 scanner 错误,扫描错误或底层 I/O 错误会被漏报。
  • sqlrowserr:对 sql.Rows.Next 循环做同类检查,让迭代错误与「结果本身就少」区分开。

两者对应的是同一类 bug:循环正常退出并不代表读取过程没有出错。

scannererr 报什么

触发形式:

sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
    line := sc.Text()
    use(line)
}
// 没有 sc.Err()

诊断信息形如:

bufio.Scanner "sc" is used in Scan loop at line N without final check of sc.Err()

实现要点:从 bufio.NewScanner 调用出发,检查返回变量是否在循环中被用于调用 Scan,但从未调用 Err()

如果 NewScanner 的 reader 参数是「不会失败」的类型——strings.Readerbytes.Readerbytes.Buffer——分析器会跳过,避免噪音。这些数据源虽然也可能在 token / 行超过内部缓冲时失败,但远不如真实 I/O 常见。

sqlrowserr 对应 Rows.Next

Rows.Next 在遇到错误时同样返回 false,和「结果集读完」表现一致。只写循环不写 Rows.Err(),错误就被当成结果更少:

rows, err := db.Query(q)
if err != nil {
    return err
}
defer rows.Close()

for rows.Next() {
    var name string
    if err := rows.Scan(&name); err != nil {
        return err
    }
    use(name)
}
// 没有 rows.Err()

背景:issue #17747 到 22K 模块实测

相关讨论在 golang/go#17747,实现提交为 golang/tools commit 85bb374

  • 有团队反馈,代码库里漏检 Scanner.Err / Rows.Err 的用法约占 10%;
  • 在约 100 处 Scan / Next 调用里,事后补上 Err 检查全部是正确的;
  • 分析器在 22K 个模块上跑出 2337 处命中,来自 1162 个模块;
  • 把「无失败可能的 reader」排除之后,噪音大幅下降。

落地上,分析器先进入 gopls(CL 730480),再纳入 cmd/vet——安排在 Go 1.27 发布完成之后,实际作为 Go 1.28 vet 的新增项。

正确写法

sc := bufio.NewScanner(f)
for sc.Scan() {
    // ...
}
if err := sc.Err(); err != nil {
    return err
}
rows, err := db.Query(q)
if err != nil {
    return err
}
defer rows.Close()

for rows.Next() {
    // ...
}
if err := rows.Err(); err != nil {
    return err
}

共同点是把 Err() 放在循环之外、退出循环之后立刻检查,而不是在循环体内判断。

落地建议

gopls 侧:分析器先进 gopls,编辑器里打开对应诊断即可第一时间看到报告,不需要等 go vet 落地。

go vet 侧:Go 1.28 起随 cmd/vet 提供,go test 默认会跑 vet,CI 上无需额外配置。存量代码按 Scan / Next 调用点做一轮审计,重点看读取文件、stdin、网络连接、数据库结果集的位置——这些才是真正会出错的路径。

复制全文 生成海报 Go vet 静态分析 错误处理

推荐文章

程序员茄子在线接单