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

相关推荐
却尘25 分钟前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 小时前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt1114 小时前
AI DDD重构实践
go
Grassto2 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto4 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室5 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题5 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉7 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想
asaotomo7 天前
一款 AI 驱动的新一代安全运维代理 —— DeepSentry(深哨)
运维·人工智能·安全·ai·go
翰德恩咨询8 天前
敏捷咨询实战:如何让DevOps从理念到高效落地
敏捷开发·devops