阅读主题
设计练习与本章回顾
课程案例展示了如何组合时间模型。下面的练习分别检验时间量的选择与日历锚点的保存,最后将这些判断收束为可以用于其他业务的设计方法。
1.11 设计练习与本章回顾
下面两个设计题允许不同业务选择,后两个诊断题检查能否定位错误所在的层次。答题时先明确承诺,再写数据依据、计算关系和能区分不同选择的边界例子;函数名本身不算完整答案。
1.11.1 用新需求检验模型
练习一:第一次到店是晚上十一点。 门店允许用户“从首次到店起使用七天”。假设首次到店为上海时间 2027 年 3 月 1 日 23:00,第七天晚上能否使用,第八天上午又怎样?如果换成实行夏令时的门店,或者用户旅行,哪些计算需要改变?
参考路径:比较固定长度与七个当地日期
一种约定是连续 168 小时。保存首次到店的时间点,截止为该瞬间加 168 小时。上海例子得到 3 月 8 日 23:00;3 月 7 日晚上和 3 月 8 日上午都仍有效。其他地区跨越夏令时跳变时,当地截止的钟表读数可能变化,但经过的长度保持 168 小时。
另一种约定是到店当天算第一天,使用到第七个当地日期结束。保存首次到店时间、按门店时区得到的开始日期和所采用的规则;截止为开始日期加七个日历日的日界。本例得到 3 月 8 日 00:00,因此第七天晚上有效,第八天上午无效。计算日界时使用明确时区,不把七天替换成 168 小时。
这两种约定在普通的上海日期里就相差 23 小时;夏令时会进一步影响经过的长度。另一种“同一当地时刻七天后截止”的约定也可以成立,但它与“到第七天结束”仍不同。
用户旅行不改变门店的承诺。本例对已售权益固定购买时的业务时区;门店以后换时区,只影响新售权益。若要修改既有权益,应另定迁移规则,不能让配置更新悄悄重算历史截止。
判断依据是所保持的量。连续 168 小时保持时间轴长度,七个当地日期保持日历覆盖,七天后同一时刻保持当地钟表读数。三种模型都应明确起点、排他截止和时区来源;如果实现无法说明自己保持的是哪一种量,即使普通日期下结果相同,也没有验证业务承诺。
练习二:2 月 29 日的年度订阅。 订阅在 2028 年 2 月 29 日生效,产品要求“每年同一天续费”。确定平年处理方式后,推算接下来八次周年日期,并说明 2032 年和 2036 年应怎样恢复原来的日期。
参考路径:保留原始月日,逐年独立计算
可以选择“目标年份没有 2 月 29 日时取 2 月最后一天”,也可以选择“没有时顺延到 3 月 1 日”。两种方案都保留原始锚点为 2028 年 2 月 29 日,并保存所选策略。每个目标年独立计算,不能把上次退让后的 2 月 28 日或 3 月 1 日当成新的周年锚点。
表 1-8:从同一个闰日锚点推算接下来八次周年日期。
| 周年年份 | 平年取 2 月末 | 平年顺延到 3 月 1 日 |
|---|---|---|
| 2029 | 02-28 | 03-01 |
| 2030 | 02-28 | 03-01 |
| 2031 | 02-28 | 03-01 |
| 2032 | 02-29 | 02-29 |
| 2033 | 02-28 | 03-01 |
| 2034 | 02-28 | 03-01 |
| 2035 | 02-28 | 03-01 |
| 2036 | 02-29 | 02-29 |
这里先推算日期。真正执行收费,还需要约定当地时刻、业务时区及解析策略;不能因为周年日期已经确定,就认为执行瞬间也确定了。
检验的重点是信息是否保留。两个闰年恢复到 2 月 29 日,证明原始锚点仍然参与生成;平年采用哪条路径,则由显式策略决定。若每次从上次退让后的日期继续加年,即使第一年结果正确,也无法证明后续仍保持周年承诺。
练习三:时间戳相等就能通过验收吗? 一个截止经过数据库往返后,与原时间点完全相等;授权只读取每天更新一次的状态。另一套系统每次都比较截止,却接受客户端传入的 now。分别说明已经验证的保证和仍然缺少的保证。
参考路径:分开存取、业务判断与时间来源
第一套系统证明截止没有在存取中改变,却没有在请求到达时检查是否已经过期。批任务迟到时,状态仍可能放行,应把新请求的判断与后台状态整理分开。
第二套系统使用了正确的区间运算,但判定输入来自可被用户改变的来源。应根据权限的控制方选择时间源,再检查它的误差是否满足业务要求。改为服务端时间解决来源控制,不等于误差自动消失。
两个例子都说明,局部性质成立不能替代整条设计链的验证。验收要沿语义、计算、存取、业务动作与来源分别提出证据。
练习四:旅行后,课程和登录都出了问题。 学员到东京后,设备时区仍设为上海,系统日期又因误操作快了一年。课程页面显示的钟点与当地预期不同,连接服务时还报告证书过期。把展示时区改成东京后,是否应当同时修复两种现象?自动校时服务已经启动,是否足以排除时间故障?
参考路径:区分表示规则、时钟读数与验证方
先检查课程是否仍对应同一个已发布瞬间。如果只是显示采用上海时区,切换到东京可以修正当地表示,不能因此重算课程计划。系统日期快了一年是另一项故障,修改时区不能消除这一年的偏差。
证书由客户端验证时,应将客户端当前时间与证书有效期比较,同时检查证书是否真的过期,不能只凭错误信息断定设备错了。恢复准确时间后重新验证;不要以关闭证书校验作为解决办法。若业务请求尚未通过 TLS,就没有进入课程服务的报名判断,调整报名截止也无法修复连接。
校时服务启动只说明进程在运行,还要检查来源是否可达、最近是否成功同步、偏差是否正在收敛。若恢复采用渐进校正,启动后也未必立即准确。验证应分别覆盖展示设置、系统时间与真实证书验证路径;仅注入课程业务的测试时钟不足以覆盖全部现象。
如果需要进一步练习时间来源与结账口径,可以继续做离线事件练习。它接续晚到数据和重算问题,包含一条可自查的处理路径。
1.11.2 用关系重建整章知识
回到 1.1—1.2 节的会员设计,“上海日期 3 月 31 日全天有效”先选定业务日历与时区,再生成 4 月 1 日开始的截止瞬间,最后通过半开区间判断请求时间。这里的日期、转换和区间依次回答三个不同问题,任何一步缺少约定,后续比较都可能准确地回答错问题。
日历计算扩展了边界的生成方式。持续时间沿时间轴增加长度,日历量先推进日期字段;夏令时改变相邻日界的间隔,月末截断可能丢失原始日号,所以未来计算需要保留相应规则与锚点。日报使用生成后的区间划分事件,预约使用日期规则和解析策略生成计划,两者共享基础机制。
表示与存储负责保留这些对象所需的信息。时间点的往返应保持同一瞬间,日期应保持同一日期,未来日程还要保留生成依据。UTC 可以表示已经确定的瞬间,却不能替我们保存“每月 31 日”这样的规则。检查存取时,要同时确认精度、类型转换和时区解释是否改变了原意。
有了截止,还得说明到达截止时系统应该做什么。什么时候受理、已有操作能否继续、资源何时允许删除,要分别说明。后台状态更新无法自动替代时间条件,一次检查成功也不会自动成为可靠的受理事实。比如,截止算得正确,并不能防止一个迟到的批任务多放行十分钟。
最后还要检查时间来源。字段名告诉我们记录了什么事件,类型决定可以怎样计算,而来源和时钟误差决定这个记录有多可信。设备校时维护当前时间估计,时区设置决定当地表示,两者分别需要正确依据。有效期、经过长度、到点执行和事件归属对时间的依赖不同,因此故障处理也不同;记录事件与测量耗时使用不同依据,多个来源的读数不能未经条件检查便当作因果顺序。
1.11.3 从模型到设计的使用顺序
面对新的时间需求,先写出字段回答的问题和必须保持的性质。例如“记录支付何时完成”选择事件时间点,“每月同一日号收费”则要求原始锚点和日历策略。随后确定转换与计算需要的全部条件,让规则先于函数选择。
再明确哪些是原始依据,哪些是派生结果,哪些必须保存为事实。按这些角色选择协议和存储,指定判断动作与时间来源,并为每一层安排验证:边界验证区间,特殊日期验证日历规则,往返验证信息保留,条件变化验证依赖,集成测试验证实际执行。不能只验证最容易运行的那一层。
是否需要保存更多数据,要看系统还需要回答什么问题。需要重现旧报表时保留当时数据依据;需要解释历史解析时保留相应规则信息;需要可靠补跑时跟踪计划与执行结果。简单产品也可以只保存少量必要信息,但省略之前应清楚将不再回答哪些问题。
本章建立的是普通业务时间建模的基础。跨机因果关系、共同等待预算和可靠执行还需要相应机制,后续章节将在这些基础上继续讨论。以后再遇到一个时间字段,可以先问清它表示什么,再逐步确定怎样计算、保存和使用,而不必从记忆里翻找一个相似的踩坑故事。
下一章讨论另一个普通字段:id。时间字段要求我们区分表示与含义,标识符也一样:能唯一找到一行数据,是否就足以稳定地标识同一个业务对象?