“缺失数据不是方便的默认值。”这句话听起来像常识,但现实中无数时间处理系统都在反其道而行。空白的出生时间变成中午12点,空白的截止日期变成当天午夜,连你的浏览器都可能把没有时间信息的日期往“00:00”一塞了事。一旦默认值填进去,界面看起来完整了,但程序已经在后台悄悄把一段未知的信息,变成它以为知道的东西。
我在处理一个八字排盘流水线时撞上了这个坑。八字分析原本可以只取年、月、日三个部分,如果时辰确实未知,诚实的做法应当是给出三个柱的结论,而把时辰相关判断全部悬置。可一旦把未知时辰默认成午时,输出看起来更“全”,可信度却直线下跌。用户看到的是一套完整的八字,却不知道里面的时辰是系统硬塞进去的。更糟的是,连后续的计算模块也无从分辨这“中午”到底是用户填的,还是系统猜的。
真正需要琢磨的工程问题,根本不是“该选哪个替补时间”——中午、午夜、当前时刻都只是同一类补丁的变体。关键是:怎么让不确定性从数据入口一路亮到计算结果?怎么做到系统能大声说出“我不知道”,而不是用一份看似完整的报表把所有人蒙过去?
要保留这种诚实,首先是数据建模。一条再普通不过的代码就能让信息丢失变成常态:
const birthTime = form.time || "12:00";
这行执行之后,下游的校验、分析、缓存、渲染全都只看到一串字符串。没有人知道“12:00”是用户亲手敲的,还是缺省值自动补上的。信息的源头在赋值那一刻已经被抹掉了。换成分类型的“可辨识联合”才能把两种状态分开:
* @typedef {{ kind: "known", localTime: string, source: "user" }} KnownTime
* @typedef {{ kind: "unknown" }} UnknownTime
* @typedef {KnownTime | UnknownTime} BirthTime
function parseBirthTime(value) {
const normalized = value?.trim();
return normalized
? { kind: "known", localTime: normalized, source: "user" }
: { kind: "unknown" };
这个简单的类型里,“kind”字段变成了程序语言的一部分。任何试图读取 localTime 的代码,必须先判断 kind 的值。更重要的是,就算时间已知,它的来源也被直接标在数据上,方便调试和展示。同样的模式放到 TypeScript、Rust 的枚举、数据库的检查约束、JSON Schema 或者 Protocol Buffers 里都行,语法不是重点,关键是把“观察到的”和“推断出来的”永远标记清楚。
不确定性建模的另一半心思要花在层次分离上。时间未知,不等于整个计算瘫痪。给定日期和地点,系统仍然可以确立日期维度的上下文、时区规则和一些日柱层面的边界。真正容易出错的地方,是让某个内部辅助值跑了出来,冒充用户提供的小时数据。
举个例子,流水线也许需要一个稳定的时刻来判断某个日期是否接近时区切换点。它完全可以设一个内部参照时间,只要把这个时间打上“仅用于内部”的标签,并且决不让它流进任何输出结果,就不会污染分析结论。这跟物理实验里用来校准的中间量一样:仪器的参考信号不是样本本身,这点必须分得一清二楚。
还有一条硬规矩:除非输出能自己证明结论的可靠性,否则不要生造一个看起来精确的数字。如果最终只能给“日期层面的影响”,那就只呈现这层结论;如果缺少小时,就只给出三柱分析,并且把时辰相关的判词挪到“需提供完整出生时刻”的区域。用午时填充空比,本质上是在提供一份你本没有资格给出的信息。
总结来说,整个系统设计就该反过来思考:不是事后打个“数据不全仅供参考”的便签,而是从一开始就让每个模块都能问一句——“这条信息是你自己看到的,还是别人塞给你的?”未知时间的问题,说到底是信任问题。一个敢说“我不知道”的系统,比一个用默认值假装一切完好的系统,要可靠得多。
热门跟贴