文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何应对不断膨胀的接口

2024-12-02 11:47

关注

本文转载自微信公众号「董泽润的技术笔记」,作者董泽润。转载本文请联系董泽润的技术笔记公众号。

难怪码农自嘲是 CRUD boy, 每天确实在不断的堆屎,在别人的屎山上缝缝补补。下面的案例并没有 blame 任何人的意思,我也是堆屎工^^ 如有雷同,请勿对号入座

案例

最近读一个业务代码,状态机接口定义有 40 个函数,查看 commit log, 初始只有 10 个,每当增加新的业务需求时,就不断的在原接口添加

  1. // OrderManager handles operation on order entity 
  2. type OrderManager interface { 
  3.  LoadOrdersByIDs(ctx context.Context, orderIDs []string) ([]*dbentity.Order, error) 
  4.   ...... 
  5.   TransitOrdersToState(ctx context.Context, orderIDs []string, toState orderstate.OrderState) ([]*dbentity.Order, error) 
  6.   ...... 
  7.  Stop() error 

业务中很多 interface 都是用来 mock UT, 实现依赖反转,而不是业务多态。OrderManager 就属于这类,所以接口膨胀后对工程质量影响并不大,就是看着不内聚...

接口为什么要小

The bigger the interface, the weaker the abstraction.

Go Proverbs[1] Rob Pike 提到:接口越大,抽像能力越弱,比如系统库中的 io.Reader, io.Writer 等等接口定义只有一两个函数。为什么说接口要小呢?举个例子

  1. type FooBeeper interface { 
  2.   Bar(s string) (string, error) 
  3.   Beep(s string) (string, error) 
  4.  
  5. type thing struct{} 
  6.  
  7. func (l *thing) Bar(s string) (string, error) { 
  8.   ... 
  9.  
  10. func (l *thing) Beep(s string) (string, error) { 
  11.   ... 
  12.  
  13. type differentThing struct{} 
  14.  
  15. func (l *differentThing) Bar(s string) (string, error) { 
  16.   ... 
  17.  
  18. type anotherThing struct{} 
  19.  
  20. func (l *anotherThing) Beep(s string) (string, error) { 
  21.   ... 

接口 FooBeeper 定义有两个函数: Bar, Beep. 由于接口实现是隐式的,我们有如下结论:

但是如果我们把 FooBeeper 打散也多个接口的组合

  1. type FooBeeper interface { 
  2.  Bar 
  3.  Beep 
  4.  
  5. type Bar interface { 
  6.  Bar(s string) (string, error) 
  7.  
  8. type Beep interface { 
  9.  Beep(s string) (string, error) 

如上述定义,就可以将接口做小,使得 differentThing anotherThing 可以复用接口

组合改造

关于如何改造 OrderManger 可以借鉴 etcd client v3[2] 定义的思想,将相关的功能聚合成小接口,通过接口的组合实现

  1. type Client struct { 
  2.  Cluster 
  3.  KV 
  4.  Lease 
  5.  Watcher 
  6.  Auth 
  7.  Maintenance 
  8.  
  9.  conn *grpc.ClientConn 
  10.  
  11.  cfg      Config 
  12.   ...... 

上面是 clientV3 结构体定义,虽然不是接口,但是思想可以借鉴

  1. // OrderManager handles operation on order entity 
  2. type OrderManager interface { 
  3.  OrderOperator 
  4.  TransitOrders 
  5.  Stop() error 

实际上可能接口只需抽成三个,OrderOperator 负责对 orders 的 CRUD 操作,TransitOrders 负责转态机流转,原来的 40 个函数函数都放到小接口里面

冗余改造

只抽成小接口是不行的,LoadOrderByXXXX 有一堆定义,根据不同条件获取订单,但实际上这些都是可以转换的

  1. func LoadOrders(ctx context.Context, FiltersParams options...) 

针对这种情况可以传入 option, 或是用结构体当成参数容器。再比如状态机流转有 TransitOrdersToState, TransitOrdersToStateByEntity, TransitOrdersStateByEntityForRegularDelivery 均属于冗余定义

还有一种冗余接口是根本没人用,或是不该这层暴露的

预防

接口拆分本质上是 ISP Interface segregation principle[3], 不应该强迫任何代码依赖它不使用的方法。IT 行业有一个笑话

当你的 MR 只有几行时,peer 会提出几十个 comment. 但是当你的 MR 几百行时,他们只会回复 LGTM

Peer review 还是要有责任心的,如果成本不高,建议顺手把老代码重构一下。重构代码有几项原则,可以参考 重构最佳实践2

CI lint 不知道是否支持检查 interface 行数,但是如果行数成为指标,可能又本末倒置了

参考资料

[1]Go Proverbs: https://go-proverbs.github.io/,

[2]etcd client v3: https://github.com/etcd-io/etcd/blob/main/client/v3/client.go#L44,

[3]Interface segregation principle: https://en.wikipedia.org/wiki/Interface_segregation_principle,

 

 

 

来源:董泽润的技术笔记内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯