golang: 从 Uber Go 风格指南,摄取 API 设计营养 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
guonaihong
V2EX    程序员

golang: 从 Uber Go 风格指南,摄取 API 设计营养

  • &nbp;
  •   guonaihong
    guonaihong 2019-10-16 09:29:51 +08:00 5672 次点击
    这是一个创建于 2243 天前的主题,其中的信息可能已经有所发展或是发生改变。

    昨天晚上快速看了 uber go 风格指南,发现最后一条技巧,对 API 设计有帮助,拿出来大家一起讨论下。

    回顾

    bad code

    // 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 */) 

    good code

    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 https://github.com/guonaihong/gout

    gout(流式 http client) 可以使用 SetCookies 可以设置一个或者多个 cookie。在大多数开源库里面用了两个函数实现类似功能。gout 这里用上可变长参数可以减少 API 个数。

    gin 里面

    在 gin(API 框架) Run 函数就是可变长参数经典用法,你可以用 router.Run()起个默认服务,也可以用 router.Run(":1234") 指定端口起服务。这里也可以减少 API 个数,写起来很爽。

    上面举的例子可以归纳出,可变长参数用在,函数参数个数 >=0 的地方,很爽。

    函数(接口)当配置

    //TODO,中午再补上。

    第 1 条附言    2019-10-16 13:49:35 +08:00

    接着聊早上的内容

    原始需求,设计Debug函数,输出日志

    聊函数当配置,先聊假如要设计一个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(IOSDebugOpen()) 和Debug(true)两种用法,内部用reflect区分出来即可
    • 针对常用组合提供预制函数,方便使用

    收获

    得到一个用起来不错的Debug函数。它拥有很强的组合功能,还方便修改。通过环境变量,可以拥有namespace级别的日志输出。

    总结

    我们通过一步一步的变化得到一个很灵活的代码套路。它如此强大。 什么时候要用它,对API设计有很高的要求,追求美感,不计较时间。平时该咋地 咋地,哈哈。。。

    github

    https://github.com/guonaihong/gout

    30 条回复    2019-11-10 00:32:15 +08:00
    flybird
        1
    flybird  
       2019-10-16 09:38:16 +08:00
    为啥我感觉第一种 bad code 更好呢,简单明了?
    下面 嗦嗦 实现了一堆,道理上讲貌似更好,读起来太嗦。
    dyllen
        2
    dyllen  
       2019-10-16 09:39:15 +08:00
    @flybird 估计这就是优雅吧,看起来高大上。上面的简单粗暴,像个粗人。
    ylsc633
        3
    ylsc633  
       2019-10-16 09:53:32 +08:00
    http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html

    我曾在这看到的.. 就学习(模仿)了一下.. 很装逼... 哈哈哈
    reus
        4
    reus  
       2019-10-16 09:59:24 +08:00
    初期只有一两个参数,那当然第一种好

    后期参数多了,就直接加 Option 结构体

    用函数这种……没有十几二十个选项我是不会用的
    reus
        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
    zunceng
        6
    zunceng  
       2019-10-16 10:08:08 +08:00
    挺好的

    不过用了依赖注入以后这样就不好写了
    reus
        7
    reus  
       2019-10-16 10:13:15 +08:00
    @zunceng 没错,用了依赖注入,第一种反而是最方便注入的
    scnace
        8
    scnace  
       2019-10-16 10:13:36 +08:00 via Android
    @reus 刚想发
    zzlettle
        9
    zzlettle  
       2019-10-16 10:30:36 +08:00
    个人感觉,go 的这种函数变接口的方式,不要到处用
    能用简单直接,人类思维方式的语言来实现的,就用我们人能一眼看明白的方式来写代码
    他这种写法,几乎一半以上实在炫技
    就好像,同样是吃饭,用什么碗都可以,有钱人非要用黄金碗
    其实重要的是你碗里面的东西

    唯一的作用就是吓唬不懂得人
    反人类
    这就是为什么 python 运行效率不高,但是热度慢慢攀升到第一得原因
    因为他是人类得语言
    love
        10
    love  
       2019-10-16 10:35:51 +08:00
    第二个看着太吓人了,在别的语言比如 Typescript 里简单明了且也有类型保证的传参方式在 Go 里怎么这么反人类。

    看来写普通业务用 Go 实在不是一个好选择。
    maichael
        11
    maichael  
       2019-10-16 10:38:47 +08:00
    其实要看你的 API 面向谁开发。
    zzlettle
        12
    zzlettle  
       2019-10-16 10:40:32 +08:00   1
    说句现在央视里面常用得政治术语来形容
    go 的这套语法风格
    跟 python 的语法风格
    区别就好像
    是党指挥枪,还是枪指挥党
    python 就是人类思维
    是党指挥枪
    所有方法函数,要围绕数据来运行
    go 就是枪指挥党
    所有方法函数,指挥其他的数据变量
    人类天生的思维方式就是
    数据驱动
    就是我学习本领,掌握了方法
    让方法为我而用
    现在 go 的这套
    就是我们人类围绕方法而改变自己

    go 的这套语法真的不太适合初学者
    wingoo
        13
    wingoo  
       2019-10-16 10:47:04 +08:00
    functional options 对于调用者友好, 开发者不友好
    不过实现好一个单独的类库还是挺好用的
    Mark3K
        14
    Mark3K  
       2019-10-16 10:50:33 +08:00
    grpc 中用了大量的这类技巧,看看就能学会
    simple2025
        15
    simple2025  
       2019-10-16 13:15:07 +08:00 via Android
    这么喜欢封装,不写 java 可惜了
    optional
        16
    optional  
       2019-10-16 13:17:47 +08:00
    db.WithCaching ... 要用 builder 就彻底一点 db.ConnectionBuilder().Withxxx().Withxxx().connect();
    chendy
        17
    chendy  
       2019-10-16 13:32:19 +08:00
    看不太明白的 java 程序员表示:何不 Builder ?
    gfreezy
        18
    gfreezy  
       2019-10-16 15:42:16 +08:00
    为啥不用 Builder,好像更加简单直观,功能也更强大
    wangxiaoaer
        19
    wangxiaoaer  
       2019-10-16 15:49:10 +08:00
    这个跟 js 的传入一个 options 对象作为参数差不多吧,但是代码看起来想死,尤其是那种隐式的接口实现。
    clippit
        20
    clippit  
       2019-10-16 18:17:37 +08:00
    @optional golang 里不兴链式调用
    useben
        21
    useben  
       2019-10-16 18:29:22 +08:00
    函数选项模式呗,go-micro 的插件化就是基于此模式的。
    婉转的实现了可变参数和默认参数的目的
    zjsxwc
        22
    zjsxwc  
       2019-10-16 21:22:57 +08:00 via Android
    这种需求还不如用 builder 模式来得简单易懂
    zjh6
        23
    zjh6  
       2019-10-16 21:25:35 +08:00
    golang 是谷哥搞的,就要晓得其没前途了.
    人对了,什么都是对的.
    人错了,怎么走都是错!
    guonaihong
        24
    guonaihong  
    OP
       2019-10-16 21:37:57 +08:00
    @Mark3K 是吗?有时间玩下。
    yixinlove
        25
    yixinlove  
       2019-10-16 22:46:03 +08:00
    我觉得还是看什么时候吧,如果配置项太多,可以考虑第二种,如果配置项只有那么几个,就没必要了。
    一切还是以人为本写代码,太复杂会看的头晕。
    guonaihong
        26
    guonaihong  
    OP
       2019-10-17 12:35:49 +08:00
    @zjh6 go 代码是开源的。问题不大,真的发生 google 不维护,也会有社区维护的,现在可以修改编译器源码的童鞋已经不少了。
    guonaihong
        27
    guonaihong  
    OP
       2019-10-17 20:54:46 +08:00
    @love 可否推荐个开发普通业务不错的语言,以后玩下。
    love
        28
    love  
       2019-10-17 21:32:05 +08:00
    @guonaihong 我用的是 js,当然 V2 有大把莫名奇妙的 java 码农疯狂 diss nodejs,你看着办
    guonaihong
        29
    guonaihong  
    OP
       2019-10-19 19:05:56 +08:00
    @love js 是挺不错的语言。我后面也打算玩下。
    zhixuanziben
        30
    zhixuanziben  
       2019-11-10 00:32:15 +08:00
    @love nodejs 出活挺快的,加上 ts 也有类型系统了,做做 CRUD 还是很方便的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     882 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 81ms UTC 22:54 PVG 06:54 LAX 14:54 JFK 17:54
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86