golang中context.WithValue的使用规范问题小结
目录
- 我们首先来看一个报错
- 我们看一些案例
- 潜在问题
- 处理办法
- 进阶例子
我们首先来看一个报错
should not use built-in type string as key for value; define your own type to avoid collisions (SA1029)go-static编程check
代码长这样
上面这段程序的波浪线报错不是warnning
级别的,可以说是编辑器的提示,虽说不影响程序的打包运行,但作为强迫症玩家,属实难以接受
这里是golang针对context.Context 类型的使用定义的规范,下面是一些官方话术的解释
- 使用context.Context时,不应该使用内置类型作为 KV 中 key 的类型,而应该使用自定义的类型来避免冲突。
- 当使用 context.Context 类型保存 KV 对时, key 不能使用原生类型,而应该使用派生类型
我们看一些案例
我们在项目中,通常利用 context.Context 作为一个生命周期的上下文传递,贯穿全局,经常来存一些自定义的键值对,保存新的 context 对象
ctx = context.WithValue(ctx, someKey, someValue)
WithValue
方法标准库的定义为
any
类型
func WithValue(parent Context, key, val any) Context { …… }
该方法的注释有这么一句context keys often have concrete type struct{}
就是建议 key 的类型通常为具体的结构体类型
我们的实际使用中,大多会这么样写
ctx = context.WithValue(ctx, "openid", userOpenID)
潜在问题
现在的项目往往涉及多个包的紧密耦合和团队成员间的协作。在一个 ctx 对象的生命周期中,它需要穿越多个逻辑层或包,每个模块都有可能利用 ctx 来存储相关信息。
以用户模块为例,它可能会使用 ctjsx 来缓存用户的 openid 字段。这种做法本身是合理的。随后,这个 ctx(以及相应的代码逻辑)继续流转,大家默认使用这个 “openid” 键来存储数据。
然而,当一个紧急需求出现,比如需要快速开发一个群聊功能,并且尽可能地复用现有代码以减少开发工作量时,问题就出现了。可能群聊模块在利用用户模块的代码时,无意中也使用了 “openid” 这个键,这次是用来存储群主的 openid。结果,当代码运行时,支援的开发人员发现了一个奇怪的现象:群主的 openid 似乎在不断地变化,仿佛群主的身份在不断轮换。
处理办法
我们以一种常见的思维方式来处理,大家通常会说对ctx
里的 key 里的内容统一规范管理,大家操作ctx
时都遵循一http://www.devze.com套规则, 这的确是一个很不错的办法
但是我现在对统一规范管理,这6个字特别厌恶,动不动就统一管理的,随着岁月的流失谁还会想着去看文档,干点儿啥都去先看文档约束,麻烦
这种局部的工作细节,分而治之,尽可能避免集中式的管理显然更加适用,又不是什么大的模块
我们先来一个小小的对比优化代码
type chatGroup string func main() { ctx := context.Background() ctx = context.WithValue(ctx, "openid", "不是群主") ctx = context.WithValue(ctx, chatGroup("openid"), "群主") fmt.Println(ctx.Value("openid")) fmt.Println(ctx.Value(chatGroup("openid"))) }
输出
不是群主
群主
通过chatGroup
,一眼就能看出来是群聊模块的东西
我们再来简写、优化一点
type chatGroup struct{} func main() { ctx := context.Background() ctx = context.WithValue(ctx, "openid", "不是群主") ctx = context.WithValue(ctx, chatGroup{}, "群主") fmt.Println(ctx.Value("openid")) fmt.Println(ctx.Value(chatGroup{})) }
struct{}
类型(准确来说空结构体是已初始化的值)也可以作为 KV 的 key 类型,当然了,也应该定义为自定义类型。
使用 struct{}
的好处是,这个类型在 Go 中原则上是不占内存空间和 gc 开销的,可以提升性能
进阶例子
封装一个ctx 引入trace ID
的案例
// traceid包 用于在 context 中维护 trace ID package traceid import "context" type traceIDKey struct{} // WithTraceID 往 context 中存入 trace ID func WithTraceID(ctx context.Context, traceID string) context.Context { return context.WithValue(ctx, traceIDKey{}, traceID) } // TraceIDpython 从 context 中提取 trace ID func TraceID(ctx context.Context) string { v := context.Value(ctx, traceIDKey{}) id, _ := v.(string) return id }
到此这篇关于golang中conpythontext.WithValue的使用规范问题小结的文章就介绍到这了,更多相关golang context.WithValue内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论