软件建模01:场景、用例与规则
同一台设备不能在重叠的租期里预留给两个请求。这个要求很短,却没有回答:12 点归还的设备能否从 12 点再次出租?取消后重复提交旧请求,会不会重新占用设备?请求被拒绝后,资源空闲了,重试是否应该成功?这些分支会改变实现,即使页面上始终只有一个“预留”按钮。
上一篇:模型帮助作出什么决定 已建立指定设备预留的 Java 21 内存基线。这里沿用其合成教学材料,提取一份能定位来源、找到反例并对照代码的用例。所有业务约定都来自教学案例,没有真实访谈、企业制度或生产验证作为背书。目标决定用例的范围
租赁经办角色希望取得设备 E1 在指定时段的预留承诺。“填写表单”和“发送请求”是完成目标的步骤;只证明按钮能点击,无法证明预留成立。成功后应有一条有效预留,后来与它冲突的新请求应被拒绝。这样,目标就有了可观察的后果。
Jacobson 与 Cockburn 的《Use-Case Foundation》v1.1 把用例组织为某个主要参与者为达成目标而使用系统的一组场景,包含成功、遇到障碍和失败的路径。第 4 页进一步区分基本场景与扩展路径;用例的精度取决于这些路径是否准确,而不取决于步骤写得多细。原文第 1、4 页
这个目标的系统边界先限定为单个 RentalDesk 实例。经办角色是业务视角中的参与者;基线 API 尚未表达操作者身份,Java 测试只能模拟提交行为。设备也只是标识,当前没有设备台账、付款、交付或归还登记。确认预留不能被解释为已经拿到设备。
用例 UC01 的触发事件是提交设备、租期和请求标识。输入需要完整,但“设备空闲”不应成为用例的前置条件,否则最需要讨论的冲突路径会被排除在外。成功保证是新增预留及其成功回执;冲突失败时不新增预留,并记录拒绝结果。非法输入在构造请求或处理请求时抛出异常,不进入这条业务拒绝路径。
从基本场景补出异常和边界
正常场景 S01 使用 2026 年 10 月 3 日 UTC 的 [10:00,12:00)。所有示例都显式使用 UTC,小时精度只是为了阅读方便,代码使用 Instant。
- 经办角色提交请求 A,指定 E1 和租期。
- 系统检查输入以及 A 是否已经处理过。
- 对新请求,系统检查 E1 的有效预留是否与该租期冲突。
- 无冲突时登记有效预留及回执,返回
CONFIRMED。
这个步骤顺序对照了现有代码,尚不承诺并发检查和登记具有原子性。用例能要求防止重复承诺,内存实现能否在多线程下履行它,需要另一组实验。当前单线程检查不能填补那个缺口。
异常场景 S02 从第 3 步分叉:A 已确认,再提交 B,申请 E1 的 [11:00,13:00)。结果为 OVERLAP_REJECTED,B 不占用设备。这里还需要失败后的走向:B 的拒绝回执保留;如果经办角色要在条件改变后重新申请,应创建新请求,而不是无限重放 B。
边界场景只改动一个条件,更容易暴露隐含规则。
| 场景 | 相对已有 E1 [10:00,12:00) 的变化 |
基线结果 | 暴露的问题 |
|---|---|---|---|
| S03 | 新租期改为 [12:00,13:00) |
确认 | 端点相接算不算冲突? |
| S04 | 时间不变,设备改为 E2 | 确认 | 冲突是全局限制还是按设备判断? |
| S05 | 起止相同或结束早于开始 | 非法参数异常 | 空租期和倒置租期有没有意义? |
| S07 | 请求 A 不变,设备改成 E2 | 非法参数异常 | 换设备是同一次重试还是新意图? |
这些场景不靠“系统应正确处理异常”来描述结果。每个场景写出输入、已有事实和终态,才能判断两种候选实现是否等价。若只记录返回值,还可能漏掉失败请求改变了原预留的错误;S07 后面再提交一个与 A 冲突的新请求,必须仍被拒绝。
把规则从步骤里提出来
“检查设备是否空闲”是动作,尚未给出判定标准。可复核的规则是:同一设备的两条有效预留不得有相交租期。术语中的“同一设备”“有效预留”和“相交”都不能省略;否则不同设备会互相阻挡,取消记录也可能永久占用资源。
Business Rules Group 的《Business Rules Manifesto》v2.0 第 2—4 节强调,规则应与流程分离,以术语和事实表达,并区分规则与执行机制。因此,“用 HashMap 保存预留”属于技术选择;它没有说明什么预留才允许存在。原文第 2—4 节
这份租赁材料的来源分两层。A0 指第 00 篇的 models/00/three-models.md 教学需求与场景;A1 指 examples/software-modeling/README.md 的基线契约。公开方法资料只支持表达方法,不替租赁企业决定政策。代码位置用于核对这些约定是否得到实现,也不能反过来把代码中的每个选择解释成业务必然。
| 约定 | 来源 | 能推翻实现的反例 | 当前映射 |
|---|---|---|---|
| R1:同一设备有效预留不得重叠 | A0 教学需求、S00-02 | E1 两个相交租期都确认 | reserve 的设备比较与 Period.overlaps |
| R2:租期开始早于结束 | A1 租期契约 | [10,10) 或 [12,10) 被接受 |
Period 构造器 |
| R3:半开区间,允许相邻租期 | A0 S00-03;A1 | [12,13) 被 [10,12) 阻挡 |
overlaps 的严格小于比较 |
| R4:取消释放有效预留 | A0 S00-06;A1 | 取消后新请求仍被原预留阻挡 | cancel 删除 bookings 条目 |
| C1:同 ID 同负载重放原结果,异负载非法 | A0 S00-04、05;A1 | 同 ID 换设备被接受 | receipts 查询及请求相等比较 |
| C2:取消和资源变化不改写旧回执 | A0 S00-06、07;A1 | 取消后重试重新占位,或旧拒绝变成功 | cancel 保留回执;reserve 先查回执 |
R1—R4 是本案例采用的业务约定,其中 R3 明确排除了清洁缓冲。C1、C2 是应用交互契约,用来规定重试如何解释,不冒充所有租赁业务共同遵循的规则。选择 UUID、数据库、HTTP 状态码或 Java 集合,都还属于实现决定。
来源表能够回答规则为什么存在。它也指出了规则变化的影响:改相邻租期政策要更新 R3 的来源决议、端点场景和区间模型;换掉 HashMap 则可能只改变存储实现。二者不应使用同一套验收理由。
取消后的确认回执意味着什么
S08 把容易混淆的两个状态分开。A 被确认后取消,第一次 cancel(A) 返回 true,第二次返回 false。随后重放 A 仍得到 CONFIRMED,因为这个结果描述 A 最初的处理决定。它并不表示此刻还存在有效预留。
实验还必须提交新请求 F,申请 E1 的原租期。F 能确认,才说明重放 A 没有重新占位。仅断言“取消后 A 返回确认”会让一个错误地恢复预留的实现也通过。
1 | |
S09 使用独立实例:A 先占位,B 因冲突被拒绝;取消 A 后,旧 B 仍返回拒绝,新请求 G 才能确认。此处必须确认唯一的阻挡者已经被移除。若实例里还残留另一条相交预留,就无法判断拒绝究竟来自历史回执还是当前冲突。
基线因此需要分别表达“当前还占用什么”和“过去怎样处理这个请求”。这会影响将来的查询界面:查询当前预留不能简单展示最后一次 reserve 的返回值。目前没有查询 API,也没有界面实验;这里只从反例确定两类信息不能混为一谈。
执行场景并保存证据
模型源文件在 examples/software-modeling/models/01/scenarios-and-rules.md,实验在 labs/01/ScenarioCheck.java。它直接调用累计基线的公开 API,没有另写一套租赁算法来证明自己。运行入口以仓库根目录为当前目录,使用 JDK 21:
1 | |
检查覆盖正常确认、冲突拒绝、相邻区间、设备隔离、非法租期、重放、载荷冲突、取消及旧拒绝回执。断言采用显式 AssertionError,不依赖 -ea;编译或断言失败时脚本返回非零。环境版本、逐项输出和退出码保存在 evidence/01/run.log、evidence/01/exit-code.txt。
本次本地运行通过 20 项检查,末行是 PASS total=20,退出码为 0。它证明这些输入在单线程内存基线上符合表中的约定;没有测试并发、持久化恢复、身份鉴别或真实用户是否认可需求。方法资料核对、场景走查和程序运行分别提供不同范围的证据。
两个仍需回答的问题
取消权限尚无规则来源。承租人、经办人和管理员分别能取消谁的预留?租期开始后能否取消?基线的 cancel(RequestId) 没有操作者或所有权参数,也不读取当前时间,因此无法实施这些限制。未来若加入授权规则,反例应包括无权者取消他人预留后失败,且原预留继续排斥冲突请求。仅返回一个“无权限”错误不足以证明没有误删。
清洁要求来自 A0 的变化卡 C00-01:设备归还后需要半小时清洁。若采用这个候选要求,合同租期 [10:00,12:00) 与资源占用期 [10:00,12:30) 就不同;S03 的 12 点申请将不再能直接确认。还需确定哪些设备需要清洁、清洁能否豁免,以及取消预留是否同时释放清洁占用。把比较符号直接改成小于等于,既不能表达半小时,也会混淆端点约定与新增占用。
这两个问题保留在模型的问题清单中,没有默认答案。后续模型需要增加的概念,分别是取消行为涉及的参与方与权限,以及合同租期之外的资源占用。现有场景可以明确指出新增规则从哪里改变了原行为。
参考资料
-
本篇场景、规则与检查程序:包含模型源文件、Java 基线、20 项场景检查及运行记录;解压后按 README 运行。
-
Ivar Jacobson、Alistair Cockburn:Use-Case Foundation v1.1,第 1、4 页。
-
Business Rules Group:Business Rules Manifesto v2.0,2003-11-01,第 2—4 节。
-
本系列教学工件:
examples/software-modeling/models/00/three-models.md、models/01/scenarios-and-rules.md、rental-core/RentalDesk.java。






