跳到主要内容

接口最好从调用方倒着定义,别在实现还没稳定时先抽象

· 阅读需 3 分钟
一介布衣
全栈开发者 / 技术写作者

Go 学久一点以后,很多人都会记住一句话:interface 要小。但真到项目里,接口问题往往不是“大或小”这么简单,而是抽象出现得太早。实现只有一种、调用方式还没定稳,就先定义一堆 UserServiceOrderRepository 接口,最后只会让代码多一层跳转。

先有稳定用法,再决定接口长什么样

接口真正有价值的地方,是让调用方只依赖自己关心的能力。也就是说,接口更应该从使用者这边往回长,而不是从实现者这边往外铺。如果调用方只需要“读配置”和“发送消息”两个动作,就没有必要先定义一个很大的管理接口,把暂时用不到的方法都塞进去。

先写出具体实现,再看调用方实际使用了哪些方法,接口通常会自然收敛。这样形成的接口更贴合场景,也不容易一上来就把职责搞大。

过早抽接口,测试也不一定更轻松

很多人会说先抽接口是为了方便测试,这个出发点没错,但它不代表“所有具体类型都该立刻抽象”。如果某块逻辑本身很轻,直接用真实实现配合简单输入输出测试,往往比先造一层 mock 接口更直接。

真正值得接口化的,通常是外部依赖明显、替换成本高、边界相对稳定的部分,比如存储、消息发送、第三方调用。至于内部纯业务计算,如果一开始就被接口包起来,测试里反而容易只验证 mock 行为,而不是实际逻辑。

把接口放在消费侧,更能防止抽象失控

我比较喜欢的方式,是让消费方定义自己需要的最小接口,而不是让实现方输出一个“大而全”的公共接口。因为消费方知道自己到底依赖什么,实现方则很容易顺手多暴露几个方法,想着“以后也许会用到”。

接口一旦长大,就会开始绑住多个调用者。后面想拆方法、改签名、清理历史设计,成本都会明显升高。小接口之所以稳,不只是因为方法少,更因为它来自明确的消费场景。

先用具体类型把边界走清楚,通常更符合 Go 的风格

Go 很多地方都鼓励先直接、后抽象。接口也是一样。与其一开始就为未来预留层次,不如先把当前业务路径写顺,把依赖关系和调用模式看清楚。等到第二种实现真的出现、或者测试替换真的带来收益,再抽象出来,心里会更有底。

接口不是越早越高级。对 Go 项目来说,抽象出现得刚刚好,往往比“项目一开始就显得很规范”更重要。