关于 golang-safety
golang-safety 是面向 Go 的正确性与防御式编程技能,适用于常规应用代码。它通过处理 nil 值、集合行为、数值范围和资源管理,帮助工程师避免程序崩溃与静默数据损坏。
主要功能
- 01nil 与接口安全
- 02切片和映射使用指导
- 03数值转换与浮点数检查
- 04资源生命周期和 defer 指导
- 05防御性复制与安全初始化
适用场景
- 01检查代码中的 nil 相关 panic
- 02排查数值转换导致的溢出
- 03避免内部集合被意外修改
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-safety安装后在 Claude Code 中输入/golang-safety确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/nil-safety.mdslice-map-safety.md详细介绍
角色设定: 你是一名防御型 Go 工程师。你会将所有未经测试、与 nil、容量和数值范围有关的假设都视为潜在的崩溃隐患。
Go 安全性:正确性与防御式编码
防止程序员失误——正常(非对抗性)代码中的错误、panic 和静默数据损坏。安全性应对攻击者;安全性也应对我们自己。
最佳实践摘要
- 在类型集合已知时优先使用泛型,而非
any——编译器可以捕获类型不匹配,而不是等到运行时发生 panic - 始终安全地进行类型断言——对于普通接口,使用逗号 ok 惯用法(
v, ok := x.(T));对于 Go 1.25+ 中的反射,优先使用reflect.TypeAssert[T](value),而不是value.Interface().(T)。 - 接口中的类型化 nil 指针不等于
nil——类型描述符会使其变为非 nil - 向 nil map 写入会 panic——使用前务必初始化
append可能会复用底层数组——如果容量允许,两个切片会共享内存,并在不知不觉中相互破坏数据- 从导出函数返回防御性副本——否则调用方可以修改你的内部数据
defer在函数退出时运行,而非每次循环迭代时运行——将循环体提取为函数- 整数转换会静默截断——
int64转为int32会发生回绕,且不会报错 - 浮点运算并不精确——使用 epsilon 比较或
math/big - 设计实用的零值——nil map 字段首次写入时会 panic;使用延迟初始化
- 使用
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 | 零值 | panic | 0 | 迭代 0 次 |
| 切片 | panic | panic | 0 | 迭代 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)] 强制复制 |
在循环中使用 defer | defer 会在函数退出时运行,而不是在每次循环迭代时运行,因此资源会不断累积。将循环体提取到单独的函数中 |
未检查范围就将 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技能