5.2 模型驱动设计:从模型到代码


5.2 模型驱动设计:从模型到代码

本节摘要:模型驱动设计(Model-Driven Design)的主张只有一句:模型与代码必须是同一个东西的两种视图,改一个必须改另一个。本节先诊断这条反馈链最常见的两处断点——分析麻痹与直接撸码——再用青柚商城"取消订单"逻辑的两版演进,示范代码怎么反过来修正模型,以及什么叫"更深的模型"。

两处断点

工作坊结束、聚合开始动工,模型与代码的关系进入考验期。绝大多数团队的失败形态收敛为两种。

断点一:分析麻痹。模型挂在墙上持续打磨,版本从 1.0 推到 4.0,代码迟迟不动——"等模型定稿再开工"。这个等待永远等不到:模型的价值只有被实现、被真实数据冲击之后才能验证,纸面上不存在"定稿"。青柚商城最早的一版订单模型在纸面上非常漂亮,动工两周就被一个真实场景击碎:分期订单的取消要按期数拆退——这个规则任何纸面推演都推不出来,只有接了真实分期数据才会显形。

断点二:直接撸码。与前者相反,代码飞快往前跑,模型文档停在 1.0。三个月后新人读代码学业务,老人凭记忆讲规则,模型成了考古层的地层名。1.4 诊断过的失焦在流程上重演:代码在演化,认知却不再更新。

两种断点的治理是同一张药方:把"模型—代码"当成一条双向回路跑起来,任何一侧的改动都必须在另一侧留痕。 模型改了,当周的代码评审必须能指出对应实现;代码里发现规则不对,第一动作是改模型(词汇表、便签墙的拍照件),第二动作才是改代码。回路转起来之后,两种断点同时消失。

断链形态 典型症状 治理动作
分析麻痹 模型版本多、代码零提交;争论"该不该有"超过两天 强制小步实现:每周必须有一段模型落成代码并演示给业务方
直接撸码 文档与实现脱节;新人只能靠读代码学规则 代码发现规则即回改模型:词汇表与实现同一次提交
双向循环 每轮迭代同时有模型修订与代码落地;业务方看过演示 维持:把"演示给业务方"设为迭代的完成定义之一

代码反哺模型:一次完整回路

回路的质感要看实例。青柚商城"取消订单"这条逻辑,经历了从翻译式实现到深模型的两版演进,第二版就是代码反哺模型的产物。

第一版是典型的"翻译式":把工作坊便签逐字翻译成代码——

// 第一版:忠实翻译便签,规则散在调用方 public void cancel(TradeOrderId id, String reason) { TradeOrder order = orders.findById(id); if (order.status() != TradeStatus.WAIT_PAYMENT && order.status() != TradeStatus.PAID) { // 什么单能取消 throw new IllegalStateException("当前状态不可取消"); } if (order.status() == TradeStatus.PAID && order.paidAt().isAfter(now().minus(30, MINUTES))) { // 已支付限时取消 refundService.quickRefund(order); // 快速退款 } else if (order.status() == TradeStatus.PAID) { auditService.submitManualRefund(order, reason); // 走人工 } order.setStatus(TradeStatus.CLOSED); orders.save(order); events.publish(OrderCancelled.of(order, reason)); }

能用,但两个毛刺在上线后第一周就冒头。毛刺一:客服工单侧也要判"什么单能取消"(展示取消按钮),把同样的状态条件抄了一份——1.4 的老病复发。毛刺二:接入分期订单后,"已支付限时取消"对分期不成立,第一个 if 立刻变特例堆。代码先于模型暴露了规则的形状:真正的业务规则其实有两条独立的线——"这张单允许被取消吗"(资格)与"取消后钱怎么回来"(清算)。第一版把两条线揉在一个方法里,所以每个新场景都在加特例。

第二版先回改模型:跟运营主管确认后,词汇表新增两个词条——可取消资格(哪些状态、什么时限内可以取消)与取消清算方式(原路快退、人工退、按期拆退)。然后代码按两个概念重组:

// 第二版:模型先改,代码跟着长出两个概念 public class TradeOrder { public Cancellation quoteCancellation(Instant at) { // 资格:可询问、可展示 return switch (status) { case WAIT_PAYMENT -> Cancellation.eligible(ClearingPlan.none()); case PAID when at.isBefore(paidAt().plus(payable.cancelWindow())) -> Cancellation.eligible(clearingFor(at)); // 清算方式由订单自己回答 default -> Cancellation.ineligible(reasonOf(at)); }; } public OrderCancelled cancel(Cancellation approval) { // 执行:凭资格操作 if (!approval.eligible()) throw new NotCancellableException(id, approval.reason()); this.status = TradeStatus.CLOSED; return OrderCancelled.of(this, approval.plan()); } } // 客服工单侧的按钮判断,直接问模型,副本消失: boolean showCancelButton = order.quoteCancellation(now()).eligible();

重组后的收益超出预期。客服侧的重复判断消失——它直接调用 quoteCancellation,资格规则从此只有一个家。分期特例有了去处——清算方式是订单对"钱怎么回来"的回答,按期拆退只是 clearingFor 的一种返回,不再污染资格判断。这一版里出现的"资格与清算"两个词,从此进了词汇表,工作坊墙上也补了对应便签——回路闭合的标志就是:代码长出的概念被业务方认领,进入双方共用的语言。

深模型:让规则找到更短的表述

上面那次演进的实质,是模型变"深"了。Evans 对深模型的定义值得背下来:深模型不是复杂的模型,而是用更少的概念覆盖更多场景的模型。"资格与清算"两个概念,覆盖了普通单、限时单、分期单、赠品类目单——而第一版的每个场景都要一个新 if。判断模型深浅的土办法:下一个新需求到来时,它是被现有概念自然消化(换参数、换策略),还是必须加特例分支? 连续三个需求都靠加特例,模型就该回炉了——通常意味着某个隐含的业务概念还没被命名。

💡 找隐含概念的技巧是听业务的副词和转折:"只有……才""其实……的话""按理说应该"。5.1 那句"券过期了也不作废,只是不许用"就是转折句里藏着的概念——过期券是一种状态而非一种删除。副词转折出现的地方,往往就是模型该挖深的地方。

带走的判断

  • 模型与代码是同一个东西的两种视图:任何一侧改动必须在另一侧留痕,回路一断就是断点。
  • 分析麻痹靠"每周必有落地"破,直接撸码靠"发现规则即回改模型"破,两者都以"演示给业务方"为闭环。
  • 代码先于纸面暴露规则形状:当特例开始堆积,先回改模型命名隐含概念,再动代码。
  • 深模型是更少概念覆盖更多场景;业务语言的副词与转折句,是挖深方向的探针。

单点的方法都齐了。5.3 把镜头拉远:五个月的完整历程摊开在一张时间线上,看看这些方法拼成整体时是什么样子。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U