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

 更新时间:2025年02月17日 10:34:01   作者:大口吃饭大口吐  
本文主要介绍了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时,不应该使用内置类型作为 KV 中 key 的类型,而应该使用自定义的类型来避免冲突。
  • 当使用 context.Context 类型保存 KV 对时, key 不能使用原生类型,而应该使用派生类型

我们看一些案例

我们在项目中,通常利用 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文带你深入探索Golang操作mongodb的方法

    一文带你深入探索Golang操作mongodb的方法

    这篇文章主要为大家详细介绍了Golang操作mongodb的相关知识,包括:初始化项目工程、容器方式安装mongo和调试运行和编译运行,感兴趣的小伙伴可以了解一下
    2023-02-02
  • 基于Golang实现统一加载资源的入口

    基于Golang实现统一加载资源的入口

    当我们需要在 main 函数中做一些初始化的工作,比如初始化日志,初始化配置文件,都需要统一初始化入口函数,所以本文就来编写一个统一加载资源的入口吧
    2023-05-05
  • 教你用go语言实现比特币交易功能(Transaction)

    教你用go语言实现比特币交易功能(Transaction)

    每一笔比特币交易都会创造输出,输出都会被区块链记录下来。给某个人发送比特币,实际上意味着创造新的 UTXO 并注册到那个人的地址,可以为他所用,今天通过本文给大家分享go语言实现比特币交易功能,一起看看吧
    2021-05-05
  • Go Uber静态分析工具NilAway使用初体验

    Go Uber静态分析工具NilAway使用初体验

    这篇文章主要介绍了Go Uber静态分析工具NilAway使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • golang微服务框架基础Gin基本路由使用详解

    golang微服务框架基础Gin基本路由使用详解

    这篇文章主要为大家介绍了golang微服务框架Gin基本路由的使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-11-11
  • go中利用reflect实现json序列化的示例代码

    go中利用reflect实现json序列化的示例代码

    和Java语言一样,Go也实现运行时反射,这为我们提供一种可以在运行时操作任意类型对象的能力,本文给大家介绍了在go中如何利用reflect实现json序列化,需要的朋友可以参考下
    2024-03-03
  • Go语言中一定要知道的切片使用注意事项总结

    Go语言中一定要知道的切片使用注意事项总结

    了解和掌握切片的使用注意事项,可以避免意外的程序行为,所以本文就来和大家深入探讨一下Go语言切片常见的注意事项,希望对大家有所帮助
    2023-06-06
  • 用Go获取短信验证码的示例代码

    用Go获取短信验证码的示例代码

    要用Go获取短信验证码,通常需要连接到一个短信服务提供商的API,并通过该API发送请求来获取验证码,由于不同的短信服务提供商可能具有不同的API和授权方式,我将以一个简单的示例介绍如何使用Go语言来获取短信验证码,需要的朋友可以参考下
    2023-07-07
  • Go语言中Redis缓存与本地内存缓存实战

    Go语言中Redis缓存与本地内存缓存实战

    在现代高并发系统中,缓存技术是提升性能和降低数据库压力的关键手段,本文将为大家介绍一下Redis缓存与本地内存缓存的具体应用,需要的可以了解下
    2025-03-03
  • 使用gopkg.in/yaml.v3 解析YAML数据详解

    使用gopkg.in/yaml.v3 解析YAML数据详解

    这篇文章主要为大家介绍了使用gopkg.in/yaml.v3 解析YAML数据示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09

最新评论