编辑
2024-04-10
还没写好
00
请注意,本文编写于 566 天前,最后修改于 541 天前,其中某些信息可能已经过时。

目录

前言
contexthelper优化需求
V3版本优化——增加范型
这里能不能改成value... T呢? func StoreT any context.Context {}
store支持多个值的话那么应该增加单元测试吗?
如果没有加上value...,那么就不能ctx = Store(ctx, key, val2, val3)吗?
返回0值( t T),这样修改是的代码更加简洁

前言

TG bot里面存了不少优化消息,有时间可以处理一下

GitHub这个项目应该实现了类似的功能:https://github.com/jacexh/golang-ddd-template/blob/37c8237d6fa1aac14ee15d5de0e9a9a661b2a0b7/pkg/infection/infection.go#L41

contexthelper优化需求

go
package 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 }
go
package 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 }

V3版本优化——增加范型

阅读下面这段代码:

go
package 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来实现的,该结构体有一个读写互斥锁来保证并发访问的安全性。代码中定义了四个函数:StoreStoreSingleValueLoadLoadAll

Store函数允许你存储一个或多个值与一个键关联。StoreSingleValue是一个特化版本,它只存储单个值。Load函数用于获取与键关联的第一个值。LoadAll函数返回与键关联的所有值的切片。

每个函数都接受一个context.Context和一个键名作为参数,StoreStoreSingleValue还接受要存储的值。这些函数通过检查上下文中是否存在valuesMap来决定是否需要创建一个新的valuesMap。如果需要,它会创建一个新的valuesMap并将其与一个特殊的键contextKey关联在上下文中。

这个API可以用在需要在上下文中传递额外状态的场景中,特别是在处理并发请求时,每个请求都可以有自己的状态。

这里能不能改成value... T呢? func Store[T any](ctx context.Context, key string, value T) context.Context {}

在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函数以支持接受多个值,或者对任何函数进行了修改时,增加或更新单元测试是非常重要的。单元测试可以帮助你验证函数是否按照预期工作,并且在未来的代码更改中保证函数的行为仍然正确。

针对Store函数接受多个值的情况,你应该编写测试来确保以下几点:

  1. 函数能够正确接受并存储单个值。
  2. 函数能够正确接受并存储多个值。
  3. 当使用相同的键存储多个值时,这些值都应该被存储,并且能够通过LoadAll函数检索到。
  4. 验证并发安全性,确保在并发环境下Store函数能够正确工作。

这里是一个简单的单元测试示例,用于测试Store函数:

go
func 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或库中,你应该为所有公开的函数和方法提供彻底的测试。这样可以减少未来修改代码时引入错误的风险,并增强代码的可靠性。

如果没有加上value...,那么就不能ctx = Store(ctx, key, val2, val3)吗?

如果没有在Store函数的参数中使用value... T,那么你就不能使用Store(ctx, key, val2, val3)这样的调用方式来传递多个值。在Go语言中,value... T表示该参数可以接收任意数量的T类型的值,这被称为变参函数。

如果Store函数的定义是这样的:

go

go
func Store[T any](ctx context.Context, key string, value T) context.Context { // 函数实现... }

那么它只能接收一个T类型的值作为参数。这意味着你只能传递一个值给value参数,如下所示:

go
ctx = Store(ctx, key, val1) // 正确

如果你尝试传递多个值,比如:

go
ctx = Store(ctx, key, val2, val3) // 错误

这将会导致编译错误,因为Store函数不是变参函数,它不接受多于一个的value参数。

如果你需要存储多个值,你必须要使用value... T这样的变参定义,或者你需要调用Store函数多次,每次传递一个值,像这样:

go
ctx = Store(ctx, key, val2) ctx = Store(ctx, key, val3)

但请注意,这样的调用方式会根据你Store函数内部的实现,可能会覆盖之前存储的值,除非你的实现是将新值追加到现有列表中。所以,如果你想要一次性存储多个值,使用value... T是更好的选择。

返回0值( t T),这样修改是的代码更加简洁

在Go语言中,返回类型的零值是一种常见的做法,尤其是在出错或者找不到值的情况下。零值是指一个变量在没有显式初始化时的默认值。例如,数值类型的零值是0,布尔类型的零值是false,指针类型的零值是nil,而对于切片、映射、通道、接口、函数等类型,零值也是nil

在你提供的Load函数中,如果在valuesMap中找不到对应的键,或者类型断言失败,函数会返回类型T的零值。这确实是一种简洁且有效的处理方式,因为它避免了在函数中多次重复编写创建零值变量的代码。

以下是Load函数的一个简化示例,其中返回类型T的零值:

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

在这个函数中,var zero T声明了一个类型为T的变量zero,其值是类型T的零值。如果上下文中没有找到值或类型断言失败,函数就会返回这个零值。

这种方式是Go语言常用的惯例,它使得代码简洁且易于理解,同时也避免了返回nil或者抛出异常,因为在Go中,通常是通过返回零值和错误值来处理异常情况。

本文作者:JIeJaitt

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!