Appearance
业务约束跨层传播:一个数字到底要改几层
“把 15 A 改成 16 A”“把 10 分钟改成 600 秒”“频率上限从 1000 Hz 改到 1500 Hz”看起来都只是改一个数字。
但在实际上位机项目里,同一个规则可能同时存在于:
text
界面提示
输入校验
业务模型
MES / API
设备下发
配置持久化
历史数据显示
测试如果只改其中一层,软件可能出现“界面允许、后端拒绝”“MES 还是旧值”“设备实际没收到新范围”这类很难第一眼发现的问题。
反过来,如果每改一个数字都全仓库搜索、重构所有相似常量,又会把一个 10 分钟任务做成半天。
V10 的办法是先判断:这是 DisplayOnly,还是 DomainConstraint?
1. 第一道判断:DisplayOnly 还是 DomainConstraint
DisplayOnly
只改变展示,不改变系统接受、判断、发送或保存的业务规则。
例如:
- “最大电流”改成“充电电流上限”;
15.0 A改成15 A的显示格式;- 单位标签移到数值右侧;
- 页面说明文字更新;
- 曲线纵轴视觉范围调整,但真实校验和设备范围不变。
默认:
text
Fast / MicroPatch
→ 找直接展示位置
→ 最小修改
→ V0 / V1不要自动扩到 Domain、MES、设备和测试。
DomainConstraint
这个值决定系统允许什么、拒绝什么、何时触发什么行为、向外部系统传什么、向设备实际下发什么。
例如:
- 电压最大允许值;
- 频率合法范围;
- 测试时长上下限;
- 连续 5 次才报警;
- 温度超过 80 ℃触发联锁;
- 参数写 MES 时的单位和范围;
- 设备下发值的倍率和边界;
- 一次测试最大循环次数。
这时走固定传播链,而不是只改 UI。
2. 固定传播链:只沿 5 个层级检查一次
V10 推荐:
text
UI
↓
Domain / Application
↓
MES / API
↓
DeviceApply
↓
FocusedTests这是一条检查链,不是说所有项目一定都有五层,也不是要求每次五层都修改。
原则是:
每层检查一次;确认没有这个约束就停止该分支,不做全仓库无限搜索。
3. 第 1 层:UI 到底只是显示,还是也做输入约束
检查:
Minimum / Maximum;- ValidationRule;
- ViewModel 输入校验;
- Slider / NumericUpDown 范围;
- ComboBox 可选项;
- 提示文字;
- 单位标签;
- 启用/禁用条件。
典型错误:
text
界面提示:允许 0–16 A
实际输入校验:仍然 <= 15 A或者反过来:
text
UI 可以输入 16 A
后端点击“应用”时又报超范围UI 是入口证据,但通常不应该成为唯一业务约束来源。
4. 第 2 层:Domain / Application 才是正式规则吗
重点找:
- 参数模型验证;
- Application Service;
- workflow 前置条件;
- 状态机条件;
- 共享常量;
- 配置 schema;
- 业务对象构造校验。
需要回答:
text
真正决定“这个值是否合法”的 owner 是谁?如果项目已经有稳定 Domain/Application owner,优先修改 owner,再让 UI 和外部边界复用它。
不要为了这次小改临时再造第二套规则来源。
5. 第 3 层:MES / API / 外部接口有没有自己的范围或单位
上位机常见问题:本地已经改了,但接口契约还是旧语义。
检查:
- DTO 字段单位;
- API validation;
- MES 上传/下发范围;
- JSON/XML 字段;
- 数据库接口;
- 外部系统文档;
- 兼容旧版本的转换逻辑。
例如:
text
本地内部:duration = 600 秒
MES 字段:durationMinutes = 10如果只是内部实现从“分钟”改成“秒”,并不代表 MES 契约也应该跟着改。
这里必须区分:
text
InternalRepresentation
ExternalContract不要因为内部重构顺手破坏外部协议。
6. 第 4 层:DeviceApply 才决定真实设备收到什么
涉及设备参数时,最终还要看:
- 下发命令;
- SCPI / Modbus / CAN / 自定义协议转换;
- 倍率、偏移、单位;
- clamp / round;
- 设备自身范围;
- apply 前二次校验;
- 通道映射。
例如:
text
UI 输入:16.0 A
Domain:16.0 A
Device protocol:单位为 0.1 A
实际下发:160如果只改了界面和 Domain,却仍在 DeviceApply 中 clamp 到 150,用户会看到“参数保存成功”,但设备实际只收到 15 A。
设备真实写操作是否安全,仍需遵守人工确认边界。
7. 第 5 层:FocusedTests 只保护这次真正改变的语义
DomainConstraint 很适合 focused test,因为它通常有清晰边界。
例如上限从 15 A 改到 16 A:
text
15.999 A → Accepted
16.000 A → Accepted
16.001 A → Rejected如果有设备编码:
text
16.0 A → DeviceRaw 160如果有外部契约:
text
External DTO 保持原单位/字段语义不需要因为改一个阈值自动跑整个测试仓库。
8. 一个实战例子:10 分钟改成 600 秒
这类任务最危险的地方不是数值本身,而是语义层变化。
先画最短链:
text
UI 输入
→ ViewModel
→ Domain/Application
→ 配置持久化
→ MES/API
→ Timer / Workflow
→ 历史显示然后逐层问:
| 层 | 原语义 | 新语义 | 是否应该改 |
|---|---|---|---|
| UI | 用户输入分钟 | 用户仍输入分钟 | 否 |
| 内部模型 | minutes | seconds | 是 |
| 配置文件 | 历史字段是 minutes | 兼容旧配置 | 视兼容策略 |
| MES | minutes | 外部契约仍 minutes | 否 |
| workflow | TimeSpan.FromMinutes | seconds 表示 | 是 |
| 历史显示 | 显示分钟 | 仍显示分钟 | 否 |
这说明“底层改秒”不等于“全系统字段都改秒”。
真正需要传播的是业务含义,不是机械改变量名和数字。
9. 一个实战例子:频率上限 1000 Hz → 1500 Hz
先判断用户到底说的是:
正式规格修改
text
以后系统正式支持到 1500 Hz→ DomainConstraint。
检查:
text
UI range
→ Domain validation
→ MES/API
→ Device capability / apply
→ focused boundary tests临时边界实验
text
先放开到 1500 Hz,我测一下→ ExperimentalChange。
优先:
text
engineering override
→ 窄入口
→ 正式 Domain/MES 不变
→ 测试结束恢复不要把实验条件误升级为正式产品规格。
10. 一个数字出现很多次,不能靠“全部替换”解决
全局搜索看到:
text
1000
1000
1000
1000不能直接全部替换成 1500。
这些数字可能分别表示:
- 频率上限 1000 Hz;
- 超时 1000 ms;
- 缓冲区 1000 点;
- 测试样本 1000 次。
正确做法:
text
先找业务 owner
→ 再看 direct consumers
→ 每层确认语义
→ 只改同一个 constraint 的传播点11. 配置项是不是 DomainConstraint
配置文件里有一个数值,不代表它天然是正式业务规则。
可以分:
text
UserPreference
DisplaySetting
EngineeringOverride
DomainConstraint
DeviceCapability
ExternalContract例如:
- 曲线刷新 200 ms:UI/性能设置;
- 最大高压 3000 V:可能是 DomainConstraint + DeviceCapability;
- 调试模式允许 3300 V:ExperimentalChange / EngineeringOverride;
- MES 字段单位:ExternalContract。
分类以后才知道改动应该传播到哪里。
12. 新旧数据和历史兼容不要遗漏
如果 DomainConstraint 进入配置、数据库、历史结果,需要额外问:
- 旧配置还能不能读;
- 旧值是否需要迁移;
- 新版打开旧测试记录时单位是否正确;
- 序列化字段是否保持兼容;
- 导出文件是否改变含义。
这时验证通常至少 V2。
但是如果约束从未持久化,就不要为了“保险”自动做数据库迁移设计。
13. 最小检查模板
yaml
Constraint:
BusinessMeaning:
Change:
Classification: DisplayOnly | DomainConstraint | ExperimentalChange
UI:
Present: Yes/No
ChangeNeeded:
DomainApplication:
Owner:
ChangeNeeded:
MES_API:
Contract:
ChangeNeeded:
DeviceApply:
ConversionOrClamp:
ChangeNeeded:
PersistenceCompatibility:
Relevant: Yes/No
Action:
FocusedVerification:
- boundary case
- conversion case
- compatibility case if relevant
NonGoals:14. 可直接复制的 Prompt
text
$host-computer-dev
我要修改一个现有上位机参数/阈值/范围。
当前规则:
【填写】
目标规则:
【填写】
请先判断这是 DisplayOnly、DomainConstraint 还是 ExperimentalChange。
如果是 DisplayOnly,按 MicroPatch 处理,不扩大范围。
如果是 DomainConstraint,只沿下面固定链检查一次:
UI → Domain/Application → MES/API → DeviceApply → FocusedTests。
要求:
1. 先找真正拥有该业务规则的 owner;
2. 不要把全仓库所有相同数字机械替换;
3. 明确内部表示和外部契约是否相同;
4. 涉及设备时检查倍率、单位、clamp 和真实能力;
5. 涉及历史配置/数据时才检查兼容;
6. 输出哪些层需要改、哪些层明确不需要改;
7. 完成后只做与这次 constraint 匹配的 focused 验证。15. 常见反模式
| 反模式 | 后果 |
|---|---|
| 只改 UI 提示 | 后端仍拒绝新值 |
| 只改后端,UI 范围没改 | 用户无法输入合法新值 |
| 内部单位改了,MES 也机械改 | 破坏外部契约 |
| 忽略 DeviceApply 的 clamp | 设备实际仍收到旧范围 |
| 全局替换相同数字 | 修改无关语义 |
| 临时测试直接改正式 Domain | 实验条件污染产品规格 |
| 每个数字都全仓库扫描 | 小改成本失控 |
下一步
- 行为后果、连续次数、Reset:行为与告警语义
- 临时实验条件:功能修改最小流程
- Raw/工程量/单位:测量值语义
- 测试强度:AI 辅助 C# 测试与回归
- 外部协议与设备下发:串口 / TCP 通信任务模板
一句话原则
先判断一个值是“显示”还是“规则”;是规则就沿固定业务链检查一次,不漏层,也不全仓库漫游。