Golang 神器!go-decorator 一行注释搞定装饰器,v0.22版本发布

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

相关推荐
桃园码工15 小时前
1-Gin介绍与环境搭建 --[Gin 框架入门精讲与实战案例]
go·gin·环境搭建
苏三有春1 天前
五分钟学会如何在GitHub上自动化部署个人博客(hugo框架 + stack主题)
git·go·github
我是前端小学生2 天前
Go语言中的方法和函数
go
探索云原生2 天前
在 K8S 中创建 Pod 是如何使用到 GPU 的: nvidia device plugin 源码分析
ai·云原生·kubernetes·go·gpu
自在的LEE2 天前
当 Go 遇上 Windows:15.625ms 的时间更新困局
后端·kubernetes·go
Gvto3 天前
使用FakeSMTP创建本地SMTP服务器接收邮件具体实现。
go·smtp·mailtrap
catmes3 天前
使用docker compose安装gitlab
运维·docker·容器·gitlab·敏捷开发·devops
白泽来了3 天前
【Go进阶】手写 Go websocket 库(一)|WebSocket 通信协议
开源·go
witton4 天前
将VSCode配置成Goland的视觉效果
ide·vscode·编辑器·go·字体·c/c++·goland