
昨天晚上快速看了 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设计有很高的要求,追求美感,不计较时间。平时该咋地 咋地,哈哈。。。
1 flybird 2019-10-16 09:38:16 +08:00 为啥我感觉第一种 bad code 更好呢,简单明了? 下面 嗦嗦 实现了一堆,道理上讲貌似更好,读起来太嗦。 |
3 ylsc633 2019-10-16 09:53:32 +08:00 http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html 我曾在这看到的.. 就学习(模仿)了一下.. 很装逼... 哈哈哈 |
4 reus 2019-10-16 09:59:24 +08:00 初期只有一两个参数,那当然第一种好 后期参数多了,就直接加 Option 结构体 用函数这种……没有十几二十个选项我是不会用的 |
5 reus 2019-10-16 10:00:53 +08:00 著名 go 开发者 Dave Cheney 写过相关博文: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis |
6 zunceng 2019-10-16 10:08:08 +08:00 挺好的 不过用了依赖注入以后这样就不好写了 |
9 zzlettle 2019-10-16 10:30:36 +08:00 个人感觉,go 的这种函数变接口的方式,不要到处用 能用简单直接,人类思维方式的语言来实现的,就用我们人能一眼看明白的方式来写代码 他这种写法,几乎一半以上实在炫技 就好像,同样是吃饭,用什么碗都可以,有钱人非要用黄金碗 其实重要的是你碗里面的东西 唯一的作用就是吓唬不懂得人 反人类 这就是为什么 python 运行效率不高,但是热度慢慢攀升到第一得原因 因为他是人类得语言 |
10 love 2019-10-16 10:35:51 +08:00 第二个看着太吓人了,在别的语言比如 Typescript 里简单明了且也有类型保证的传参方式在 Go 里怎么这么反人类。 看来写普通业务用 Go 实在不是一个好选择。 |
11 maichael 2019-10-16 10:38:47 +08:00 其实要看你的 API 面向谁开发。 |
12 zzlettle 2019-10-16 10:40:32 +08:00 说句现在央视里面常用得政治术语来形容 go 的这套语法风格 跟 python 的语法风格 区别就好像 是党指挥枪,还是枪指挥党 python 就是人类思维 是党指挥枪 所有方法函数,要围绕数据来运行 go 就是枪指挥党 所有方法函数,指挥其他的数据变量 人类天生的思维方式就是 数据驱动 就是我学习本领,掌握了方法 让方法为我而用 现在 go 的这套 就是我们人类围绕方法而改变自己 go 的这套语法真的不太适合初学者 |
13 wingoo 2019-10-16 10:47:04 +08:00 functional options 对于调用者友好, 开发者不友好 不过实现好一个单独的类库还是挺好用的 |
14 Mark3K 2019-10-16 10:50:33 +08:00 grpc 中用了大量的这类技巧,看看就能学会 |
15 simple2025 2019-10-16 13:15:07 +08:00 via Android 这么喜欢封装,不写 java 可惜了 |
16 optional 2019-10-16 13:17:47 +08:00 db.WithCaching ... 要用 builder 就彻底一点 db.ConnectionBuilder().Withxxx().Withxxx().connect(); |
17 chendy 2019-10-16 13:32:19 +08:00 看不太明白的 java 程序员表示:何不 Builder ? |
18 gfreezy 2019-10-16 15:42:16 +08:00 为啥不用 Builder,好像更加简单直观,功能也更强大 |
19 wangxiaoaer 2019-10-16 15:49:10 +08:00 这个跟 js 的传入一个 options 对象作为参数差不多吧,但是代码看起来想死,尤其是那种隐式的接口实现。 |
21 useben 2019-10-16 18:29:22 +08:00 函数选项模式呗,go-micro 的插件化就是基于此模式的。 婉转的实现了可变参数和默认参数的目的 |
22 zjsxwc 2019-10-16 21:22:57 +08:00 via Android 这种需求还不如用 builder 模式来得简单易懂 |
23 zjh6 2019-10-16 21:25:35 +08:00 golang 是谷哥搞的,就要晓得其没前途了. 人对了,什么都是对的. 人错了,怎么走都是错! |
24 guonaihong OP @Mark3K 是吗?有时间玩下。 |
25 yixinlove 2019-10-16 22:46:03 +08:00 我觉得还是看什么时候吧,如果配置项太多,可以考虑第二种,如果配置项只有那么几个,就没必要了。 一切还是以人为本写代码,太复杂会看的头晕。 |
26 guonaihong OP @zjh6 go 代码是开源的。问题不大,真的发生 google 不维护,也会有社区维护的,现在可以修改编译器源码的童鞋已经不少了。 |
27 guonaihong OP @love 可否推荐个开发普通业务不错的语言,以后玩下。 |
28 love 2019-10-17 21:32:05 +08:00 @guonaihong 我用的是 js,当然 V2 有大把莫名奇妙的 java 码农疯狂 diss nodejs,你看着办 |
29 guonaihong OP @love js 是挺不错的语言。我后面也打算玩下。 |
30 zhixuanziben 2019-11-10 00:32:15 +08:00 @love nodejs 出活挺快的,加上 ts 也有类型系统了,做做 CRUD 还是很方便的 |