【开发第一性原理】03 · 为什么一个数据只能有一个源头

《开发第一性原理》系列第 3/12 篇 · 上一篇:02 - 复杂问题,要先学会”切” · 下一篇:04 · 找不到问题点?把范围砍一半试试
系列说明:这个系列写我在开发里反复验证过的基本规律。不追新概念,只讲那些早晚会再次应验的东西。
TL;DR:一个数据只该有一个源头。
客户改了个名,系统里改了五处,漏了一处,发货单就印错了名字。这篇聊 SSoT(单一数据源):同一份数据存多份,为什么不一致是迟早的事,以及我平时怎么做——写入收敛、读取按需,最后附一个 grep 自检的小清单。
一、客户名称对不上
前段时间业务部门来找我,说发货单上印的客户名称,和销售订单上对不上。
我查了一下,同一家客户”ABC 有限公司”,系统里至少存了五份名称:销售订单抬头存了一份,采购订单抬头存了一份,客户主数据里一份,供应商主数据里一份,还有一张报表用的自建表里又有一份。字段名都叫”客户名称”,值各自独立,谁也不会自动同步谁。
这次是客户被收购重组,名字改成了”ABC 集团股份有限公司”。业务改了主数据,改了订单,也改了报表公式。但漏掉了那张自建表。
于是发货按旧名字印,发票按新名字开。客户拿到两样东西,名字对不上,电话就过来了。
我花了一个下午,在系统里逐个搜”ABC”出现在哪些地方。这种排查很折磨人:每找到一处,你都不知道它是不是最后一处。
二、存了五份,就要记得改五处
你可能觉得,改的时候仔细一点、勤快点,不就行了?
我后来想过这个问题。同一份数据存了 N 份,每次改,就要保证 N 处一起改对。改一两次不漏,靠细心可以做到;一年两年、十次二十次都不漏,就没人敢打包票了。更新点一多,漏掉只是时间问题。
所以这事怪不到谁头上。哪怕每个参与者都很认真,结果也是结构决定的,不是态度决定的。
这就是软件工程里说的 SSoT(Single Source of Truth,单一数据源)。落到我的理解,其实就一句话:
描述只放一处,其他地方只存编码。
拿客户来说:名称、地址这些描述,只在客户主数据里维护。别的地方要用——发货单、报表——存个客户编号就够了,要显示名称的时候,实时去主数据里取。编号到处存没什么问题,它是引用,不是副本;描述存了第二份,才是对不上的开始。
这样修改只发生在一个地方,不一致从源头上就没有发生的机会。
我以前觉得,客户名称这种小字段,多存几份没什么,反正值都一样。经过这件事我改了看法:看起来一样的数据,只要存了多份,早晚会变得不一样。
三、12306 也是这么做的
这个思路不是软件行业独有,买票的时候就能体会到。
一趟车还剩多少张票,只有 12306 一个库说了算。你在携程、飞猪上看到的余票,不是它们各自存的,是展示之前实时查来的——查完没付款,过一会儿再看,数字可能就变了。
更关键的是写入:真正出票、扣库存的,永远只有 12306 一处。第三方平台做得再大,也没有自己的”票库”。
道理不复杂:关键数据只在一处维护,其他地方要用就实时来取。自己另存一份,从存下的那一刻起,那份就已经过期了。
回到开发里,一个字段的真值也应该这样对待——只在一处写,别处要用,来查。
四、我现在的几个习惯
落到日常工作,我做了这么几件事。
新写代码时:
-
临时表、自建表里只存客户编码,不存客户名称。报表上要名称,JOIN 客户主数据去拿
-
确实有冗余需要的时候(比如为了性能建了缓存表),就在写入的程序里同步更新。写进代码里,比”大家记得同步”可靠得多
-
建表的时候,唯一约束能加就加。比如客户编码,结构上就不允许出现两条一样的记录——与其指望每个人写入时都记得检查,不如让数据库直接拦住
排查存量代码时:
-
像排查那次一样,把关键值拿到代码和表结构里搜一遍:客户名称、税率、汇率,凡是写死的字面量、或者在别的表里又存了一份副本的,搜到一处就是一处隐患
-
隔段时间问自己一句:这个值的源头在哪?其他地方都只是引用吗?
五、写在最后
描述只放一处,其他地方只存编码。 多存一份描述,不是多一道保险,是多一个将来要对不上的地方。
把写入收敛到一个入口,需要的地方随时去取真值。剩下的事,交给结构。
下一篇聊”把范围砍一半试试”:程序出了问题找不到点,从中间切开,一半一半地缩小范围。
系列导览
《开发第一性原理》 共 12+ 篇,按 4 大主题展开:
- 排障方法论(01-04):先查什么 + 在哪查 + 怎么用工具
- 失败处理(05-06):事后保留 + 事前防御
- 开发者测试(07):测到边界
- 信息可被找到(08-12):字面量 / 配置表 / 代码 / 文档 / 包关系