昨天晚上快速看了 uber go 风格指南,发现最后一条技巧,对 API 设计有帮助,拿出来大家一起讨论下。
// package db
func Connect(
  addr string,
  timeout time.Duration,
  caching bool,
) (*Connection, error) {
  // ...
}
// Timeout and caching must always be provided,
// even if the user wants to use the default.
db.Connect(addr, db.DefaultTimeout, db.DefaultCaching)
db.Connect(addr, newTimeout, db.DefaultCaching)
db.Connect(addr, db.DefaultTimeout, false /* caching */)
db.Connect(addr, newTimeout, false /* caching */)
type options struct {
  timeout time.Duration
  caching bool
}
// Option overrides behavior of Connect.
type Option interface {
  apply(*options)
}
type optionFunc func(*options)
func (f optionFunc) apply(o *options) {
  f(o)
}
func WithTimeout(t time.Duration) Option {
  return optionFunc(func(o *options) {
    o.timeout = t
  })
}
func WithCaching(cache bool) Option {
  return optionFunc(func(o *options) {
    o.caching = cache
  })
}
// Connect creates a connection.
func Connect(
  addr string,
  opts ...Option,
) (*Connection, error) {
  options := options{
    timeout: defaultTimeout,
    caching: defaultCaching,
  }
  for _, o := range opts {
    o.apply(&options)
  }
  // ...
}
// Options must be provided only if needed.
db.Connect(addr)
db.Connect(addr, db.WithTimeout(newTimeout))
db.Connect(addr, db.WithCaching(false))
db.Connect(
  addr,
  db.WithCaching(false),
  db.WithTimeout(newTimeout),
)
里面主要用了两种技巧
gout(流式 http client) 可以使用 SetCookies 可以设置一个或者多个 cookie。在大多数开源库里面用了两个函数实现类似功能。gout 这里用上可变长参数可以减少 API 个数。
在 gin(API 框架) Run 函数就是可变长参数经典用法,你可以用 router.Run()起个默认服务,也可以用 router.Run(":1234") 指定端口起服务。这里也可以减少 API 个数,写起来很爽。
上面举的例子可以归纳出,可变长参数用在,函数参数个数 >=0 的地方,很爽。
//TODO,中午再补上。
聊函数当配置,先聊假如要设计一个debug函数,目的是输出日志。第一反应是设计如下API
// 函数原型
func Debug(flag bool) {}
// 使用
Debug(true)
并且输出源不是必须的,上面聊过,支持>=0个参数,知道用可变长参数。 如果类型都不一样,可以用interface{} ok,基于这两点认知修改函数,函数内部用 类型断言或者反射可区分出类型
// 函数原型
func Debug(x ...interface{}){}
// 使用
var w bytes.Buffer{}
Debug(true, &w)
type OpenColor bool
Debug(true, OpenColor(true))
这时候需要上函数,
func iosDebug() bool {
    len(os.Getenv("IOS_DEBUG")> 0
}
// 使用
Debug(true, iosDebug)
有环境变量IOS_DEBUG打开日志,并且支持颜色高亮。假如要修改呢? 几百几千个日志调用的地方都要修改。。。
// 使用
Debug(true, iosDebug, OpenColor(true))
我们优化下上面的做法
type debugOption struct {
    openOutput bool
    openColor bool
    w io.Write
}
// Option overrides behavior of Connect.
type Option interface {
  apply(*debugOption)
}
type optionFunc func(*debugOption)
func (f optionFunc) apply(o *debugOption) {
  f(o)
}
func IOSDebugOpen() Option {
  return optionFunc(func(o *debugOption) {
    if len(os.Getenv("IOS_DEBUG")> 0 {
         o.openOutput = t
         o.openColor =true
    }
  })
}
// 函数定义
// 函数内部只要执行apply接口就行
func Debug(x ...interface{}){}
// 使用
Debug(IOSDebugOpen())
如果有全局修改只要改IOSDebugOpen函数就行。
得到一个用起来不错的Debug函数。它拥有很强的组合功能,还方便修改。通过环境变量,可以拥有namespace级别的日志输出。
我们通过一步一步的变化得到一个很灵活的代码套路。它如此强大。 什么时候要用它,对API设计有很高的要求,追求美感,不计较时间。平时该咋地 咋地,哈哈。。。
|  |      1flybird      2019-10-16 09:38:16 +08:00 为啥我感觉第一种 bad code 更好呢,简单明了? 下面 啰啰嗦嗦 实现了一堆,道理上讲貌似更好,读起来太啰嗦。 | 
|      3ylsc633      2019-10-16 09:53:32 +08:00 http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html 我曾在这看到的.. 就学习(模仿)了一下.. 很装逼... 哈哈哈 | 
|  |      4reus      2019-10-16 09:59:24 +08:00 初期只有一两个参数,那当然第一种好 后期参数多了,就直接加 Option 结构体 用函数这种……没有十几二十个选项我是不会用的 | 
|  |      5reus      2019-10-16 10:00:53 +08:00  1 著名 go 开发者 Dave Cheney 写过相关博文: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis | 
|  |      6zunceng      2019-10-16 10:08:08 +08:00 挺好的  不过用了依赖注入以后这样就不好写了 | 
|  |      9zzlettle      2019-10-16 10:30:36 +08:00 个人感觉,go 的这种函数变接口的方式,不要到处用 能用简单直接,人类思维方式的语言来实现的,就用我们人能一眼看明白的方式来写代码 他这种写法,几乎一半以上实在炫技 就好像,同样是吃饭,用什么碗都可以,有钱人非要用黄金碗 其实重要的是你碗里面的东西 唯一的作用就是吓唬不懂得人 反人类 这就是为什么 python 运行效率不高,但是热度慢慢攀升到第一得原因 因为他是人类得语言 | 
|  |      10love      2019-10-16 10:35:51 +08:00 第二个看着太吓人了,在别的语言比如 Typescript 里简单明了且也有类型保证的传参方式在 Go 里怎么这么反人类。 看来写普通业务用 Go 实在不是一个好选择。 | 
|  |      11maichael      2019-10-16 10:38:47 +08:00 其实要看你的 API 面向谁开发。 | 
|  |      12zzlettle      2019-10-16 10:40:32 +08:00  1 说句现在央视里面常用得政治术语来形容 go 的这套语法风格 跟 python 的语法风格 区别就好像 是党指挥枪,还是枪指挥党 python 就是人类思维 是党指挥枪 所有方法函数,要围绕数据来运行 go 就是枪指挥党 所有方法函数,指挥其他的数据变量 人类天生的思维方式就是 数据驱动 就是我学习本领,掌握了方法 让方法为我而用 现在 go 的这套 就是我们人类围绕方法而改变自己 go 的这套语法真的不太适合初学者 | 
|  |      13wingoo      2019-10-16 10:47:04 +08:00 functional options 对于调用者友好, 开发者不友好 不过实现好一个单独的类库还是挺好用的 | 
|      14Mark3K      2019-10-16 10:50:33 +08:00 grpc 中用了大量的这类技巧,看看就能学会 | 
|      15chenqh      2019-10-16 13:15:07 +08:00 via Android 这么喜欢封装,不写 java 可惜了 | 
|      16optional      2019-10-16 13:17:47 +08:00 db.WithCaching ... 要用 builder 就彻底一点   db.ConnectionBuilder().Withxxx().Withxxx().connect(); | 
|  |      17chendy      2019-10-16 13:32:19 +08:00 看不太明白的 java 程序员表示:何不 Builder ? | 
|  |      18gfreezy      2019-10-16 15:42:16 +08:00 为啥不用 Builder,好像更加简单直观,功能也更强大 | 
|      19wangxiaoaer      2019-10-16 15:49:10 +08:00 这个跟 js 的传入一个 options 对象作为参数差不多吧,但是代码看起来想死,尤其是那种隐式的接口实现。 | 
|      21useben      2019-10-16 18:29:22 +08:00 函数选项模式呗,go-micro 的插件化就是基于此模式的。 婉转的实现了可变参数和默认参数的目的 | 
|  |      22zjsxwc      2019-10-16 21:22:57 +08:00 via Android 这种需求还不如用 builder 模式来得简单易懂 | 
|      23zjh6      2019-10-16 21:25:35 +08:00 golang 是谷哥搞的,就要晓得其没前途了. 人对了,什么都是对的. 人错了,怎么走都是错! | 
|  |      24guonaihong OP @Mark3K 是吗?有时间玩下。 | 
|  |      25yixinlove      2019-10-16 22:46:03 +08:00 我觉得还是看什么时候吧,如果配置项太多,可以考虑第二种,如果配置项只有那么几个,就没必要了。 一切还是以人为本写代码,太复杂会看的头晕。 | 
|  |      26guonaihong OP @zjh6 go 代码是开源的。问题不大,真的发生 google 不维护,也会有社区维护的,现在可以修改编译器源码的童鞋已经不少了。 | 
|  |      27guonaihong OP @love 可否推荐个开发普通业务不错的语言,以后玩下。 | 
|  |      28love      2019-10-17 21:32:05 +08:00 @guonaihong 我用的是 js,当然 V2 有大把莫名奇妙的 java 码农疯狂 diss nodejs,你看着办 | 
|  |      29guonaihong OP @love js 是挺不错的语言。我后面也打算玩下。 | 
|      30zhixuanziben      2019-11-10 00:32:15 +08:00 @love nodejs 出活挺快的,加上 ts 也有类型系统了,做做 CRUD 还是很方便的 |