《Go小技巧易错点100例》第三十三篇
Validator自定义校验规则
Go语言中广泛使用的validator
库支持通过结构体标签定义校验规则。当内置规则无法满足需求时,我们可以轻松扩展自定义校验逻辑。
示例场景:验证用户年龄是否成年(≥18岁)
type User struct {Age int`validate:"adult"`
}func TestValidator(t *testing.T) {// 注册自定义校验规则validate := validator.New()_ = validate.RegisterValidation("adult", func(fl validator.FieldLevel) bool {return fl.Field().Int() >= 18})// 验证通过案例user := User{Age: 10}err := validate.Struct(user)if err != nil {fmt.Println(err)}
}
运行后会输出:
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'adult' tag
实现要点:
1.使用RegisterValidation
注册自定义校验标签
2.通过Field().Int()
获取字段值
3.返回布尔值表示校验结果
sync.Pool优化对象复用
对于频繁创建/销毁的对象,使用sync.Pool
可以显著降低GC压力,提升性能。
JSON编码优化示例:
var bufferPool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) },
}func EncodeJSON(v interface{}) ([]byte, error) {buf := bufferPool.Get().(*bytes.Buffer)defer bufferPool.Put(buf)buf.Reset() // 关键:清空旧数据err := json.NewEncoder(buf).Encode(v)return buf.Bytes(), err
}
使用注意:
- 取出对象后必须调用Reset()
- 对象生命周期不受池管理
- 适合重量级对象的复用
sync.WaitGroup的正确使用
错误地在goroutine内部调用Add()会导致竞态条件:
// 危险用法
for i := 0; i < 10; i++ {go func() {wg.Add(1) // 并发写入导致竞态defer wg.Done()}()
}
正确做法:
// 安全用法
for i := 0; i < 10; i++ {wg.Add(1) // 在goroutine外部提前Addgo func() {defer wg.Done()}()
}
wg.Wait()
关键原则:
1.Add()调用必须在goroutine外部
2.Done()与Add()次数严格匹配
3.Wait()要在所有Add()完成后调用
nil的语义陷阱
不同类型的nil表现
var (p *int // nil pointers []int // nil slicem map[int]int // nil mapf func() // nil functioni interface{} // nil interface
)
危险的nil比较
var p *int = nil
fmt.Println(p == nil) // true
fmt.Println(i == p) // false!
原理剖析:
- 接口变量包含
(type, value)
二元组 - 当存储nil指针时,类型信息仍然存在
- 与纯nil接口比较时会返回false
安全实践:
1.使用明确的类型断言检查
2.避免直接比较接口与具体类型指针
3.使用反射进行深度nil检查