Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

value: AnyValue 设计 #120

Closed
flycash opened this issue Nov 18, 2022 · 11 comments · Fixed by #121
Closed

value: AnyValue 设计 #120

flycash opened this issue Nov 18, 2022 · 11 comments · Fixed by #121
Labels
good first issue Good for newcomers

Comments

@flycash
Copy link
Contributor

flycash commented Nov 18, 2022

仅限中文

使用场景

Go 因为泛型方面以及 error 处理两方面的限制,导致有一些 API 非常难用。举一个例子,在正常的情况下,我们的缓存 API 会设计成:

type Cache interface {
      Get(key string) (any, error)
}

那么用户在使用的时候,如果想要万无一失使用这个 API,它必须要进行两次检测:

val, err := c.Get("mykey")
if err != nil {
     return err
}
intVal, ok := val.(int)
if !ok {
     return errors.New(xxxx)
}
// 使用 IntVal

所以我们可以考虑参考类似于 sql.Row 的设计,提供一个已经封装好了的 API : AnyValue

type AnyValue struct {
    val any
    err error
}

行业分析

如果你知道有框架提供了类似功能,可以在这里描述,并且给出文档或者例子

可行方案

核心的类型是

type AnyValue struct {
     val any
     err error
}

func (a AnyValue) Int() (int, error) {
      if err != nil {
          return 0, err 
     }
     val, ok := a.(int)
     if !ok {
         return errInvalidType
     }
     return val, nil
}

// 返回 int 数据,或者默认值
func (a AnyValue) IntOr(def int) int {
    val, err := a.Int()
    if err != nil {
       return nil
    }
}

这里我设计了两类方法:

  • 支持类型断言,并且返回值和 error
  • 支持类型断言,并且返回值或者指定的默认值

除了 int,还要支持以下类型:

  • int 族
  • uint 族
  • float 族
  • string
  • []byte

其它的 map 和切片,或者数组,因为 Go 类型的限制,我觉得就没有特别强的动力去支持了。

其它

要不要做类型转换

假如说值的真实类型是 int,但是用户要求返回一个 int64,那么按照道理我们可以将 int 转化为 int64 类型

但是如果我们打算支持类型转换,那么我们就需要进一步考虑,string 转基本类型要不要支持?基本类型转 string 类型要不要支持?于是我们就会陷入这种很尴尬的境地,即如果我们要支持类型转化,我们就要考虑这些类型的排列组合。

即便我们要支持,我也倾向于我们提供另外一组方法,以 Convert 为前缀

  • ConvertInt() (int error)
  • ConvertIntOr(def int) int

那么用户就可以知道,这些方法内部是支持类型转化的。这种转化在一个特定的场景下会非常有用:web 等操作 string 类型数据的框架。例如 Redis 的客户端拿到的永远是 string,我们就可以利用这种机制简化代码:

redis.Get("mykey").ConvertInt()

又或者是 Web 的路径参数,Header 的值,查询参数等,都可以利用这种 API 来简化代码

要不要支持特性类型的切片

例如要不要支持 []int, []uint 这种比较常见的切片类型?这个确实是可以考虑支持的,如果不嫌麻烦的话就是可以一起支持了

放在哪个包?

我暂时是想说新开一个 value(x) 的包,放在这里面。另外一个可以考虑的是直接放在顶级目录下。我还在犹豫,这个可以进一步讨论。

你使用的是 ekit 哪个版本?

你设置的的 Go 环境?

上传 go env 的结果

@flycash flycash added the good first issue Good for newcomers label Nov 18, 2022
@WeiJiadong
Copy link
Contributor

关于@longyue0521对提交的code review建议,有这几个点要讨论一下,确定下结论,方便后续及时做调整:
1.value包的放置位置。 --是需要对外提供还是说只是内部使用
2.test用例的书写规范。 --1.测试用例模板选用问题;2.测试用例是从顶层递归向下写,还是每层只做好自己的检测;3.检测判断函数,有点像第1点,主要是结果判断应该用哪种方式。
3.是否要对错误进行封装。 --结合自己的平时经验,是要对错误做一层封装,附带上抛出错误的位置,方便定位问题。无意义字符串有这俩低效问题:1.需要全局搜代码以及所有依赖库(比较难搜索)的代码;2.如果不幸出现描述错误的字符串重复情况,那定位问题就是天坑了。

@flycash
Copy link
Contributor Author

flycash commented Nov 21, 2022

我的想法是:

  • value 包放在 ekit 之下,暴露出去给人用——我自己就要用到了。然后叫 value 不一定特别合适,你们有什么建议吗?
  • 关于测试,我并不想引入太多的规范,比如说是否使用args 或者 fields。因为从可读性的角度来说,没什么区别,我相信对于我们来说也都能看懂。所以它本身就是一个纯粹的规范问题。而纯粹的规范问题我是不太愿意给开发者限制的。只有那种影响功能性与非功能性的,设定规范才有意义。所以你的测试用例里面用 arg 还是用 field,我觉得都 ok。唯一的缺陷可能就是 arg 和 field 看上去稍微累赘一点
  • 测试用例我赞同 longyue 的看法,即最好是站在本层上考虑,站在一个功能性的角度考虑
  • 检测判断函数应该使用 assert 包或者 require 包,要点就是你想中断后面的执行,就用 require,否则用 assert。用这两个包代码会更加简洁紧凑,比如说没有那么多的 if-else
  • 错误包装问题:站在中间件设计者的角度来说,我们把错误描述清楚就可以了。必要的定位信息正如 longyue 所说,交给用户去处理。这其实是一个偏好问题——比如说我定位问题一般就是拿着错误信息来定位,在 Go 里面基本上不依赖于具体的堆栈信息。从 Go 推荐的做法来看(它没有内置堆栈信心),他们也应该是倾向于单纯利用错误信息来定位。我的最终方案其实是通过错误码来帮助用户排查问题。目前尚未引入而已

@flycash
Copy link
Contributor Author

flycash commented Nov 21, 2022

这里我可以稍微再讨论一下测试模板的问题。实际上我认为划定一个模板是很困难的事情,在 ekit 里面可能就不是特别难。我之前用过很多种写测试的方式:

  • 第一种是你们在训练营里面看我写的,也是我最常用的
  • 第二种是 Goland IDE 自带的模板,偶尔也用用,一般是有一些特别复杂的对象我会考虑用这种手段;
  • 第三种则是函数式 + 前面第一种或者第二种,一般是因为我测试的对象内部有复杂逻辑,而我需要使用 mock 数据来返回

在第三种形态下,测试看起啦就是:

a func() AnyValue

一些特别复杂的,难以创建的实例我也会通过这种形态来测试。所以总的来说我觉得这方面划定一个模板意义不是特别大。而且有些时候我也会觉得这是一个偏好问题

@flycash
Copy link
Contributor Author

flycash commented Nov 21, 2022

另外一个,关于 Convert 类的方法,你们觉得有没有必要支持呢?我对此也是稍微有点犹豫。

@WeiJiadong
Copy link
Contributor

另外一个,关于 Convert 类的方法,你们觉得有没有必要支持呢?我对此也是稍微有点犹豫。

这个问题都不大

@WeiJiadong
Copy link
Contributor

我的想法是:

  • value 包放在 ekit 之下,暴露出去给人用——我自己就要用到了。然后叫 value 不一定特别合适,你们有什么建议吗?
  • 关于测试,我并不想引入太多的规范,比如说是否使用args 或者 fields。因为从可读性的角度来说,没什么区别,我相信对于我们来说也都能看懂。所以它本身就是一个纯粹的规范问题。而纯粹的规范问题我是不太愿意给开发者限制的。只有那种影响功能性与非功能性的,设定规范才有意义。所以你的测试用例里面用 arg 还是用 field,我觉得都 ok。唯一的缺陷可能就是 arg 和 field 看上去稍微累赘一点
  • 测试用例我赞同 longyue 的看法,即最好是站在本层上考虑,站在一个功能性的角度考虑
  • 检测判断函数应该使用 assert 包或者 require 包,要点就是你想中断后面的执行,就用 require,否则用 assert。用这两个包代码会更加简洁紧凑,比如说没有那么多的 if-else
  • 错误包装问题:站在中间件设计者的角度来说,我们把错误描述清楚就可以了。必要的定位信息正如 longyue 所说,交给用户去处理。这其实是一个偏好问题——比如说我定位问题一般就是拿着错误信息来定位,在 Go 里面基本上不依赖于具体的堆栈信息。从 Go 推荐的做法来看(它没有内置堆栈信心),他们也应该是倾向于单纯利用错误信息来定位。我的最终方案其实是通过错误码来帮助用户排查问题。目前尚未引入而已

1.ok,我改一下位置,起名字是个世纪难题,你们一起看看,哈哈哈...
2.ok
3.ok,这个场景ok,但后续场景复杂了,还是建议每个单元保证自己这个单元没问题,不然迭代相关代码真的很麻烦,很多用例可能都得推翻重新写,而且复杂场景,构造起来可能也不是那么方便。
4.后续我调一下这个模板,生成用assert之类的。
5.ok,那就不裹这层了。

@WeiJiadong
Copy link
Contributor

这里我可以稍微再讨论一下测试模板的问题。实际上我认为划定一个模板是很困难的事情,在 ekit 里面可能就不是特别难。我之前用过很多种写测试的方式:

  • 第一种是你们在训练营里面看我写的,也是我最常用的
  • 第二种是 Goland IDE 自带的模板,偶尔也用用,一般是有一些特别复杂的对象我会考虑用这种手段;
  • 第三种则是函数式 + 前面第一种或者第二种,一般是因为我测试的对象内部有复杂逻辑,而我需要使用 mock 数据来返回

在第三种形态下,测试看起啦就是:

a func() AnyValue

一些特别复杂的,难以创建的实例我也会通过这种形态来测试。所以总的来说我觉得这方面划定一个模板意义不是特别大。而且有些时候我也会觉得这是一个偏好问题

是的,有外部依赖的mock场景,多协程场景,未导出场景,确实都属于比较难写单测的场景,比较难统一。但似乎可以给一个参考写法,尽量只聚焦于用例书写,复杂场景就自己对应改一把。

@flycash
Copy link
Contributor Author

flycash commented Nov 21, 2022

是的,有外部依赖的mock场景,多协程场景,未导出场景,确实都属于比较难写单测的场景,比较难统一。但似乎可以给一个参考写法,尽量只聚焦于用例书写,复杂场景就自己对应改一把。

如果要参考的话,你就参考已有的写法。这个是我的偏好,也就是不用 args 和 fields 的那种。

Convert 我主要考虑的是允许哪些类型进行转换,以及怎么转换,还有就是要不要考虑溢出问题。例如:

  • string 可以转所有的基本类型:这个倒是比较简单,直接用 strconv 就可以,我们不必做任何修改,strconv 检测了溢出就检测,没检测就拉到
  • 数字类型之间的互相转换:理论上来说我们保持和 strconv 的语义,也就是说 strconv 检测溢出,我们这里也检测
  • 基本类型转 string:这个应该也蛮简单
  • []byte 转基本类型:这个是我觉得很棘手的一个事情。一方面来说从编码角度来说,有大顶端小顶端的说法;另外一方面也可能它其实就是一个字符串 ”10“ 这种。基本类型转 []byte 也是有这方面的顾虑

从我个人倾向上来说,我觉得溢出问题可以不校验。但是话又说回来,如果我们不校验,那么大多数用户是没有那个觉悟去校验溢出问题的。

@WeiJiadong
Copy link
Contributor

是的,有外部依赖的mock场景,多协程场景,未导出场景,确实都属于比较难写单测的场景,比较难统一。但似乎可以给一个参考写法,尽量只聚焦于用例书写,复杂场景就自己对应改一把。

如果要参考的话,你就参考已有的写法。这个是我的偏好,也就是不用 args 和 fields 的那种。

Convert 我主要考虑的是允许哪些类型进行转换,以及怎么转换,还有就是要不要考虑溢出问题。例如:

  • string 可以转所有的基本类型:这个倒是比较简单,直接用 strconv 就可以,我们不必做任何修改,strconv 检测了溢出就检测,没检测就拉到
  • 数字类型之间的互相转换:理论上来说我们保持和 strconv 的语义,也就是说 strconv 检测溢出,我们这里也检测
  • 基本类型转 string:这个应该也蛮简单
  • []byte 转基本类型:这个是我觉得很棘手的一个事情。一方面来说从编码角度来说,有大顶端小顶端的说法;另外一方面也可能它其实就是一个字符串 ”10“ 这种。基本类型转 []byte 也是有这方面的顾虑

从我个人倾向上来说,我觉得溢出问题可以不校验。但是话又说回来,如果我们不校验,那么大多数用户是没有那个觉悟去校验溢出问题的。

嗯嗯,目前测试用例就是去掉了fileds和args。
这么分析的话,好像covert提供的作用不太大,因为用户还得基于自己的场景了解下库的实现,因为我们并不能很好的预测出来用户的预期效果。

@longyue0521
Copy link
Collaborator

@WeiJiadong @flycash

  1. value是包名,类型名就用Any(大写),感觉一下写法哪个最不反感
    • value.Any.Int() , value.Any.IntOrDefault(xx)
    • 也可以考虑加个to, value.Any.ToInt(),value.Any.ToIntOrDefault(xx),
    • 或者放到根目录下,ekit.AnyValue.Int() ekit.AnyValue.IntOrDefault(xxx)
  2. 看了你们上面的讨论,我感觉Covert这个功能,现阶段属于ROI比较低的,要不先放放,当它的被需要的程度像AnyValue这样时,咱们再来实现,那时我们对Convert需求理解也更清晰,更容易做出判断要支持哪些,不支持哪些类型的转换.

@flycash
Copy link
Contributor Author

flycash commented Nov 21, 2022

@WeiJiadong @flycash

  1. value是包名,类型名就用Any(大写),感觉一下写法哪个最不反感

    • value.Any.Int() , value.Any.IntOrDefault(xx)
    • 也可以考虑加个to, value.Any.ToInt(),value.Any.ToIntOrDefault(xx),
    • 或者放到根目录下,ekit.AnyValue.Int() ekit.AnyValue.IntOrDefault(xxx)
  2. 看了你们上面的讨论,我感觉Covert这个功能,现阶段属于ROI比较低的,要不先放放,当它的被需要的程度像AnyValue这样时,咱们再来实现,那时我们对Convert需求理解也更清晰,更容易做出判断要支持哪些,不支持哪些类型的转换.

目前来看,好像单独一个 value 包没啥意义,因为我们没有别的 value 了,那就放 ekit 下面好了。万一还有别的,我们可以在大版本升级的时候把这个东西挪走。倒也不需要加 To,主要是我们这个没有那种隐含的类型转换之类的,比如说 ConvertXXX 改名叫做 ToXXX 反而比较合适。

那么 ConvertXXX 就将来我们再看了

@flycash flycash linked a pull request Nov 23, 2022 that will close this issue
flycash pushed a commit that referenced this issue Nov 23, 2022
* value: AnyValue 设计 (#120)

* 修复ci检测问题

* 1.fix cr问题
2.add changelog对该pr的引用
3.add license 头部

* 1.修改ChangeLog,加入新特性描述
2.挪出value包,放在根目录
3.统一error格式打印

* 断言方式.Name改为.String

Co-authored-by: vividwei <vividwei@tencent.com>
flycash added a commit that referenced this issue Dec 6, 2022
* sqlx 加密列 key长度校验 (#102)

* sqlx 加密列 key长度校验

* sqlx 加密列 key长度校验 补单元测试

* 修改加密列key长度错误提示

* atomicx: 泛型封装 atomic.Value (#101)

* atomicx: 泛型封装 atomic.Value

* 添加 CHANGELOG

* syncx/atomicx: 增加 Swap 和 CAS 的泛型包装

* 添加 swap nil 的测试

* 添加更加多的 benchmark 测试,同时保证 NewValue 和 NewValueOf 的语义在 nil 上一致

* 优化单元测试

* queue: API 定义 (#109)

* queue: API 定义

* 补充 API 说明

* 实现优先级队列和并发安全优先级队列 (#110)

基于小顶堆和切片的实现

* queue: 延时队列 (#115)

* 延迟队列: 优化唤醒入队元素逻辑 (#117)

* 修改CHANGELOG链接;添加测试用例修复bug

Signed-off-by: longyue0521 <longyueli0521@gmail.com>

* 修改cond的SignalCh为signalCh;理清注释

Signed-off-by: longyue0521 <longyueli0521@gmail.com>

Signed-off-by: longyue0521 <longyueli0521@gmail.com>

* value: AnyValue 设计 (#120) (#121)

* value: AnyValue 设计 (#120)

* 修复ci检测问题

* 1.fix cr问题
2.add changelog对该pr的引用
3.add license 头部

* 1.修改ChangeLog,加入新特性描述
2.挪出value包,放在根目录
3.统一error格式打印

* 断言方式.Name改为.String

Co-authored-by: vividwei <vividwei@tencent.com>

* queue: 基于切片的并发阻塞队列和基于 CAS 的并发队列设计 (#119)

* queue:使用list包中的LinkedList实现并发阻塞链式队列 (#122)

* queue:增加链式并发阻塞队列

Co-authored-by: kangdan <ujn_kangdan@qq.com>
Co-authored-by: dan.kang <dan.kang@realai.ai>

* ConcurrentLinkBlockingQueue 改成ConcurrentLinkedBlockingQueue (#123)



* ConcurrentLinkBlockingQueue 改成ConcurrentLinkedBlockingQueue

* modify .CHANGELOG.md

* modify .CHANGELOG.md

Co-authored-by: kangdan <ujn_kangdan@qq.com>
Co-authored-by: dan.kang <dan.kang@realai.ai>

* queue: ConcurrentLinkedQueue增加超时控制逻辑 (#124)



Co-authored-by: kangdan <ujn_kangdan@qq.com>
Co-authored-by: dan.kang <dan.kang@realai.ai>

* queue: 添加例子

- 添加队列例子
- 去除 ConcurrentLinkedQueue 的超时机制

* queue: 添加例子 (#126)

Signed-off-by: longyue0521 <longyueli0521@gmail.com>
Co-authored-by: hookokoko <648646891@qq.com>
Co-authored-by: Gevin <flyhigher139@gmail.com>
Co-authored-by: Longyue Li <longyueli0521@gmail.com>
Co-authored-by: 韦佳栋 <353470469@qq.com>
Co-authored-by: vividwei <vividwei@tencent.com>
Co-authored-by: kangdan666 <95063166+kangdan6@users.noreply.github.com>
Co-authored-by: kangdan <ujn_kangdan@qq.com>
Co-authored-by: dan.kang <dan.kang@realai.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants