Go 接口设计留扩展点,我在三处收手
我们公司的 admin 后台是我一个人写的,里面有个接口,参数从最开始的三字段长到了六字段。每次加需求我都得回去改一遍调用方。后来我专门想过 Go 接口设计怎么留扩展点,答案不在类型定义多宽,而在三处我主动停手的地方。
第一版接口改了三回,问题不在类型上
那个接口一开始收三个参数:用户 ID、页码、每页条数。第一版很干净,调用方也少。
后来要加筛选条件,我加了第四个参数。再后来要加时间范围,我加了第五个、第六个。每次加参数,所有调用点都要跟着改一遍。
前两回我以为是类型不够宽,试过把参数换成 map[string]string。结果更难维护:谁传了什么看不出来,编译期也拦不住错。
第三回我才想清楚,问题不是参数宽窄,而是每加一个需求都逼着所有调用方改一遍。
收手点一:参数打包成结构体
第一次收手是把入参收成一个结构体。结构体加字段,旧调用方不用动,零值就是默认行为。
我现在的习惯是:只要一个接口的参数可能超过三个,第一天就打包成结构体。
关键是新字段必须有明确零值语义。加了字段以后旧调用行为变了,那就不是扩展。我给自己定的规矩是:新增字段默认关闭,想启用得显式传。
收手点二:返回里预留错误范围
第二次收手在返回值上。我一开始返回 error,错误信息是拼出来的字符串,客户端想区分「参数错」和「没权限」,只能去匹配字符串,改一个字就全崩。
后来我固定了几档错误范围,做成一层的错误类型,用 errors.Is 判断。加新错误只要归到已有档位里,调用方不用改。
这一步的价值不在当下,在半年后。老客户端遇到不认识的分支,忽略就行。
Go 接口设计怎么留扩展点:第三处最反直觉
第三次收手,是把权限判断从业务实现里拿出去。
我那个接口里原本混着权限检查,加一种角色就要改一次核心逻辑。改到最后,一个函数里躺着三段判断,谁也不敢动。
分开以后,接口只负责「能不能做」,具体「怎么做」走后面的实现。加角色只动判断那一层。代价是多了一层调用,一个人的项目完全值得,改错的概率比跑慢一点严重得多。
三处收手,哪些能复制
能复制的部分很明确:参数用结构体、错误分档、权限和实现分离。这三条跟语言没关系,换 Java 也一样成立。
不能复制的是顺序。我是被三次改动打到疼才想到收手,如果你还没被改过接口,看这篇大概率只会觉得过度设计。
有一件事我到现在也没做好:扩展点留多了,接口会变宽,一个参数半年没人用也不敢删。所以我只在这三处留,别处能简单就简单。写 Go 接口设计怎么留扩展点,说到底就一句话:别让一次需求变化牵动所有调用方。
常见问题
Q:Go 接口设计怎么留扩展点,一开始就要留够吗?
A:不用,先留三处最容易被牵动的位置就够了。判断标准是「加一个需求,会不会逼所有调用方改代码」。
Q:接口参数超过几个就该打包成结构体?
A:我自己的线是三个。超过三个还在往里加,说明这个接口承担的东西已经超出参数列表能表达的范围了。
Q:这样设计会不会变成过度设计?
A:会,而且我踩过。扩展点留太多,接口会宽到没人敢动,所以我现在只固定留这三处。
相关阅读
- Go 里 channel 和 mutex 该用哪个
- Go 中间件写多厚合适,我见过两个极端
- Go 单体服务怎么分层,一个人维护够用
- Go 服务优雅退出:三步不断掉请求
- Go 配置放文件还是环境变量,我踩坑后定了
本文作者西米,37 岁程序员,一个人加五个 AI 员工在跑一家公司。踩过的坑和真实账本都写在公众号「西米分享」。
完整代码、踩坑清单与最新进展,都在公众号。
📱 关注公众号「西米分享」