
做了几年半导体可靠性测试机的上位机系统有一件事我印象特别深一块测试台架上温度采集模块、电源控制模块、报警处理模块、流程控制模块四个 DLL 互相引用。某次为了满足一个新的测试规范需要在“超温持续三秒”时先抓一帧数据再切断电源我连着改了温度类、电源类、采集类还顺带动了报警模块的接口。改完那天下班前我在车上坐了很久脑子里只有一个念头这种多对多的联动调用不能再继续下去了。后来在一套四箱体老化监测系统的重构里我把交互逻辑整体迁到了中介者模式Mediator Pattern上。整个上位机系统的联动规则终于有了一个可以单独查看、单独测试、单独修改的地方。这篇文章想把这段实践完整讲清楚中介者模式在半导体可靠性测试机上位机系统里的详细实现包括为什么这个场景非用它不可、代码骨架怎么搭、以及产线上跑久了踩到的坑。1. 可靠性测试机的软件复杂性为什么交互规则会失控1.1 一台测试机上位机到底要协调多少设备先别急着谈设计模式回到物理现场看看半导体可靠性测试机是怎么运转的。可靠性测试和功能测试不同它不是跑几分钟就结束的事。高温存储要几百小时HTOL 老化测试要加压加温跑上千小时温循环要按规范反复升降温。整个过程里上位机扮演的角色不是“发几条命令”这么简单它要持续调度一堆硬件设备保证测试条件不偏离规范区间。以我参与过的四箱体 HTOL 老化系统为例单台上位机要管理四台温箱每台有独立的加热/制冷PID回路和过温保护每台温箱里 8 个测试工位每个工位对应一路直流电源或 SMU多路温度采集仪负责监控 PCB 板卡上几十个点位的实时温度报警单元声光报警、远程通知、日志记录都挂在这上面流程控制器负责配方解析、阶段跳转、暂停恢复UI 界面需要实时刷新每个通道的温度、电压、电流和测试状态这还不算数据管理、报表导出和数据库落盘。一句话总结上位机就是一个同时盯着几十个动态变量、几百小时不出错、出了错要按规范自动处置的监控系统。1.2 没有中介者时的网状调用有多痛苦我最早接手这套系统的代码时整个架构是典型的“谁跟谁有关系谁就互相持有引用”。温度控制器需要报警于是它直接 new 了报警模块在超温分支里调AlarmManager.Raise()温度控制器还要断电于是它又持有电源模块的引用在超温分支里调PowerSupply.ForceOff()电源模块断电后要刷新流程状态于是它又回头调流程控制器的Pause()UI 想展示这一切于是它又持有所有模块的引用在每个状态变化后手动刷新。表面上每个模块都“各司其职”实际上耦合已经乱成一团。为了满足“超温持续三秒断电且断电前先采一帧数据”这种新规范我得顺着调用链一路改下去温度控制器的超温逻辑、报警模块的触发条件、电源模块的接口签名、采集模块的快照命令、流程控制器的暂停时序。改完一处编译错误和逻辑漏洞会从另一个地方冒出来。这种网状架构的真正问题不是代码多而是没有一处地方能完整回答“当温度超限时系统到底会按什么顺序做什么”。在互联网软件里这是维护性烦恼在测试机上这就是安全风险——某次联动漏掉一个分支可能意味着整批被测器件在异常状态下多跑了几个小时。1.3 观察者、事件总线和中介者我为什么选了中介者当时摆在面前的有三条路。第一条是观察者模式。让温度控制器发布超温事件报警模块和电源模块各自订阅、各自处理。听起来很解耦但我很快意识到一个问题订阅关系散落在各模块的构造函数里真正的联动时序还是看不见。A 和 B 都订阅了同一个事件谁先执行、谁后执行代码里没有显式答案。设备联锁最怕的就是时序不确定。第二条是引入事件总线。相当于全局的消息中转站任何模块都可以向总线发布事件任何模块都可以订阅。比观察者更进一步但这套机制在工业设备里有个矛盾点事件总线为了通用性通常不关心业务语义而测试机的联动规则恰恰是强业务、强顺序、强约束的。第三条就是中介者模式。它不追求谁都能跟谁通信而是明确指出“所有交互都通过一个中介者进行”。联锁规则集中在一个类里时序是显式的逻辑是可以写单测的。哪怕后续需求频繁变变也只是改一个地方。我最后选了第三条。核心理由只有一句话**可靠性测试机最需要的不是解耦本身而是让联锁行为可预期、可审计、可测试。**中介者模式恰好把这一点变成了架构默认值。2. 中介者模式的核心思想与领域映射2.1 原理把多对多通信改成多对一中介者模式的定义本身不难用一个中介对象封装一组对象之间的交互。各对象不再显式互相引用而是统一向中介者发送请求由中介者决定把这些请求路由给谁、按什么顺序执行。用生活类比就很好懂。没有中介时你想租一间房得自己联系房东、约时间、谈合同、处理纠纷每个房东你都要单独对接一遍。有了中介你只跟中介沟通中介再去协调房东、物业和相关部门。你不需要知道房东有几个房东也不需要知道房客是谁。对应到软件结构上就是把原来的“多对多”依赖网收紧成“多对一”的星型结构原来的 A 调 B、B 调 C、C 调 A变成 A 发消息给 MediatorMediator 再决定要不要通知 B 和 C各模块之间不直接持有引用只持有中介者的引用新增联动时也不改各模块之间的调用链只在中介者里加一条路由规则2.2 中介者模式和观察者模式到底有什么本质区别这是我在评审代码时经常被问到的问题。两者看起来很接近——都是通过一个中间对象传递消息但背后的交互模型是不同的。我用一张表来对比维度中介者模式观察者模式交互方向同事对象显式向中介者发消息由中介者路由给目标发布者广播事件订阅者自行决定是否处理时序控制中介者可以明确规定执行顺序订阅顺序通常由注册顺序决定不够显式依赖关系同事对象知道自己被哪个中介者管理订阅者可能完全不知道发布者是谁调试友好度路由规则集中日志链路清晰订阅关系分散排查时需全局搜索典型场景业务流程编排、设备联锁、复杂交互规则UI 事件刷新、状态通知、一对多广播拿界面刷新举例观察者模式很舒服数据变了谁关心谁刷新订阅关系不重要。但拿“超温断电、暂停流程、抓取快照、通知远程”这套联锁来说观察者模式就撑不住——因为这是一组必须按特定顺序执行的强流程不是一帮模块各干各的。2.3 映射到测试机场景的四个关键设计决策理论落到项目里要做几个务实的决策。决策一中介者不碰硬件。中介者只协调业务对象不直接操作串口、GPIB、PCIe 这些底层通道。硬件的协议细节留在设备驱动类里中介者只关心“事件发生了应该通知谁”。这样中介者才能脱离硬件单独测。决策二所有交互通过事件参数对象传递而不是散装方法参数。温度超限事件携带环境温度、报警阈值、持续时长流程暂停事件携带当前配方名、阶段序号。数据打包成一个DeviceEvent对象既方便路由也方便记录日志。决策三注册表模式支持运行时增删设备。半导体测试机经常出现“这次跑 4 箱体下次跑 6 箱体”“这个通道有器件那个通道空着”的情况。中介者用设备名注册表管理对象而不是写死在构造函数里。决策四同步联锁为主异步日志为辅。联锁动作必须同步执行完确保断电发生在流程暂停之前但日志落盘、远程通知这类操作不阻塞主链路可以异步处理。3. 代码实现一套可落地的多箱体老化机方案3.1 工程目录怎么分层我采用的方案是基于 C# 和 .NET 的上位机框架前后端在同一进程里设备驱动通过抽象接口与业务层隔离。工程目录大致是这样ReliabilityTester/ ├── Mediator/ │ ├── ITestMediator.cs │ ├── TestMediator.cs │ ├── DeviceEvent.cs │ └── DeviceNames.cs ├── Devices/ │ ├── ITestDevice.cs │ ├── TemperatureController.cs │ ├── PowerSupplyController.cs │ ├── DataAcquisitionUnit.cs │ └── AlarmManager.cs ├── Flow/ │ ├── TestFlowController.cs │ └── RecipeRunner.cs └── Tests/ └── MediatorInterlockTests.csMediator放中介者核心Devices放设备模块Flow放流程编排Tests放联动逻辑的单测。分层的核心原则是设备模块不知道 Mediator 内部实现Mediator 也不知道硬件协议。3.2 核心接口与事件参数的设计先定同事对象的契约。所有要接入中介者的模块都实现ITestDevicepublic interface ITestDevice { string DeviceName { get; } void BindMediator(ITestMediator mediator); void HandleEvent(DeviceEvent evt); void Start(); void Stop(); }DeviceName用来注册和路由匹配BindMediator在注册时由中介者调用HandleEvent处理别人发来的事件Start和Stop管理设备生命周期。中介者接口保持精简public interface ITestMediator { void Register(ITestDevice device); void Route(DeviceEventType evtType, string targetDevice); void Send(DeviceEvent evt, ITestDevice sender); }Register注册设备Route配置路由关系Send发布事件。注意这里我把“路由配置”暴露成了公开方法而没有隐藏在中介者内部——这不是偷懒而是为了让联动规则肉眼可见。事件参数采用强类型枚举加数据对象public enum DeviceEventType { TemperatureOverLimit, StageChanged, FlowPaused, FlowResumed, DataSnapshotRequested } public sealed record DeviceEvent( DeviceEventType EventType, string SourceDevice, DateTime Timestamp, object? Payload null);用record是因为事件对象具备值语义日志里直接打印比较方便。Payload用object是为了容纳不同事件的附加数据虽然牺牲了一点类型安全但换来路由系统的通用性。工业代码里我偏向这种务实的半强类型方案。3.3 TestMediator 的注册与路由实现中介者本体是最核心的部分我按“查表路由”而不是“if-else 判断”来实现public sealed class TestMediator : ITestMediator { private readonly Dictionarystring, ITestDevice _devices new(); private readonly DictionaryDeviceEventType, Liststring _routes new(); private readonly object _lock new(); public void Register(ITestDevice device) { lock (_lock) { _devices[device.DeviceName] device; device.BindMediator(this); } } public void Route(DeviceEventType evtType, string targetDevice) { lock (_lock) { if (!_routes.TryGetValue(evtType, out var list)) { list new Liststring(); _routes[evtType] list; } if (!list.Contains(targetDevice)) list.Add(targetDevice); } } public void Send(DeviceEvent evt, ITestDevice sender) { Liststring targets; lock (_lock) { if (!_routes.TryGetValue(evt.EventType, out var list)) return; targets new Liststring(list); } foreach (var name in targets) { if (_devices.TryGetValue(name, out var target)) target.HandleEvent(evt); } } }几个实现细节值得说明。第一Send里先锁着拿到目标设备名单然后立刻释放锁再逐个调用。如果不复制快照而是直接在锁内遍历调用很容易出现死锁——因为HandleEvent里可能反手又调用Send锁是可重入的还好但如果有异步线程就在中间卡死。复制快照虽然多了一点开销但稳定第一。第二路由表是显式配置出来的。比如在RecipeRunner启动时写_mediator.Route(DeviceEventType.TemperatureOverLimit, DeviceNames.PowerSupply); _mediator.Route(DeviceEventType.TemperatureOverLimit, DeviceNames.AlarmManager); _mediator.Route(DeviceEventType.TemperatureOverLimit, DeviceNames.DataAcquisition); _mediator.Route(DeviceEventType.StageChanged, DeviceNames.Temperature); _mediator.Route(DeviceEventType.StageChanged, DeviceNames.PowerSupply);任何人打开启动代码一眼就能看出“温度超限影响谁、阶段变更影响谁”。这正是我要的可审计性。第三设备名字段集中管理不用裸字符串public static class DeviceNames { public const string Temperature TemperatureController; public const string PowerSupply PowerSupplyController; public const string DataAcquisition DataAcquisitionUnit; public const string Alarm AlarmManager; public const string Flow TestFlowController; public const string Ui MainWindowViewModel; }3.4 设备端怎么和中介者协作以温度控制器为例它负责定时采集温箱温度、执行内部 PID 控制同时在超限时触发事件。public sealed class TemperatureController : ITestDevice { public string DeviceName DeviceNames.Temperature; private ITestMediator _mediator; public void BindMediator(ITestMediator mediator) { _mediator mediator; } public void HandleEvent(DeviceEvent evt) { if (evt.EventType DeviceEventType.StageChanged) { // 根据测试阶段切换温箱目标温度 var stage evt.Payload?.ToString(); ApplyStage(stage); } } private void OnOverLimit(float currentTemp, float threshold) { _mediator.Send( new DeviceEvent( DeviceEventType.TemperatureOverLimit, DeviceName, DateTime.Now, new { CurrentTemp currentTemp, Threshold threshold }), this); } }温度控制器内部只管检测温度和触发事件它不知道谁会响应、以什么顺序响应。收到StageChanged事件时它按阶段调整目标温度其他模块的事一概不管。再看电源模块。它的大部分业务逻辑都在HandleEvent里处理其他模块发来的事件public sealed class PowerSupplyController : ITestDevice { public string DeviceName DeviceNames.PowerSupply; public bool ForceOffTriggered { get; private set; } public void BindMediator(ITestMediator mediator) { } public void HandleEvent(DeviceEvent evt) { if (evt.EventType DeviceEventType.TemperatureOverLimit) { ForceOff(); ForceOffTriggered true; } else if (evt.EventType DeviceEventType.StageChanged) { // 升温阶段加大电流测试阶段按配方输出 } } private void ForceOff() { // 实际发送硬件命令到直流电源这里省略协议细节 } }注意PowerSupplyController里没有对温度模块的任何引用它知道自己正在处理“超温”这个事件但不知道是谁发来的。这种单向依赖让每个设备模块都变得非常好测——构造一个中介者注册几个假模块发一条事件断言状态变化即可。3.5 经典中介者与我这个变体的差异教科书里的中介者模式通常把交互逻辑直接写在中介者类里由中介者调用同事的具体方法public void Send(string message, Colleague colleague) { if (colleague tempController) powerSupply.ForceOff(); }这种方式在交互关系稳定、同事数量固定的场景下完全没问题。但半导体测试机不满足这个前提通道数量随测试板卡变化设备可能动态上下线配方不同导致联动规则不同。所以我改成了“路由表 事件对象”的注册表式中介者把“事件到目标设备”的映射变成数据配置而不是编码在逻辑里。相对代价是路由规则不像经典中介者那么直观地集中在一个Send方法里。我的补偿措施是所有路由配置集中在RecipeRunner启动阶段并且配套一份静态路由表文档。这样既保留了中介者的集中编排优势又支持了运行时的灵活性。4. 联动场景实录中介者怎么协调一次完整处置4.1 温度超限联锁先断电、再采集、后暂停的顺序怎么保证超温联锁是最不能出错的场景。假设 2 号温箱在老化测试中温度突破了 135°C 警戒线系统必须在最短时间内完成一系列动作。没有中介者时这份逻辑散落在温度控制器、电源模块、采集模块和流程控制器的各自代码里。更可怕的是它们的调用顺序是靠“谁先被通知”决定的而不是靠业务顺序决定的。中介者方案把顺序固化在路由表里_mediator.Route(DeviceEventType.TemperatureOverLimit, DeviceNames.PowerSupply); _mediator.Route(DeviceEventType.TemperatureOverLimit, DeviceNames.DataAcquisition); _mediator.Route(DeviceEventType.TemperatureOverLimit, DeviceNames.Flow); _mediator.Route(DeviceEventType.TemperatureOverLimit, DeviceNames.Alarm);路由表按Dictionary的Liststring顺序执行也就是电源断电 → 采集快照 → 暂停测试流程 → 发出报警。先断电再采集是为了确保在安全供电状态下抓取最后一帧数据采集完再暂停流程是为了让流程状态的现场信息完整落盘。var evt new DeviceEvent( DeviceEventType.TemperatureOverLimit, DeviceNames.Temperature, DateTime.Now, new { BoxId 2, Temp 137.2f, Threshold 135.0f, DurationSec 3 }); _mediator.Send(evt, temperatureController);各模块在自己的HandleEvent里收到事件。报警模块很快直接发消息和声光报警采集模块把当前各通道电压电流打入一张快照表流程控制器把当前阶段置为FaultHalt。整个过程不需要温度控制器知道任何目标模块的接口签名。4.2 配方阶段切换升温、保温、测试、降温的无缝衔接老化测试不是恒压跑到底而是按配方分阶段升温阶段温箱从室温升到 125°C电源输出逐渐加到额定值保温阶段维持温度让各工位热平衡测试阶段开始周期性的电压电流采集和功能检测降温阶段测试结束按规范降温到安全温度RecipeRunner负责解析配方、推进阶段。每个阶段切换时它向中介者发送StageChanged事件public sealed class RecipeRunner { private readonly ITestMediator _mediator; public void AdvanceStage(string stageName) { _mediator.Send( new DeviceEvent( DeviceEventType.StageChanged, DeviceNames.Flow, DateTime.Now, stageName), this); } }温度控制器收到StageChanged后调整温箱目标温度电源模块收到事件后切换输出模式采集模块收到事件后决定采集频率UI 收到事件后刷新阶段指示。这种广播式的中介者交互和观察者模式看起来很像但区别在于所有模块响应哪个阶段、响应顺序如何都可以在中介者的路由配置和模块代码里逐一核对。如果某个阶段有模块没正确响应单测里立刻就能发现。4.3 用户暂停与异常恢复状态一致性处理测试过程中操作员可能因为检查器件、开门操作等原因手动暂停。暂停不能只是“停止输出”这么简单。电源应该保持安全输出还是直接关闭温箱温度要不要维持当前测试数据要不要落盘这些决定如果让 UI 直接调用各模块又得写一圈散装代码。中介者的做法是让 UI 发一个FlowPaused事件需要处理暂停的模块各自响应流程控制器记录当前配方行和阶段序号电源模块切换到保持模式不再改变输出温度控制器维持当前目标温度采集模块停止常规记录只保留告警监控恢复时发送FlowResumed流程控制器校验各模块状态快照确认一致后才继续推进。有一次产线门没关好导致中断恢复电源模块还在保持模式但流程控制器已经跳到下一阶段。加了这套媒介协调后恢复前会先向电源模块查询是否处于保持模式不一致就直接进入错误处理流程不再往前跑。4.4 高频采集数据为什么不走中介者这一点是我特别想强调的边界。老化测试的数据采集频率通常在 100ms 到 1s 之间部分特殊测试会到 10ms 级别。如果把每条采集数据都封装成事件、通过中介者广播系统会在短时间内产生大量对象分配和队列堆积GC 压力直接拉高上位机界面都可能卡顿。所以我在架构里明确划了一条线业务事件温度超限、阶段切换、暂停恢复、报警触发走中介者因为频率低、联动强原始采集数据电压、电流、温度点走专门的数据管道用环形缓冲或双缓冲队列直接投递到存储层和 UI 图表// 错误示范高频数据走事件路由 mediator.Send(new DeviceEvent(DeviceEventType.DataSample, ..., sample)); // 正确做法原始数据走专用管道 _dataPipe.Enqueue(sample);这条边界避免了“中介者变成性能瓶颈”的经典陷阱。中介者处理的是秒级甚至分钟级的事件数据管道处理的是毫秒级的采数流两者各司其职。5. 上线以来的踩坑记录与调优经验5.1 中介者膨胀路由表从清爽变成一团乱麻系统跑了一个多月后路由表开始失控。温度相关的路由有七八条流程相关的又有五六条报警相关的还往里塞逻辑。中介者类本身没变大但RecipeRunner里的路由配置已经超过四十行。每加一个需求先要翻半天路由表看新事件该路由给谁会不会和已有事件冲突。我用的第一个解法是路由与编排分离。路由表只负责“什么事件发给谁”具体的处理逻辑分别在各个模块的HandleEvent里。如果发现某类联动规则本身很复杂就单独抽一个协调类让它也实现ITestDevice作为中介者路由链上的一个特殊节点。第二个解法是按领域拆分中介者实例。测试机里天然存在几个交互域温度域的联锁温箱、温度采集、电源流程域的编排配方、阶段、暂停恢复监控域的通知报警、日志、远程推送我创建了三个TestMediator实例分别管理各自域的注册与路由。跨域的交互仍然存在但通过DomainBridge这类薄层做显式转译。这样一来单个中介者的路由规模回到可控范围启动代码也更容易读懂。5.2 循环通知A 触发 BB 又触发 A差点死循环这个坑很典型。电源模块在ForceOff后发了一个PowerSupplyInterlockChanged事件结果这个事件被路由到了流程控制器流程控制器暂停后又发了一个FlowPaused事件而这个事件恰好被路由给了电源模块。两个模块互相触发导致事件风暴。排查时我用事件序列号把日志完整打了出来才看清循环链路。解决办法有三层第一层给DeviceEvent增加SequenceId每条事件链都有唯一编号第二层中介者里做简单的链条深度限制超过 20 跳直接丢弃并告警第三层从根上梳理路由表删掉那些“只是想知道别人状态但并不需要硬联动”的冗余路由我自己的经验是第三层最治本。很多循环通知的根源不是中介者问题而是路由配置拍脑袋加多了。每次加路由前先问一句“这个模块真的必须在事件发生时同步干活吗”多数时候答案是否定的。5.3 线程安全设备回调来自串口线程、定时器线程和 UI 线程上位机里天然存在多线程环境串口接收线程、GPIB 轮询线程、UI 调度线程、数据采集线程。设备和中介者的交互散落在这些线程里稍不注意就会出现竞态。我的处理策略如下路由表的读写全部加锁保证配置变更安全Send方法先拷快照再通知避免回调过程中路由表变化导致漏发或错发联锁类事件通过SynchronousPosting同步执行确保“断电已完成”这个事实在后续逻辑成立日志类、界面刷新类事件交给线程池异步处理不阻塞业务链路这里给一个容易踩的细节不要在设备的HandleEvent里直接操作 UI 控件。联锁事件发生在后台线程直接改 UI 会触发跨线程异常。我统一在HandleEvent里更新模块状态字段然后由 UI 层通过PropertyChanged或定时刷新读取最新状态。5.4 模拟设备与单元测试联锁逻辑的可回归防线中介者架构带来一个额外的好处设备模块可以很轻松地被替身替换。我把真实设备驱动和模拟设备做成两个实现生产环境挂真实驱动测试环境挂模拟驱动。联动逻辑的单测长这样[Fact] public void 温度超限事件应触发电源联锁() { var mediator new TestMediator(); var psu new PowerSupplyController(); var temp new TemperatureController(); mediator.Register(psu); mediator.Register(temp); mediator.Route(DeviceEventType.TemperatureOverLimit, psu.DeviceName); temp.SimulateOverLimit(137.2f, 135.0f); Assert.True(psu.ForceOffTriggered); }类似的用例覆盖了“超温后流程必须暂停”“阶段切换后电源输出模式正确”“连续两次超温不重复触发断电”等回归场景。产线上的故障大多出在联锁上有这套测试打底后续改路由表时心里有底多了。6. 中介者的边界什么时候不该用它6.1 哪些场景引入中介者反而更糟中介者模式不是银弹我在项目里也总结过几个“别硬上”的信号。模块只有两三个交互关系一眼到底。比如一个主窗体和一个工具条直接引用比绕一圈中介者更清晰。通信频率极高、数据密集。原始采集数据这类场景专用管道是正解。强行套中介者只会带来严重的分配开销和 GC 压力。本身语义就是一对多广播。界面刷新这种“谁关心谁订阅”的弱联动观察者模式比中介者自然得多。没有跨模块顺序要求的交互。如果一个事件只需要让某个独立模块自己处理不需要编排那直接调用就好。判断标准很简单如果你不需要显式控制“谁先做、谁后做、做错了怎么回滚”那你可能不需要中介者。反过来一旦你的系统出现“多个模块必须按顺序协作应对同一异常”的场景中介者的价值就立刻体现出来了。6.2 从中介者到本地消息总线的演进路径有些读者可能已经注意到我实现的中介者已经和本地消息总线如 MediatR很像了。如果后续模块数量继续膨胀确实可以考虑平滑迁移到成熟的轻量消息总线框架。但我要泼一点冷水。消息总线框架的通用性更强同时意味着它不会替你做领域约束。设备联锁里要求的“同步顺序保证”“路由配置可审计”“设备动态增删”在通用框架里都需要额外配置和约定才能实现。从我的实践看在半导体设备软件这种强业务、强安全约束的场景里自研一个轻量中介者比引入通用框架更可控。如果非要给迁移路径排个序我的建议是保持当前的中介者核心不变逐步用命名约定统一事件类型和设备名如果出现跨域交互较多的需求再考虑把中介者替换为进程内消息总线任何时候都要保留“模拟设备 联动单测”这条回归防线6.3 给同行的一条实用建议如果你正在开发类似的设备上位机软件不要一上来就搞复杂的事件总线先从最朴素的中介者开始。把“设备模块”和“中间协调者”的分工写清楚先让系统跑起来再根据实际出现的问题逐步演进。我个人在这一轮重构里最大的收获不是把调用关系从网状变成了星状而是让整个系统有了一个“唯一的联动规则编辑入口”。上位机软件和互联网后端最大的不同在于它要为一个持续几百上千小时的物理测试负责。一套联锁逻辑如果散落在七八个类里谁都不敢保证它百分之百按规范执行。把规则收拢到一个地方、配着模拟设备把每条联动写成单测是我在这个项目里做过最值当的架构决定。最后分享一个小技巧中介者路由表不只是代码它本身就值得做成一张运维文档。每次改路由顺手更新文档和对应单测产线排障时翻路由表比翻调用堆栈快好几倍。这套习惯坚持下来后续接手的同事会非常感谢你。