go-defensive
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Defensive Programming Patterns
Go语言防御式编程模式
Verify Interface Compliance
验证接口合规性
Source: Uber Go Style Guide
Verify interface compliance at compile time using zero-value assertions.
Bad
go
type Handler struct{}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}Good
go
type Handler struct{}
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}Use for pointer types, slices, maps; empty struct for value receivers.
nil{}来源:Uber Go 风格指南
使用零值断言在编译时验证接口合规性。
不良示例
go
type Handler struct{}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}良好示例
go
type Handler struct{}
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}指针类型、切片、映射使用;值接收器使用空结构体。
nil{}Copy Slices and Maps at Boundaries
边界处复制切片和映射
Source: Uber Go Style Guide
Slices and maps contain pointers. Copy at API boundaries to prevent unintended modifications.
来源:Uber Go 风格指南
切片和映射包含指针。在API边界处进行复制,以防止意外修改。
Receiving
接收数据时
Bad
go
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips // caller can still modify d.trips
}Good
go
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}不良示例
go
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips // 调用者仍可修改d.trips
}良好示例
go
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}Returning
返回数据时
Bad
go
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters // exposes internal state!
}Good
go
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}不良示例
go
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters // 暴露内部状态!
}良好示例
go
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}Defer to Clean Up
使用defer进行资源清理
Source: Uber Go Style Guide, Effective Go
Use to clean up resources (files, locks). Avoids missed cleanup on multiple returns.
deferBad
go
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount // easy to miss unlocksGood
go
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.countDefer overhead is negligible. Only avoid in nanosecond-critical paths.
来源:Uber Go 风格指南、Effective Go
使用清理资源(文件、锁)。避免在多返回路径中遗漏资源清理。
defer不良示例
go
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount // 容易忘记解锁良好示例
go
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.countdeferDefer for File Operations
文件操作中使用defer
Place immediately after opening a file for clarity:
defer f.Close()go
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // Close sits near Open - much clearer
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...)
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed
}
}
return string(result), nil // f will be closed
}在打开文件后立即放置,以提升代码清晰度:
defer f.Close()go
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // Close与Open相邻 - 清晰度大幅提升
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...)
if err != nil {
if err == io.EOF {
break
}
return "", err // 文件会被关闭
}
}
return string(result), nil // 文件会被关闭
}Defer Argument Evaluation
defer的参数求值时机
Arguments to deferred functions are evaluated when executes, not when the
deferred function runs:
defergo
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// Prints: 4 3 2 1 0 (LIFO order, values captured at defer time)延迟函数的参数在执行时求值,而非延迟函数运行时:
defergo
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// 输出:4 3 2 1 0(后进先出顺序,值在defer时捕获)Defer LIFO Order
defer的后进先出顺序
Multiple defers execute in Last-In-First-Out order:
go
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a")) // trace() runs now, un() runs at return
fmt.Println("in a")
}
// Output: entering: a, in a, leaving: a多个defer语句按照后进先出的顺序执行:
go
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a")) // trace()立即执行,un()在返回时执行
fmt.Println("in a")
}
// 输出:entering: a, in a, leaving: aStart Enums at One
枚举从1开始
Source: Uber Go Style Guide
Start enums at non-zero to distinguish uninitialized from valid values.
Bad
go
const (
Add Operation = iota // Add=0, zero value looks valid
Subtract
Multiply
)Good
go
const (
Add Operation = iota + 1 // Add=1, zero value = uninitialized
Subtract
Multiply
)Exception: When zero is the sensible default (e.g., ).
LogToStdout = iota来源:Uber Go 风格指南
枚举从非零值开始,以区分未初始化值与有效取值。
不良示例
go
const (
Add Operation = iota // Add=0,零值看起来像有效取值
Subtract
Multiply
)良好示例
go
const (
Add Operation = iota + 1 // Add=1,零值代表未初始化
Subtract
Multiply
)例外情况:当零值是合理默认值时(例如)。
LogToStdout = iotaUse time.Time and time.Duration
使用time.Time和time.Duration
Source: Uber Go Style Guide
Always use the package. Avoid raw for time values.
timeint来源:Uber Go 风格指南
始终使用包。避免使用原始类型表示时间值。
timeintInstants
时间点
Bad
go
func isActive(now, start, stop int) bool {
return start <= now && now < stop
}Good
go
func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}不良示例
go
func isActive(now, start, stop int) bool {
return start <= now && now < stop
}良好示例
go
func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}Durations
时长
Bad
go
func poll(delay int) {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
poll(10) // seconds? milliseconds?Good
go
func poll(delay time.Duration) {
time.Sleep(delay)
}
poll(10 * time.Second)不良示例
go
func poll(delay int) {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
poll(10) // 是秒?还是毫秒?良好示例
go
func poll(delay time.Duration) {
time.Sleep(delay)
}
poll(10 * time.Second)JSON Fields
JSON字段
When isn't possible, include unit in field name:
time.DurationBad
go
type Config struct {
Interval int `json:"interval"`
}Good
go
type Config struct {
IntervalMillis int `json:"intervalMillis"`
}当无法使用时,在字段名中包含单位:
time.Duration不良示例
go
type Config struct {
Interval int `json:"interval"`
}良好示例
go
type Config struct {
IntervalMillis int `json:"intervalMillis"`
}Avoid Mutable Globals
避免可变全局变量
Source: Uber Go Style Guide
Use dependency injection instead of mutable globals.
Bad
go
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
}
// Test requires save/restore of global
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time { return someFixedTime }
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
}Good
go
type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{now: time.Now}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
}
// Test injects dependency cleanly
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time { return someFixedTime }
assert.Equal(t, want, s.Sign(give))
}来源:Uber Go 风格指南
使用依赖注入替代可变全局变量。
不良示例
go
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
}
// 测试需要保存/恢复全局变量
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time { return someFixedTime }
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
}良好示例
go
type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{now: time.Now}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
}
// 测试可以干净地注入依赖
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time { return someFixedTime }
assert.Equal(t, want, s.Sign(give))
}Avoid Embedding Types in Public Structs
避免在公开结构体中嵌入类型
Source: Uber Go Style Guide
Embedded types leak implementation details and inhibit type evolution.
Bad
go
type ConcreteList struct {
*AbstractList
}Good
go
type ConcreteList struct {
list *AbstractList
}
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
}Embedding problems:
- Adding methods to embedded interface is a breaking change
- Removing methods from embedded struct is a breaking change
- Replacing the embedded type is a breaking change
来源:Uber Go 风格指南
嵌入类型会暴露实现细节,阻碍类型演进。
不良示例
go
type ConcreteList struct {
*AbstractList
}良好示例
go
type ConcreteList struct {
list *AbstractList
}
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
}嵌入类型的问题:
- 为嵌入接口添加方法属于破坏性变更
- 从嵌入结构体中移除方法属于破坏性变更
- 替换嵌入类型属于破坏性变更
Use Field Tags in Marshaled Structs
在序列化结构体中使用字段标签
Source: Uber Go Style Guide
Always use explicit field tags for JSON, YAML, etc.
Bad
go
type Stock struct {
Price int
Name string
}Good
go
type Stock struct {
Price int `json:"price"`
Name string `json:"name"`
// Safe to rename Name to Symbol
}Tags make the serialization contract explicit and safe to refactor.
来源:Uber Go 风格指南
始终为JSON、YAML等序列化格式使用显式字段标签。
不良示例
go
type Stock struct {
Price int
Name string
}良好示例
go
type Stock struct {
Price int `json:"price"`
Name string `json:"name"`
// 可以安全地将Name重命名为Symbol
}标签使序列化契约显式化,且在重构时更安全。
Crypto Rand
加密随机数
Source: Go Wiki CodeReviewComments (Normative)
Do not use or to generate keys, even throwaway ones. This is a security concern.
math/randmath/rand/v2Unseeded or time-seeded random generators have predictable output:
- provides only a few bits of entropy
Time.Nanoseconds() - Keys generated this way can be guessed by attackers
Use instead:
crypto/randgo
import (
"crypto/rand"
)
func Key() string {
return rand.Text()
}For text output:
- Use directly (preferred)
crypto/rand.Text - Or encode random bytes with or
encoding/hexencoding/base64
来源:Go Wiki CodeReviewComments(规范性内容)
不要使用或生成密钥,即使是临时密钥也不行。这是一个安全问题。
math/randmath/rand/v2未初始化种子或基于时间种子的随机生成器输出可预测:
- 仅提供少量熵
Time.Nanoseconds() - 以此方式生成的密钥可能被攻击者猜测
请使用替代:
crypto/randgo
import (
"crypto/rand"
)
func Key() string {
return rand.Text()
}对于文本输出:
- 直接使用(推荐)
crypto/rand.Text - 或者使用或
encoding/hex编码随机字节encoding/base64
Panic and Recover
Panic与Recover
Source: Effective Go
Use only for truly unrecoverable situations. Library functions should avoid panic—if the problem can be worked around, let things continue rather than taking down the whole program.
panicUse to regain control of a panicking goroutine (only works inside deferred functions):
recovergo
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}Key rules:
- Never expose panics across package boundaries—always convert to errors
- Acceptable to panic in if a library truly cannot set itself up
init() - Use recover to isolate panics in server goroutine handlers
For detailed patterns including server protection and package-internal panic/recover, see references/PANIC-RECOVER.md.
来源:Effective Go
仅在真正无法恢复的情况下使用。库函数应避免panic——如果问题可以解决,应让程序继续运行而非终止整个程序。
panic使用重新控制发生panic的goroutine(仅在延迟函数中有效):
recovergo
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}核心规则:
- 永远不要跨包边界暴露panic——始终将其转换为错误
- 如果库确实无法完成初始化,在中panic是可接受的
init() - 使用recover隔离服务器goroutine处理器中的panic
有关包括服务器保护和包内panic/recover在内的详细模式,请参阅references/PANIC-RECOVER.md。
Quick Reference
快速参考
| Pattern | Rule |
|---|---|
| Interface compliance | |
| Receiving slices/maps | Copy before storing |
| Returning slices/maps | Return a copy |
| Resource cleanup | Use |
| Defer argument timing | Evaluated at defer, not call time |
| Enums | Start at |
| Time instants | Use |
| Time durations | Use |
| Mutable globals | Use dependency injection |
| Type embedding | Use explicit delegation |
| Serialization | Always use field tags |
| Key generation | Use |
| Panic usage | Only for truly unrecoverable situations |
| Recover pattern | Use in defer; convert to error at API boundary |
| 模式 | 规则 |
|---|---|
| 接口合规性 | |
| 接收切片/映射 | 存储前先复制 |
| 返回切片/映射 | 返回副本 |
| 资源清理 | 使用 |
| Defer参数时机 | 在defer时求值,而非调用时 |
| 枚举 | 从 |
| 时间点 | 使用 |
| 时长 | 使用 |
| 可变全局变量 | 使用依赖注入 |
| 类型嵌入 | 使用显式委托 |
| 序列化 | 始终使用字段标签 |
| 密钥生成 | 使用 |
| Panic使用 | 仅用于真正无法恢复的情况 |
| Recover模式 | 在defer中使用;在API边界转换为错误 |
See Also
另请参阅
- - Core Go style principles
go-style-core - - Goroutine and channel patterns
go-concurrency - - Error handling best practices
go-error-handling
- - Go语言核心风格原则
go-style-core - - Goroutine与通道模式
go-concurrency - - 错误处理最佳实践
go-error-handling