软件建模E06:Feature Model与软件产品线
一套租赁软件同时提供门店自提、配送和跨境租赁,并不意味着每个交付版本都可以随意组合这些能力。配送需要地址,跨境需要配送与保险,取货方式又只能选择一种。只列出功能名称,无法解释为什么某个组合不能交付。
本篇把这些教学政策写成有限特性模型,枚举全部配置并检查反例。实验回答哪些产品组合被允许,不实现跨境业务,也不把一个特性名称视为已经交付的功能。
特性模型描述一族产品
FODA 原始报告通过领域分析寻找相关系统的共性与差异,并使用特性表达可选、必需及替代关系。它讨论的是一族系统的变化空间,不能只把一份待办事项表换个标题就称为产品线。Kang 等,FODA Feasibility Study,1990
租赁样本保留七个布尔变量:Rental、Booking、Pickup、Delivery、Address、Insurance、CrossBorder。Rental 是根,Booking 为根下面的必选能力。Booking 生效时,Pickup 与 Delivery 恰好选一个;Address、Insurance 与 CrossBorder 则可以按配置选择。
树状结构还不够。Delivery 要求 Address,CrossBorder 同时要求 Delivery 和 Insurance;Pickup 与 CrossBorder 排斥。最后一条已经能由取货互斥和跨境依赖推出,但保留它可以直接表达业务政策,并在错误报告中指出冲突双方。冗余规则应有清晰目的,避免两处维护却写出不同含义。
flowchart TD
R[Rental 根必选] --> B[Booking 必选]
B --> P[Pickup]
B --> D[Delivery]
R --> A[Address 可选]
R --> I[Insurance 可选]
R --> C[CrossBorder 可选]
P --- X[Pickup与Delivery恰好一个]
X --- D
D -.->|要求| A
C -.->|要求| D
C -.->|要求| I
这张 Mermaid 图是本篇的关系说明,没有冒充某个特性编辑器的标准文件。可执行事实源是 models/E06/product.json,格式由实验脚本定义,只支持父子、必选、异或组、依赖和排斥。加载器核对重复特性与引用名称;它不是完整产品线语言的语法和语义验证器。
把关系翻译成可检查条件
选中一个孩子必须选中父亲,因此不能在没有 Booking 时启用 Pickup。必选关系反过来要求:当 Rental 被选中时,Booking 也必须存在。两个方向不能混淆;普通可选孩子不应因父节点存在而被自动启用。
异或组的判断需要带上父节点条件。Booking 存在时,两个取货选项的选中数量必须等于一;没有选择任何一个和同时选择两个都失败。依赖用蕴含表达:如果有 Delivery 就必须有 Address。排斥则禁止 Pickup 与 CrossBorder 同时成立。这些条件直接对应 violations 返回的错误标签。
七个变量共有一百二十八种真假赋值。脚本用 Python 标准库逐个构造集合,保留没有违约的配置。这里不需要 SAT 求解器就能把当前空间查完;变量增加后,指数增长会使穷举迅速失去实用性。本篇没有把小样本的完成时间外推成大型产品线的性能结论。
运行得到七个合法产品。其中四个选择自提:地址和保险各自可有可无。三个选择配送:地址必须存在,没有跨境时保险可选,有跨境时保险必须存在。这个分组提供一个独立的人工计数,与程序输出逐项对照,避免只有脚本给自己的结果盖章。
地址在自提版本中允许存在,是当前模型的明确政策。它可以表示资料管理能力,但实验并未实现用途。如果产品负责人要求自提版本绝不包含地址模块,就需要再加排斥约束,合法数量也会变化。发现这种疑问应回到政策讨论,不能偷偷改求解器来凑出预期数字。
非法配置需要定位到关系
第一个反例同时选择自提与配送,违反恰选一项;它还缺少地址,因此报告同时指出配送依赖未满足。一个配置可能有多条错误,不能看到第一条后就假定其他约束成立。
第二个反例只选配送却没有地址,树上的父子关系都成立,跨分支依赖仍然失败。第三个反例包含跨境、配送和地址,却漏掉保险。剩下的样本分别覆盖取货方式空缺、孩子缺父节点以及未知特性名称。合法的最小自提配置也经过同一入口,防止校验器一律拒绝。
flowchart LR
M[特性及关系] --> E[枚举128个赋值]
E --> V[7个合法产品]
E --> F[非法配置及违约标签]
M --> C[变化卡 跨境排斥保险]
C --> N[重新枚举 得6个合法产品]
N --> D[CrossBorder不出现在任何合法产品]
变化卡增加“跨境排斥保险”。旧规则又要求跨境必须有保险,两者组合使跨境无法出现在任何合法产品中。枚举仍得到六个结果,说明整个模型有解;逐特性检查才发现 CrossBorder 成为死特性。只检查“至少存在一个合法配置”,会漏掉这一类变化事故。
脚本实际打印变化前后的配置数量和死特性名称。它没有计算最小不可满足核,错误解释来自声明关系与样本对照。多条规则构成间接矛盾时,产品线工具的诊断能力会比这个简短脚本更丰富;这也是扩大模型后应重新评估工具的具体原因。
三种feature不能混成一张表
产品线特性决定允许交付哪些能力组合。FDD 中的 feature 用于组织有业务价值的设计与构建工作,例如“计算一笔租赁的费用”。它可以帮助实现某个产品特性,但二者没有强制一对一关系。一次交付工作可能同时影响多个产品版本。Palmer,An Introduction to Feature-Driven Development
运行时开关又涉及已经部署的系统如何按时间、租户或流量选择行为。如果一个配送版本正在接受订单,临时关闭地址处理能力,就可能打破该版本依赖关系。实验对全部合法配送配置移除 Address,再次调用同一校验函数,实际观察到全部失败。
这个删除操作只是配置层的反例,没有启动服务或模拟在途订单。真实开关还需要缓存传播、历史数据兼容、回滚及请求一致性机制。当前模型只能给出“哪些组合不能允许”的静态条件,不能证明发布切换过程安全。
把三者分开后,追踪关系反而更明确:产品配置选择 Delivery,交付计划列出实现配送的工作,运行环境再规定何时启用其行为。完成一项 FDD 工作不代表所有组合都验收通过;配置可满足也不代表对应模块已经存在。
配置验收与模型演化
本例没有把七个产品复制成七套源代码,也没有自动生成安装包。输出的是配置集合和违反条件,为后续装配提供输入。若要继续生成,必须再定义每个特性映射哪些模块、配置项、测试与数据迁移,并核对选中能力确实出现在产物中。
模型评审可以围绕变化前后集合进行:哪些旧配置仍合法、哪些配置被移除、是否出现死特性。实验的跨境变化卡明确移除了原来的一个合法配置;没有记录这项差异,就可能让老客户的组合在新模型下悄悄失效。
有限穷举给出的保证也要写全。它遍历的是当前七个变量和当前脚本支持的条件,不包含数量属性、费用预算、动态绑定或多级产品派生。JSON 结构本身没有规范认证,不能声称可被任意 Feature Model 工具直接导入。
装配规则还需要独立验收
允许的配置与实际装配的模块之间,需要一份明确映射。例如选中配送意味着打包地址处理器,同时启用地址验证测试;没有选中配送时,处理器是否仍可存在,则由隔离或复用政策决定。布尔模型只描述能力选择,不规定二进制文件如何裁剪,也不能证明未选能力从最终产物中消失。
升级时还要保存客户原配置,再用新规则逐个重验。某个组合从合法集合中消失,可能需要迁移功能、调整合同或维持旧版本。枚举报告只能发现不兼容,不能替业务决定处理方式。跨境死亡的变化卡尤其说明,总配置仍有六种并不意味着升级对所有客户安全。
若后续引入数量和费用属性,穷举对象也要随之改变。保险额度不是一个真假值,配送半径也不是一个单独标签;把它们继续压成布尔变量,会丢失真正需要判断的阈值。应先明确属性域、单位和约束,再选择能够处理该范围的求解方式。现有脚本保持七变量边界,使每一项结果都能被完整复核。
复跑
下载 实验包,使用 Python 标准库运行,不需要第三方求解器。
1 | |
正常输出包含一百二十八个赋值的汇总、七个合法产品、非法样本及变化卡,十项断言通过后返回零。为独立入口追加 Rental,Booking,Delivery,会因缺少 Address 返回一。PYTHON_BINARY 可以指定解释器,脚本通过自身位置定位模型,不依赖当前工作目录。
原始日志、退出码与锁稿记录保存在 examples/software-modeling/evidence/E06/。产品线配置与主线 FDD章节 可以交叉阅读,但本篇不会把一个静态合法组合报告成已经实现的租赁产品。






