关于go:每天学点-Go-规范-context-类型的-key-有什么讲究

34次阅读

共计 2636 个字符,预计需要花费 7 分钟才能阅读完成。

当初团队里简直所有的代码都须要通过 Code Review(代码审查)之后,才容许合入主分支。作为 CR 负责人之一,在 CR 中看到了不少不适宜的问题,也看到了不少值得学习的点。笔者决定从明天开始,一点一滴地记录这些做法、教训、教训,以飨读者。

这系列文章,我都会先抛出一句话标准阐明,而后解释问题的背景,最初再给出正确的标准。

一句话标准

  • 当应用 context.Context 类型保留 KV 对时, key 不能应用原生类型,而应该应用派生类型

问题背景

咱们晓得,能够利用 context.Context 类型来存一些自定义的键值对——当然了,须要保留新的 context 对象:

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

很快就能够留神到 key 的参数类型是 any,也就是 Go 1.17 之前的 interface{}。“可能是为了可能应用 int 吧?”——初学者很可能会这么想。

在理论的 CR 中,能够看到很多人都应用 string 类型作为 key,比方这是一个十分典型的例子:

ctx = context.WithValue(ctx, "openid", userOpenID)

存在问题

如果你应用了 VSCode,并且一键装置了 Go 开发工具包,那么 VSCode 大概率也装置了 golangci-lint 工具。此时,下面的这段代码会喜提一个 warning:

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

告警信息尽管是英文,但很容易了解:

  • 不应该应用内建类型作为 KV 中 key 的类型,而应该应用自定义的类型来防止抵触。

为什么要这么做呢?很简略,古代软件都是团队开发的,多模块互相耦合,相互合作。在一个 ctx 对象的整个生命周期中,它须要通过多个逻辑 / 模块的洗礼,每一个模块都可能应用 ctx 来存储相应信息。

假如 user 模块,它应用 ctx 类型缓存了用户的 openid 字段。这个逻辑没什么问题。而后这个 ctx(和代码逻辑)持续往后走,大家约定,就应用这个 "openid" 来存储。

有一天,来了一个紧急需要,比如说要做一个群聊性能,尽可能复用老代码缩小开发。或者 group 模块就利用了 user 模块的代码。好巧不巧,从其余团队过去声援的开发同学,也应用了 "openid" 这个 key,来存储群主的 openid。后果绕了一圈,这位同学发现:咦怎么这群主的 openid 老变成他人的 openid?搁这群主轮流做是吧?

解决办法

可能有人感觉:那我把 key 的定义对立收集起来规定不就行了?解决一个问题总有上中下策,这是个方法,然而一个下下下策。软件工程主打一个分而治之,在没有必要的状况下,尽可能防止集中式的治理。

最上策的解决办法简略而言,就是应用自定义的类型作为 key 的类型。咱们看看上面的代码:

type myString string

func main() {ctx := context.Background()
    ctx = context.WithValue(ctx, "openid", "不是群主")
    ctx = context.WithValue(ctx, myString("openid"), "群主")

    fmt.Println(ctx.Value("openid"))
    fmt.Println(ctx.Value(myString("openid")))
}

两行输入:

 不是群主
群主 

这就十分清晰了,只管底层类型雷同(都是 string 类型),然而通过 type 定义之后,Go 是作为齐全不同的 key 来解决的。针对具体类型自定义 key 类型之后,很好地解决了同名 key 抵触的问题。

不过呢,如果你并不需要应用同一个 key 类型,存储多个不同 value,那么下面的模式还只能说是中策,真正的上策是这么做的:

type myString struct{}

func main() {ctx := context.Background()
    ctx = context.WithValue(ctx, "openid", "不是群主")
    ctx = context.WithValue(ctx, myString{}, "群主")

    fmt.Println(ctx.Value("openid"))
    fmt.Println(ctx.Value(myString{}))
}

我还记得我当年第一次看到这个模式的时候,我就是上图的这个表情。是的,struct{} 类型也能够作为 KV 的 key 类型——当然了,也应该定义为自定义类型。

应用 struct{} 的益处可是大大的多:首先,这个类型在 Go 中原则上是不占内存空间和 gc 开销的,能够晋升性能;其次,这少了开发者额定“写一个 key”的工夫(类型往往能够通过 IDE 疾速补全),大大提高了敲代码的速度呀。

典型例子

应用 context WithValue 办法,有一个很典型的例子,就是在 ctx 中存入一个 trace ID,用于跟踪整个调用链。那么,咱们能够包装一个 traceid 包,比拟标准的写法是这样的:

// Package traceid 用于在 context 中保护 trace ID
package traceid

import "context"

// 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
}

type traceIDKey struct{}

本文章采纳 常识共享署名 - 非商业性应用 - 雷同形式共享 4.0 国内许可协定 进行许可。

原作者:amc,原文公布于腾讯云开发者社区,也是自己的博客。欢送转载,但请注明出处。

原作者:amc,欢送转载,但请注明出处。

原文题目:《每天学点 Go 标准 – context 类型的 key 有什么考究?》

公布日期:2023-08-16

原文链接:https://cloud.tencent.com/developer/article/2313242。

正文完
 0