上周评审一个内部工具的方案。三个人的团队,用户是公司里的二十来个运营同事,方案里有消息队列、事件溯源,还有一套插件注册机制。我翻到第三页,问了一句:这套东西上线之后,半夜两点坏了,谁起来修?会议室安静了几秒。后来砍成了一张表加一个定时任务,两天上线。
这种场面我见过太多次,包括我自己写的方案。所以我慢慢攒出一套判断办法,不优雅但好用——核心是逼自己把「未来的好处」翻译成「现在的具体后果」。
我通常先问三个问题
第一,删掉这一层,哪一行会出错?
注意是「哪一行」,不是「扩展性会变差」。如果答不上来具体到函数或表的名字,说明这一层现在没有承担任何东西,只是在等一个可能永远不来的需求。
第二,谁会改这个参数,什么时候改?
配置化的本质是把决策推迟给未来的某个人。问题在于,很多方案里那个「未来的某个人」其实就是三个月后的你自己,而你当时大概率不会去改它,只会加一个新的开关。我见过最典型的堆积:
1 | if settings.ENABLE_NEW_FLOW and not settings.LEGACY_MODE: |
这段代码活了一年半,两个开关的组合从来没被真正切换过,但每次读这段的人都得停下来想两秒。两秒乘上团队人数和读的次数,就是配置项的真实账单。
第三,它坏了,谁在几点被叫起来?
这个问题筛掉的是「我加了但我不会运维」的组件。引入一个中间件,等于给自己签了一份 7×24 的值班合同。如果这个组件解决的问题一年只发生几次,那份合同的价格通常是亏的。
三个问题有个共同点:都要求你指出具体的、会发生的后果,而不是「以后可能要」。说不出具体后果的复杂度,基本可以判定为投机。
复杂度该买的是可逆性,不是优雅
我在银行系统里维护过一堆看起来挺笨的东西:幂等键、审计表、显式状态流转、每日对账。从设计感上讲,这些都不漂亮,写起来也烦。但它们防的是不可逆的错——重复扣款、状态丢失、账对不上。
所以我现在的区分标准是:这个复杂度保护的是可逆性,还是满足的是想象力?
投机性复杂度有个很稳定的特征:收益全落在未来,成本全落在现在。而未来那笔收益,往往有一个更便宜的买法——等它真来了再买。真来了再改,通常也就两天的事;提前两年准备,两年里每天都要付利息。
我吃过一次亏。早期做一个数据导出的小服务,我写了个 formatter 接口,配了三个实现,外加一个按名字查找的注册表,理由是「以后导出格式会变多」。三个月后统计,只有一个实现被调用过,另外两个是我凭想象写的。而真正出问题的地方是时区——那部分我根本没抽象,硬编码在三个不同的地方。
需求真正变化的位置,和我预先抽象的位置,基本不重合。这可能是我这几年最贵的一课。
几个我不太信任的信号
看到下面这些,我会把方案的每一页重新问一遍:
- 接口只有一个实现,而且这个实现是同一周写出来的。 只有一个调用方的接口不算接口,算一层转发。
- 配置项比调用方多。 三个调用方,十一个开关。
- 为了对称而加的层。 A 有了 B 也得有,否则「不一致」。但 A 和 B 的变更频率可能差一个数量级。
- 文档里出现「未来可以……」。 这句话后面通常跟着一段没人写的代码。
- 新人从 clone 到本地跑起来的时间。 我记得有个项目,新同事折腾了整整两天,其中一天半在配环境和理解目录结构。那一刻我就知道抽象层次已经超过团队规模了。
还有一条更底层的:抽象是负债,读的人付利息。 抽象的成本在每一次有人读代码时兑现,收益只在改动发生时兑现。如果读的人远多于改的人——内部工具、脚本、一次性数据任务基本都是这种情况——那抽象大概率是亏的。一个人维护的项目尤其明显:三个月后的你就是那个读的人,而你不会记得当初为什么这么分层。
我会刻意加复杂度的地方
反过来,有些东西我几乎不加犹豫就上,哪怕当时看着多余:
幂等。 任何会写数据的接口,配一个幂等键。给写接口加重试却不加幂等,等于把一次故障放大成 N 次,这个账我算过一次就不想再算。
显式状态。 审批、订单、长流程任务,状态写在字段里,而不是散在几个布尔标志的组合里。status = 'pending_review' 比 is_submitted && !is_approved && !is_rejected 好读,也好查。
审计与权限边界。 谁在什么时候改了什么。这条在出问题时救过我,也在别人出问题时救过同事。
限流与重试预算。 上游挂了,你是等三十秒还是等三毫秒,得提前决定,不能等线上告诉你。
这些的共同点是:它们约束的是「出事之后能不能收拾」,而不是「未来能不能扩展」。前者是不确定的、必然会来的;后者是不确定的、可能不来的。为必然来的事付钱,不算过度设计。
现在写方案,我会在最后一页加一栏叫「如果删掉它」:每一层、每个中间件、每个配置项填一行,写清楚删掉之后哪一行会出错、谁会疼。写不出来的,就在下次迭代前删掉。上周填完那一栏,三个人的方案里砍掉了四个组件——其中两个是我自己坚持要加的。
下次评审,你可以试试先在纸上画这一栏,再去写架构图。顺序反过来,往往就晚了。
评论