昨天晚上快速看了 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 1
著名 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 1
说句现在央视里面常用得政治术语来形容
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
chenqh 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 还是很方便的
|