mcpField
G
golang-safety

golang-safety

github.com/samber开发3.7k41.0k 次安装5 天前更新
代码仓库

关于 golang-safety

golang-safety 是面向 Go 的正确性与防御式编程技能,适用于常规应用代码。它通过处理 nil 值、集合行为、数值范围和资源管理,帮助工程师避免程序崩溃与静默数据损坏。

主要功能

  1. 01nil 与接口安全
  2. 02切片和映射使用指导
  3. 03数值转换与浮点数检查
  4. 04资源生命周期和 defer 指导
  5. 05防御性复制与安全初始化

适用场景

  1. 01检查代码中的 nil 相关 panic
  2. 02排查数值转换导致的溢出
  3. 03避免内部集合被意外修改

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-safety

安装后在 Claude Code 中输入/golang-safety确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
nil-safety.md
slice-map-safety.md

详细介绍

角色设定: 你是一名防御型 Go 工程师。你会将所有未经测试、与 nil、容量和数值范围有关的假设都视为潜在的崩溃隐患。

Go 安全性:正确性与防御式编码

防止程序员失误——正常(非对抗性)代码中的错误、panic 和静默数据损坏。安全性应对攻击者;安全性也应对我们自己。

最佳实践摘要

  1. 在类型集合已知时优先使用泛型,而非 any——编译器可以捕获类型不匹配,而不是等到运行时发生 panic
  2. 始终安全地进行类型断言——对于普通接口,使用逗号 ok 惯用法(v, ok := x.(T));对于 Go 1.25+ 中的反射,优先使用 reflect.TypeAssert[T](value),而不是 value.Interface().(T)。
  3. 接口中的类型化 nil 指针不等于 nil——类型描述符会使其变为非 nil
  4. 向 nil map 写入会 panic——使用前务必初始化
  5. append 可能会复用底层数组——如果容量允许,两个切片会共享内存,并在不知不觉中相互破坏数据
  6. 从导出函数返回防御性副本——否则调用方可以修改你的内部数据
  7. defer 在函数退出时运行,而非每次循环迭代时运行——将循环体提取为函数
  8. 整数转换会静默截断——int64 转为 int32 会发生回绕,且不会报错
  9. 浮点运算并不精确——使用 epsilon 比较或 math/big
  10. 设计实用的零值——nil map 字段首次写入时会 panic;使用延迟初始化
  11. 使用 sync.Once 进行延迟初始化——即使在并发环境下,也能保证只执行一次

Nil 安全性

与 nil 相关的 panic 是 Go 中最常见的崩溃原因。

nil 接口陷阱

接口存储(类型,值)。只有类型和值都为 nil 时,接口才是 nil。返回一个类型化 nil 指针会设置类型描述符,使接口变为非 nil:

// ✗ Dangerous — interface{type: *MyHandler, value: nil} is not == nil
func getHandler() http.Handler {
    var h *MyHandler // nil pointer
    if !enabled {
        return h // interface{type: *MyHandler, value: nil} != nil
    }
    return h
}

// ✓ Good — return nil explicitly
func getHandler() http.Handler {
    if !enabled {
        return nil // interface{type: nil, value: nil} == nil
    }
    return &MyHandler{}
}
nil map、切片和 channel 的行为
类型索引 nil 值写入 nil 值nil 值的 Len/Cap遍历 nil 值
Map零值panic0迭代 0 次
切片panicpanic0迭代 0 次
Channel永久阻塞永久阻塞0永久阻塞
// ✗ Bad — nil map panics on write
var m map[string]int
m["key"] = 1

// ✓ Good — initialize or lazy-init in methods
m := make(map[string]int)

func (r *Registry) Add(name string, val int) {
    if r.items == nil { r.items = make(map[string]int) }
    r.items[name] = val
}

有关 nil 接收者、泛型中的 nil 以及 nil 接口性能,请参阅 Nil 安全性深入解析。

切片与 Map 安全性

切片别名——append 陷阱

如果容量允许,append 会复用底层数组。此时两个切片会共享内存:

// ✗ Dangerous — a and b share backing array
a := make([]int, 3, 5)
b := append(a, 4)
b[0] = 99 // also modifies a[0]

// ✓ Good — full slice expression forces new allocation
b := append(a[:len(a):len(a)], 4)
Map 并发访问

不得并发访问 Map——→ 有关同步原语,请参阅 samber/cc-skills-golang@golang-concurrency。

有关 range 陷阱、子切片导致的内存滞留以及 slices.Clone/maps.Clone,请参阅 切片与 Map 深入解析。

数值安全性

隐式类型转换会静默截断
// ✗ Bad — silently wraps around if val > math.MaxInt32 (3B becomes -1.29B)
var val int64 = 3_000_000_000
i32 := int32(val) // -1294967296 (silent wraparound)

// ✓ Good — check before converting
if val > math.MaxInt32 || val < math.MinInt32 {
    return fmt.Errorf("value %d overflows int32", val)
}
i32 := int32(val)
浮点数比较
// ✗ Bad — floating point arithmetic is not exact
var a, b, c float64 = 0.1, 0.2, 0.3
a+b == c // false

// ✓ Good — use epsilon comparison
const epsilon = 1e-9
math.Abs((a+b)-c) < epsilon // true
除以零

整数除以零会 panic。浮点数除以零会产生 +Inf、-Inf 或 NaN。

func avg(total, count int) (int, error) {
    if count == 0 {
        return 0, errors.New("division by zero")
    }
    return total / count, nil
}

有关作为安全漏洞的整数溢出,请参阅 samber/cc-skills-golang@golang-security 技能章节。

资源安全性

循环中的 defer——资源累积

defer 在_函数_退出时运行,而非每次循环迭代时运行。资源会一直累积,直到函数返回:

// ✗ Bad — all files stay open until function returns
for _, path := range paths {
    f, _ := os.Open(path)
    defer f.Close() // deferred until function exits
    process(f)
}

// ✓ Good — extract to function so defer runs per iteration
for _, path := range paths {
    if err := processOne(path); err != nil { return err }
}
func processOne(path string) error {
    f, err := os.Open(path)
    if err != nil { return err }
    defer f.Close()
    return process(f)
}
Goroutine 泄漏

→ 有关 goroutine 生命周期和防止泄漏,请参阅 samber/cc-skills-golang@golang-concurrency。

不可变性与防御性复制

返回切片或 Map 的导出函数应返回防御性副本。

保护结构体内部数据
// ✗ Bad — exported slice field, anyone can mutate
type Config struct {
    Hosts []string
}

// ✓ Good — unexported field with accessor returning a copy
type Config struct {
    hosts []string
}

func (c *Config) Hosts() []string {
    return slices.Clone(c.hosts)
}

初始化安全性

零值设计

设计类型,使 var x MyType 可以安全使用——防止“忘记初始化”错误:

var mu sync.Mutex   // ✓ usable at zero value
var buf bytes.Buffer // ✓ usable at zero value

// ✗ Bad — nil map panics on write
type Cache struct { data map[string]any }
使用 sync.Once 进行延迟初始化
type DB struct {
    once sync.Once
    conn *sql.DB
}

func (db *DB) connection() *sql.DB {
    db.once.Do(func() {
        db.conn, _ = sql.Open("postgres", connStr)
    })
    return db.conn
}
init() 函数的陷阱

→ 有关为什么应避免使用 init(),转而采用显式构造函数,请参阅 samber/cc-skills-golang@golang-design-patterns。

使用 Linter 强制检查

许多安全隐患都能由 Linter 自动捕获:errcheck、forcetypeassert、nilerr、govet、staticcheck。有关配置和用法,请参阅 samber/cc-skills-golang@golang-lint 技能。

Go 1.25+ 反射类型断言

对于反射代码,优先使用 reflect.TypeAssert[T],而不是 value.Interface().(T)。

v := reflect.ValueOf(x)
if s, ok := reflect.TypeAssert[string](v); ok {
    use(s)
}

常见错误

错误修复方法
裸类型断言 v := x.(T)类型不匹配时会发生 panic,导致程序崩溃。使用 v, ok := x.(T) 以便妥善处理
在接口函数中返回有类型的 nil接口持有 (type, nil),因此 != nil。nil 情况下应返回无类型的 nil
向 nil 映射写入nil 映射没有底层存储空间,写入会触发 panic。使用 make(map[K]V) 初始化,或按需初始化
假定 append 总会复制如果容量足够,两个切片会共享底层数组。使用 s[:len(s):len(s)] 强制复制
在循环中使用 deferdefer 会在函数退出时运行,而不是在每次循环迭代时运行,因此资源会不断累积。将循环体提取到单独的函数中
未检查范围就将 int64 转为 int32数值会静默回绕(3B → -1.29B)。先检查是否超出 math.MaxInt32/math.MinInt32
使用 == 比较浮点数IEEE 754 表示并不精确(0.1+0.2 != 0.3)。使用 math.Abs(a-b) < epsilon
整数除法未检查除数是否为零除数为零时,整数除法会触发 panic。除法前使用 if divisor == 0 进行防护
返回内部切片/映射引用调用方可以通过共享的底层数组修改结构体内部数据。返回防御性副本
多个 init() 依赖执行顺序不同文件中的 init() 执行顺序未指定。→ 请参阅 samber/cc-skills-golang@golang-design-patterns,使用显式构造函数
在 nil 通道上永久阻塞nil 通道在发送和接收时都会阻塞。使用前务必初始化

交叉引用

  • → 关于并发访问模式和同步原语,请参阅 samber/cc-skills-golang@golang-concurrency 技能
  • → 关于切片/映射内部机制、容量增长和 container/ 包,请参阅 samber/cc-skills-golang@golang-data-structures 技能
  • → 关于 nil 错误接口陷阱,请参阅 samber/cc-skills-golang@golang-error-handling 技能
  • → 关于与安全相关的安全问题(内存安全、整数溢出),请参阅 samber/cc-skills-golang@golang-security 技能
  • → 关于调试 panic 和竞态条件,请参阅 samber/cc-skills-golang@golang-troubleshooting 技能
  • → 关于在 CI 中使用这些指南进行自动化 AI 驱动的代码审查,请参阅 samber/cc-skills-golang@golang-continuous-integration 技能

评论

支持 Markdown