go中函数式重构的核心是用高阶函数抽离横切逻辑,统一签名func(context.context, interface{}) (interface{}, error),按场景选函数或结构体包装,严格遵循执行顺序与职责边界。

Go 里没有装饰器语法,也不靠 interface 堆砌抽象,真正能落地的函数式重构,是用高阶函数把横切逻辑抽成可组合、可测试、不污染核心业务的小单元。
用 func(context.Context, interface{}) (interface{}, error) 统一处理签名
这是所有链式增强的前提。签名必须包含 context.Context——否则超时、取消、值传递全失效;返回 error 必须原样或用 %w 包装,不能吞掉。
- 不要为每个业务写不同签名,比如
func(*User) error和func(*Order) error并列存在,它们无法被同一个装饰器复用 - 如果参数类型差异大,用
interface{}+ 类型断言(或更推荐:定义窄接口如type Validatable interface{ Validate() error }) - 避免在闭包里捕获大对象(如整个 service 实例),容易引发内存泄漏或隐式依赖
按场景选高阶函数还是结构体包装
不是所有“增强”都适合用函数。关键看是否需要持状态、多配置或嵌套生命周期管理。
- 日志、指标、超时、重试——用高阶函数,例如
WithLogging、WithTimeout(5*time.Second),轻量、无状态、易组合 - HTTP 中间件、gRPC 拦截器、事务控制——用结构体包装,因为要持
*sql.Tx、http.ResponseWriter等上下文资源,且需显式defer或Close - 权限校验这类强语义逻辑,必须在调用前检查,失败立即返回;它不适合纯函数链,而应作为独立前置步骤,或封装进结构体的
Handle方法中
避免装饰器链执行顺序反直觉
链式调用 WithMetrics(WithTimeout(...)) 的执行顺序是:最外层最先执行、最内层最先返回。这和阅读顺序相反,容易踩坑。
-
WithTimeout应该包在WithMetrics外层,否则指标上报可能因超时中断而缺失 - 日志装饰器若放在最内层,就看不到超时或重试过程;放在最外层,又可能记录不到内部 panic 的堆栈
- 建议固定模板:
WithRecovery(WithLogging(WithTimeout(...))),recover 最外,timeout 次外,logging 居中,业务逻辑最内
校验与转换必须提前剥离,不塞进 handler
别让 HandlerFunc 承担协议解析、字段校验、时间格式转换三重职责。这不是函数式,是混乱。
- 为每个 API 定义专属 DTO,带
Validate() error方法,失败直接返回400,不进入后续装饰器链 - DTO 转 domain 对象的逻辑,单独抽成
FromDTO(*CreateUserRequest) (*User, error),不在装饰器里做 - 错误统一用
errors.Is(err, ErrInvalidInput)判断,而不是字符串匹配;装饰器只管流程,不管业务语义
真正难的不是写出 WithXXX 函数,而是判断哪部分该进链、哪部分该提前拦截、哪部分根本不该出现在链里——边界模糊的地方,往往就是业务语义没想清楚的地方。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











