Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > golang context.WithValue

golang中context.WithValue的使用规范问题小结

作者:大口吃饭大口吐

本文主要介绍了golang中context.WithValue的使用规范问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

我们首先来看一个报错

should not use built-in type string as key for value; define your own type to avoid collisions (SA1029)go-staticcheck

代码长这样

image.png

上面这段程序的波浪线报错不是warnning级别的,可以说是编辑器的提示,虽说不影响程序的打包运行,但作为强迫症玩家,属实难以接受

这里是golang针对context.Context 类型的使用定义的规范,下面是一些官方话术的解释

我们看一些案例

我们在项目中,通常利用 context.Context 作为一个生命周期的上下文传递,贯穿全局,经常来存一些自定义的键值对,保存新的 context 对象

ctx = context.WithValue(ctx, someKey, someValue)

WithValue方法标准库的定义为
key 和 val 都是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 来存储相关信息。

以用户模块为例,它可能会使用 ctx 来缓存用户的 openid 字段。这种做法本身是合理的。随后,这个 ctx(以及相应的代码逻辑)继续流转,大家默认使用这个 “openid” 键来存储数据。

然而,当一个紧急需求出现,比如需要快速开发一个群聊功能,并且尽可能地复用现有代码以减少开发工作量时,问题就出现了。可能群聊模块在利用用户模块的代码时,无意中也使用了 “openid” 这个键,这次是用来存储群主的 openid。结果,当代码运行时,支援的开发人员发现了一个奇怪的现象:群主的 openid 似乎在不断地变化,仿佛群主的身份在不断轮换。

处理办法

我们以一种常见的思维方式来处理,大家通常会说对ctx里的 key 里的内容统一规范管理,大家操作ctx时都遵循一套规则, 这的确是一个很不错的办法

但是我现在对统一规范管理,这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)
}

// TraceID 从 context 中提取 trace ID
func TraceID(ctx context.Context) string {
    v := context.Value(ctx, traceIDKey{})
    id, _ := v.(string)
    return id
}

到此这篇关于golang中context.WithValue的使用规范问题小结的文章就介绍到这了,更多相关golang context.WithValue内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文