go-decorator
, 让 Go 一行注释就能使用装饰器的工具。装饰器能够切面(Aspect-Oriented Programming,AOP)、代理(Proxy)任意的函数和方法,提供观察和控制函数的能力。
v0.22.0 更新日志:
- 修复:下划线参数导致编译错误的问题。#10
- 添加:Q&A 文档
Feature
- 装饰器应用便捷 :通过简单添加
//go:decor F
注释(F
为装饰器函数)即可使用装饰器,能够快速实现如"样板代码注入、非侵入式函数行为改变、控制逻辑流"等逻辑。 - 装饰器自定义与复用:可以自由定义函数作为装饰器,并应用于任何顶级(Top-Level)函数、方法、结构体。
- 多装饰器支持 :支持使用多个(行)
//go:decor
装饰器修饰函数。 - 类型方法自动装饰 :支持类型声明
type T types
,装饰器会自动装饰代理所有以T
或*T
为接收者的方法。 - 装饰器参数化:装饰器支持可选参数,为开发带来更多可能性。
- 支持编译时验证:支持编译时 lint 验证,确保 Go 编译代码的健壮性,能检测如未定义装饰器或未引用包等问题,并给出错误原因和行号。
- 高性能:仅在编译时增强目标函数,不降低编译程序的性能,无反射操作。
Demo Code
go
package main
import (
"github.com/dengsgo/go-decorator/decor"
"log"
)
func main() {
// 正常调用你的函数。
// 由于这是一个声明使用装饰器logging的函数,
// decorator 编译链会在编译代码时注入装饰器方法logging的调用。
// 所以使用上面的方式编译后运行,你会得到如下输出:
//
// 2023/08/13 20:26:30 decorator function logging in []
// 2023/08/13 20:26:30 this is a function: myFunc
// 2023/08/13 20:26:30 decorator function logging out []
//
// 而不是只有 myFunc 本身的一句输出。
// 也就是说通过装饰器改变了这个方法的行为!
myFunc()
}
// 通过使用 go:decor 注释声明该函数将使用装饰器logging来装饰。
//
//go:decor logging
func myFunc() {
log.Println("this is a function: myFunc")
}
// 这是一个普通的函数
// 它具有 func(*decor.Context [, ...any]) 类型签名,因此它还是一个装饰器方法,
// 可以在其他函数上使用这个装饰器。
// 在函数中,ctx 是装饰器上下文,可以通过 ctx 获取到目标函数的函数名、出入参
// 以及执行目标函数。
func logging(ctx *decor.Context) {
log.Println("decorator function logging in", ctx.TargetIn)
ctx.TargetDo()
log.Println("decorator function logging out", ctx.TargetOut)
}
更多请查阅:中文使用手册
Q&A
我在什么场景下需要用到 go-decorator ?
如果目前 go 标准工具链已经能够满足你的需求,那么就无需考虑引入 go-decorator
。不要因为手里有锤子,就到处找钉子,不要创造需求。
通常情况,只有你的项目到达一定规模后,在此基础上实现一些比较通用的逻辑时,发现通过现有工具链、三方库无法解决,或者要动大量现有逻辑,存在大量重复编码的情况,这时可以考虑使用 go-decorator
。它最大的优点就是允许开发者以非侵入的方式控制函数,记录、改变函数的行为。
比如有如下场景:生产环境有一 Web 程序出现了一些性能问题,但是出现的概率较低,呈现随机偶发性。经过编码CR,怀疑是某些请求的参数导致一些函数响应延迟高导致的。现在要排查这个问题,找出是哪些函数、哪些参数导致的,要如何在不影响线上稳定性的情况下,去 debug 呢?
大致方案:
- 日志方案。把用户请求链路全部或者采样的落下来,采集离线来分析。
- 改代码,埋点监控。在认为有问题的代码里手动插入一些埋点,然后离线来分析。
- 代码里已经有各种 log, 直接线上某个节点开 debug 级别日志,然后离线分析。
- 其他方案...
这些方案或多或少存在一些现实问题:
- 风险。为了解决 bug 要做代码修改,尤其是大量修改现有代码逻辑,到处埋点,这不仅严重破坏了函数单一职能原则,加剧代码理解的成本,还对其他比如单测、高敏感逻辑等造成不可预测的风险。
- 成本。代码修改成本,日志储存成本,投入的人力成本等等。
- 复用性。事实上,这种问题一般都是 case by case, 很难说抽象出通用的模型来处理。一般的做法也就是继续完善日志系统、加更多埋点上报等,尽量采集更多的信息应对以后可能出现的问题。
但是现在 go-decorator
为你提供了新的思路,使用它来处理此类问题就很方便。处理流程为:
一、编写函数 LogFuncInfo,记录出入参和耗时
伪代码:
go
func LogFuncInfo(elapsed int, ctx *decor.Context) {
in = getInArgsMsg(ctx.TargetIn)
out = getOutArgsMsg(ctx.TargetOut)
msg = format("elapsed:%s, funcName:%s, In: %s, Out: %s", elapsed, ctx.TargetName, in, out)
Log.send(msg)
}
二、编写装饰器函数 RecordSlowFunc, 记录慢函数
伪代码:
go
func RecordSlowFunc(ctx *decor.Context) {
startTime = now()
ctx.TargetDo() // 执行原函数(目标函数)
elapsed = now() - startTime // 耗时
const slowTime = getSlowTimeConfig() // ms
if (elapsed >= slowTime) {
LogFuncInfo(elapsed, ctx) // 记录慢日志
}
}
三、给任意函数加上注释,使用装饰器 RecordSlowFunc
伪代码:
go
//go:decor RecordSlowFunc
func maybeSlow(g *Context, state *State, req *Request, other *Other) {
// code ...
}
代码修改结束!
我们不需要改动任何现有代码的逻辑,要做的只是给它们加上一行注释 //go:decor RecordSlowFunc
!
通过 go-decorator
编译后,所有加上此注释的函数都会被自动注入逻辑:当执行时间超过配置的慢函数时长就会被捕获,把此次函数的出入参、函数名、具体耗时上报给日志系统。
因此可以看到,使用 go-decorator
之后。不仅能够精确的采集到想要的数据,还几乎让我们没有心智负担的来修改代码。更重要的是,我们非常自然的抽象出了可复用模型。后面再发生此类问题进行排查,此经验直接复用,对于个人、团队来说,都是宝贵的财富。
风险、成本和可复用性三者有了较优解。
只有这个场景才能用到 go-decorator 吗?
当然不是。上面的例子只是给大家一个初步的认知,可以使用非侵入的方式来处理问题,而不用大动干戈到处改代码逻辑。
理论上,这些场景都非常适合考虑使用 go-decorator
:
- 存在大量重复代码的编写
- 存在大量模板化的代码
- 解决逻辑杂糅问题
- 保持函数职能单一原则,保持简洁不被污染
所以,一些独立的模块,像 日志、缓存、ORM、配置、埋点等,都可以将他们包装成装饰器函数从而给需要的函数使用,这样既天然代码解耦合,还能隐藏实现细节,专注于逻辑本身。
总之,要权衡利弊,在适合自己的场景考虑。
go-decorator 是 Go 官方库吗?
不是。期望此项目可以推进 Go 官方加入装饰器特性。
go-decorator
是完全开源的,并且基于友好的 MIT License
,任何组织和个人都可获取代码并修改完善。在此也呼吁感兴趣的同学参与其中,贡献力量。
什么时候发布正式版本?
很遗憾,无法提供具体日期。尽管在设计和实现上,go-decorator
一直致力于保持稳定的生产可用性,但由于目前涉及的测试用户样本和反馈不足,我对此仍持谨慎态度,无法在短时间内发布正式版本。一旦条件成熟,将及时调整节奏和发布。
go-decorator 如何保证稳定性?
- 更多的测试用例,尽可能覆盖更多的边界场景;
- 基于 Issue 的用户反馈;
- 外部贡献;
在未收到更多测试样本和反馈之前,go-decorator
将保持 v0 测试版本的迭代。
迭代节奏是怎样的?
当前所有需要的核心特性均已实现。在发布正式版本之前,不再考虑加新特性(除非 Go 上游有重大变更),因此迭代均以修复 BUG 为主,比较依赖 Issue 的反馈。
还会给 go-decorator 加入更多特性吗?
Less is more.
保持克制,尽可能完成最核心的功能和稳定的体验,是当前的重点工作。如果你有新特性的想法或者构思可以通过 Issue 提交反馈。
Repo
Github: github.com/dengsgo/go-...
Issue: Issue