TG bot里面存了不少优化消息,有时间可以处理一下
GitHub这个项目应该实现了类似的功能:https://github.com/jacexh/golang-ddd-template/blob/37c8237d6fa1aac14ee15d5de0e9a9a661b2a0b7/pkg/infection/infection.go#L41
gopackage v2
import (
"context"
"sync"
)
type k int
var contextKey = k(0)
// v1 绑定的 map 换成绑定的 valuesMap
type valuesMap struct {
m map[string][]interface{}
parent context.Context
mu sync.RWMutex
}
func newValuesMap(parent context.Context) *valuesMap {
return &valuesMap{
m: make(map[string][]interface{}),
parent: parent,
}
}
func Store(ctx context.Context, key string, value interface{}) context.Context {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
vm = newValuesMap(ctx)
ctx = context.WithValue(ctx, contextKey, vm)
}
vm.mu.Lock()
defer vm.mu.Unlock()
vm.m[key] = append(vm.m[key], value)
return ctx
}
func StoreSingleValue(ctx context.Context, key string, value interface{}) context.Context {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
vm = newValuesMap(ctx)
ctx = context.WithValue(ctx, contextKey, vm)
}
vm.mu.Lock()
defer vm.mu.Unlock()
vm.m[key] = []interface{}{value}
return ctx
}
func Load(ctx context.Context, key string) interface{} {
values := LoadAll(ctx, key)
// values 里面的首值就是 key 距离最近的节点 value
if len(values) > 0 {
return values[0]
}
return nil
}
func LoadAll(ctx context.Context, key string) []interface{} {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
return nil
}
vm.mu.RLock()
values, ok := vm.m[key]
vm.mu.RUnlock()
if ok && len(values) > 0 {
return values
}
// 递归遍历父 valuesMap
if vm.parent != nil && ctx != vm.parent {
return LoadAll(vm.parent, key)
}
return nil
}
gopackage v2
import (
"context"
"sync"
)
type k int
var contextKey = k(0)
// 使用泛型类型参数T
type valuesMap[T any] struct {
m map[string]T
parent context.Context
mu sync.RWMutex
}
func newValuesMap[T any](parent context.Context) *valuesMap[T] {
return &valuesMap[T]{
m: make(map[string]T),
parent: parent,
}
}
// Store和Load函数现在也需要使用泛型类型参数
func Store[T any](ctx context.Context, key string, value T) context.Context {
vm, ok := ctx.Value(contextKey).(*valuesMap[T])
if !ok {
vm = newValuesMap[T](ctx)
ctx = context.WithValue(ctx, contextKey, vm)
}
vm.mu.Lock()
defer vm.mu.Unlock()
vm.m[key] = value
return ctx
}
func Load[T any](ctx context.Context, key string) T {
vm, ok := ctx.Value(contextKey).(*valuesMap[T])
if !ok {
var zero T
return zero
}
vm.mu.RLock()
defer vm.mu.RUnlock()
value, ok := vm.m[key]
if ok {
return value
}
// 如果当前context没有,尝试递归查找父context
if vm.parent != nil && ctx != vm.parent {
return Load[T](vm.parent, key)
}
var zero T
return zero
}
阅读下面这段代码:
gopackage v2
import (
"context"
"sync"
)
type k int
var contextKey = k(0)
type valuesMap struct {
m map[string][]interface{}
parent context.Context
mu sync.RWMutex
}
func newValuesMap(parent context.Context) *valuesMap {
return &valuesMap{
m: make(map[string][]interface{}),
parent: parent,
}
}
// Store returns a copy of parent in which the value associated with key is value
func Store[T any](ctx context.Context, key string, value... T) context.Context {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
vm = newValuesMap(ctx)
ctx = context.WithValue(ctx, contextKey, vm)
}
vm.mu.Lock()
defer vm.mu.Unlock()
vm.m[key] = append(vm.m[key], value)
return ctx
}
// StoreSingleValue returns a copy of parent in which the value associated with key is value
func StoreSingleValue[T any](ctx context.Context, key string, value T) context.Context {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
vm = newValuesMap(ctx)
ctx = context.WithValue(ctx, contextKey, vm)
}
vm.mu.Lock()
defer vm.mu.Unlock()
vm.m[key] = []interface{}{value}
return ctx
}
// Load returns the first value stored in the context for the given key.
func Load[T any](ctx context.Context, key string) T {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
var zero T
return zero
}
vm.mu.RLock()
defer vm.mu.RUnlock()
values, ok := vm.m[key]
if ok && len(values) > 0 {
value, ok := values[0].(T)
if !ok {
var zero T
return zero
}
return value
}
var zero T
return zero
}
// LoadAll returns all value associated with this context for key
func LoadAll[T any](ctx context.Context, key string) []T {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
return nil
}
vm.mu.RLock()
defer vm.mu.RUnlock()
values, ok := vm.m[key]
if !ok {
return nil
}
var result []T
for _, v := range values {
value, ok := v.(T)
if ok {
result = append(result, value)
}
}
return result
}
好的,这段代码是用Go语言写的,它定义了一个用于在context.Context中存储和加载值的API。这个API允许你将多个值与一个给定的键关联,并且能够检索到这些值。这是通过使用一个结构体valuesMap来实现的,该结构体有一个读写互斥锁来保证并发访问的安全性。代码中定义了四个函数:Store、StoreSingleValue、Load和LoadAll。
Store函数允许你存储一个或多个值与一个键关联。StoreSingleValue是一个特化版本,它只存储单个值。Load函数用于获取与键关联的第一个值。LoadAll函数返回与键关联的所有值的切片。
每个函数都接受一个context.Context和一个键名作为参数,Store和StoreSingleValue还接受要存储的值。这些函数通过检查上下文中是否存在valuesMap来决定是否需要创建一个新的valuesMap。如果需要,它会创建一个新的valuesMap并将其与一个特殊的键contextKey关联在上下文中。
这个API可以用在需要在上下文中传递额外状态的场景中,特别是在处理并发请求时,每个请求都可以有自己的状态。
在Go语言中,value... T表示一个变参函数,允许你传递零个或多个T类型的参数。在你提供的Store函数签名中,使用value... T是合理的,因为这样可以让你存储一个或多个与键相关联的值。实际上,代码中的Store函数已经使用了value... T来实现这一功能。
如果你想要改变StoreSingleValue函数来接受一个或多个值而不是单个值,你可以将其参数列表中的value T替换为value... T。不过,这样做之后,StoreSingleValue函数就和Store函数的功能一样了,因为它们都能接受一个或多个值。如果你的目的是要让StoreSingleValue函数有与Store函数不同的行为,例如只接受单个值,那么就不应该做这样的修改。
总结一下,如果你想要StoreSingleValue函数接受多个值,可以这样修改:
go// StoreSingleValue returns a copy of parent in which the values associated with key are the provided values
func StoreSingleValue[T any](ctx context.Context, key string, values... T) context.Context {
// 函数实现...
}
但请注意,这样修改后,StoreSingleValue的行为将与Store函数相同,你可能需要重新考虑函数的命名和用途,以避免混淆。
是的,当你修改了Store函数以支持接受多个值,或者对任何函数进行了修改时,增加或更新单元测试是非常重要的。单元测试可以帮助你验证函数是否按照预期工作,并且在未来的代码更改中保证函数的行为仍然正确。
针对Store函数接受多个值的情况,你应该编写测试来确保以下几点:
LoadAll函数检索到。Store函数能够正确工作。这里是一个简单的单元测试示例,用于测试Store函数:
gofunc TestStore(t *testing.T) {
ctx := context.Background()
key := "myKey"
// 测试存储单个值
val1 := "value1"
ctx = Store(ctx, key, val1)
if got := LoadAll[string](ctx, key); !reflect.DeepEqual(got, []string{val1}) {
t.Errorf("Store() = %v, want %v", got, []string{val1})
}
// 测试存储多个值
val2 := "value2"
val3 := "value3"
ctx = Store(ctx, key, val2, val3)
if got := LoadAll[string](ctx, key); !reflect.DeepEqual(got, []string{val1, val2, val3}) {
t.Errorf("Store() = %v, want %v", got, []string{val1, val2, val3})
}
// 可以添加更多测试案例,例如并发测试,测试不同类型的值等。
}
单元测试是确保你的代码质量的关键部分,尤其是在公共API或库中,你应该为所有公开的函数和方法提供彻底的测试。这样可以减少未来修改代码时引入错误的风险,并增强代码的可靠性。
如果没有在Store函数的参数中使用value... T,那么你就不能使用Store(ctx, key, val2, val3)这样的调用方式来传递多个值。在Go语言中,value... T表示该参数可以接收任意数量的T类型的值,这被称为变参函数。
如果Store函数的定义是这样的:
go
gofunc Store[T any](ctx context.Context, key string, value T) context.Context {
// 函数实现...
}
那么它只能接收一个T类型的值作为参数。这意味着你只能传递一个值给value参数,如下所示:
goctx = Store(ctx, key, val1) // 正确
如果你尝试传递多个值,比如:
goctx = Store(ctx, key, val2, val3) // 错误
这将会导致编译错误,因为Store函数不是变参函数,它不接受多于一个的value参数。
如果你需要存储多个值,你必须要使用value... T这样的变参定义,或者你需要调用Store函数多次,每次传递一个值,像这样:
goctx = Store(ctx, key, val2) ctx = Store(ctx, key, val3)
但请注意,这样的调用方式会根据你Store函数内部的实现,可能会覆盖之前存储的值,除非你的实现是将新值追加到现有列表中。所以,如果你想要一次性存储多个值,使用value... T是更好的选择。
在Go语言中,返回类型的零值是一种常见的做法,尤其是在出错或者找不到值的情况下。零值是指一个变量在没有显式初始化时的默认值。例如,数值类型的零值是0,布尔类型的零值是false,指针类型的零值是nil,而对于切片、映射、通道、接口、函数等类型,零值也是nil。
在你提供的Load函数中,如果在valuesMap中找不到对应的键,或者类型断言失败,函数会返回类型T的零值。这确实是一种简洁且有效的处理方式,因为它避免了在函数中多次重复编写创建零值变量的代码。
以下是Load函数的一个简化示例,其中返回类型T的零值:
gofunc Load[T any](ctx context.Context, key string) T {
vm, ok := ctx.Value(contextKey).(*valuesMap)
if !ok {
var zero T
return zero
}
vm.mu.RLock()
defer vm.mu.RUnlock()
values, ok := vm.m[key]
if ok && len(values) > 0 {
value, ok := values[0].(T)
if !ok {
var zero T
return zero
}
return value
}
var zero T
return zero
}
在这个函数中,var zero T声明了一个类型为T的变量zero,其值是类型T的零值。如果上下文中没有找到值或类型断言失败,函数就会返回这个零值。
这种方式是Go语言常用的惯例,它使得代码简洁且易于理解,同时也避免了返回nil或者抛出异常,因为在Go中,通常是通过返回零值和错误值来处理异常情况。
本文作者:JIeJaitt
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!