mcpField
G
go-defensive

go-defensive

github.com/cxuu开发1861.2k 次安装19 周前更新
代码仓库

关于 go-defensive

go-defensive 汇总 Go API 边界处的防御式编程实践,面向希望降低数据别名、资源泄漏和错误处理风险的开发者。指南涉及复制切片与映射、清理资源、枚举、随机数及 panic 处理。

主要功能

  1. 01在 API 边界复制切片与映射
  2. 02使用 defer 清理资源
  3. 03避免可变全局状态
  4. 04建议枚举从非零值开始
  5. 05区分安全随机数与普通随机数
  6. 06限制 panic 并规范 recover

适用场景

  1. 01审查 API 边界的数据安全
  2. 02检查文件、锁与取消操作的清理
  3. 03评估枚举、全局状态和 panic 用法

安装

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

$npx skills add https://github.com/cxuu/golang-skills --skill go-defensive

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

包含的文件

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

references/
BOUNDARY-COPYING.md
GLOBAL-STATE.md
MUST-FUNCTIONS.md
PANIC-RECOVER.md
TIME-ENUMS-TAGS.md

详细介绍

Go 防御式编程模式

兼容性:加密示例可能使用 crypto/rand.Text,这需要 Go 1.24+。

资源路由

  • references/BOUNDARY-COPYING.md - 跨 API 边界复制切片/映射时阅读。
  • references/GLOBAL-STATE.md - 引入或移除包级全局变量时阅读。
  • references/MUST-FUNCTIONS.md - 判断是否可以接受遇错即 panic 的辅助函数时阅读。
  • references/PANIC-RECOVER.md - 评估 panic、recover 或崩溃隔离时阅读。
  • references/TIME-ENUMS-TAGS.md - 处理时间类型、枚举零值或结构体标签时阅读。

防御式检查清单优先级

在 API 边界加固代码时,按以下顺序检查:

Reviewing an API boundary?
├─ 1. Error handling     → Return errors; don't panic (see go-error-handling)
├─ 2. Input validation   → Copy slices/maps received from callers
├─ 3. Output safety      → Copy slices/maps before returning to callers
├─ 4. Resource cleanup   → Use defer for Close/Unlock/Cancel
├─ 5. Interface checks   → Route compile-time assertions to go-interfaces
├─ 6. Time correctness   → Use time.Time and time.Duration, not int/float
├─ 7. Enum safety        → Start iota at 1 so zero-value is invalid
└─ 8. Crypto safety      → crypto/rand for keys, never math/rand

快速参考

模式规则详情
边界复制接收和返回时复制切片/映射BOUNDARY-COPYING.md
延迟清理在 os.Open 后立即使用 defer f.Close()见下文
接口检查编译时接口实现断言参见 go-interfaces
时间类型使用 time.Time / time.Duration,绝不使用原始 intTIME-ENUMS-TAGS.md
枚举起始值使用 iota + 1,让零值表示无效见下文
加密随机数密钥使用 crypto/rand,绝不使用 math/rand见下文
Must 函数仅在初始化时使用;失败时 panicMUST-FUNCTIONS.md
Panic/recover绝不跨包暴露 panicPANIC-RECOVER.md
可变全局变量改用依赖注入见下文

验证接口实现

将编译时接口实现断言交由 go-interfaces 处理。 仅使用此技能来识别 API 边界的健壮性风险;何时适合使用断言以及断言的具体形式由接口技能负责。

在边界处复制切片和映射

切片和映射包含指向底层数据的指针。在 API 边界处进行复制,以防止意外修改。

// Receiving: copy incoming slice
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)

// Returning: copy map before returning
result := make(map[string]int, len(s.counters))
for k, v := range s.counters { result[k] = v }

使用 Defer 进行清理

使用 defer 清理资源(文件、锁)。这样可以避免在多个返回路径中遗漏清理。

p.Lock()
defer p.Unlock()

if p.count < 10 {
  return p.count
}
p.count++
return p.count

defer 的开销可以忽略不计。为清晰起见,在 os.Open 后立即放置 defer f.Close()。延迟函数的参数会在执行 defer 时求值,而不是在 函数运行时求值。多个 defer 按后进先出顺序执行。

结构体字段标签

建议:始终为需要编组或解组的结构体添加显式字段标签。

type User struct {
    Name  string `json:"name"  yaml:"name"`
    Email string `json:"email" yaml:"email"`
}

字段标签是序列化契约——重命名结构体字段却不更新标签,会悄无声息地破坏线路兼容性。对于任何跨越序列化边界的类型,都应将标签视为公共 API 的一部分。

将枚举起始值设为一

将枚举起始值设为非零,以区分未初始化值和有效值。

const (
  Add Operation = iota + 1  // Add=1, zero value = uninitialized
  Subtract
  Multiply
)

例外:零值是合理默认值时(例如,LogToStdout = iota)。

时间、结构体标签和嵌入

避免可变全局变量

注入依赖,而不是修改包级变量。这样无需保存和恢复全局状态也能测试代码。

type signer struct {
  now func() time.Time  // injected; tests replace with fixed time
}

func newSigner() *signer {
  return &signer{now: time.Now}
}

加密随机数

生成密钥时不要使用 math/rand 或 math/rand/v2——这是一个安全问题。以时间为种子的生成器会产生可预测的输出。

import "crypto/rand"

func Key() string { return rand.Text() }

对于文本输出,直接使用 crypto/rand.Text,或使用 encoding/hex 或 encoding/base64 编码随机字节。


Panic 和 Recover

仅在确实无法恢复的情况下使用 panic。库函数应避免使用 panic。

func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(work)
}

关键规则:

  • 绝不跨包暴露 panic——始终将其转换为错误
  • 如果库确实无法完成自身初始化,可以在 init() 中 panic
  • 使用 recover 在服务器 goroutine 处理程序中隔离 panic

Must 函数

Must 函数会在出错时 panic——仅在程序初始化期间使用,此时失败意味着程序无法运行。

var validID = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)
var tmpl = template.Must(template.ParseFiles("index.html"))

相关技能

  • 错误处理:在选择返回错误还是触发 panic,或在边界处包装错误时,请参阅 go-error-handling
  • 并发安全:在使用互斥锁、原子操作或通道保护共享状态时,请参阅 go-concurrency
  • 接口检查:在添加编译时接口实现检查时,请参阅 go-interfaces
  • 数据结构复制:在处理切片/映射内部结构或指针别名时,请参阅 go-data-structures

评论

支持 Markdown