乐于分享
好东西不私藏

QT C++实战:一套工业软件架构的5个核心设计思想

QT C++实战:一套工业软件架构的5个核心设计思想

从”能用”到”好用”:一套工业软件架构的5个核心设计思想

设备跑起来的时候没人关心你用了什么设计模式,只在乎它会不会停。


🎯 开篇:工业软件的架构之痛

做工业软件的朋友都知道,设备跑起来的时候没人关心你用了什么设计模式,只在乎它会不会停。

但作为一个有追求的架构师,我们既要让设备7×24小时稳定运行,也要让代码优雅得像一首诗。

今天我想跟大家分享的,是HY-HMI这套架构背后的5个核心设计思想——不是教科书上的理论,而是我们在半导体封装设备上踩坑踩出来的真知灼见。

🏗️ 一、设计思想一:微内核,大生态

1.1 为什么是微内核,不是微服务

很多人一提到插件化就想到微服务,但工业软件绝对不能搞微服务那一套

 工业软件 vs 互联网软件的本质区别                  

我的核心设计思想进程内微内核插件化,而不是跨进程微服务化。

所有的模块解耦都在同一个进程内完成,所有插件之间的调用都是直接的函数调用,没有序列化反序列化开销。

这就是为什么我们的RPC调用平均延迟只有0.12ms的根本原因。

1.2 微内核的三大核心原则

// 微内核三大原则:
//  
//  1. 核心稳定不变 —— 服务总线、日志、权限
//  2. 业务插件可变 —— 运动控制、视觉、工序
//  3. 依赖方向唯一 —— 只能内核,绝对不能反过来

大家看我们代码结构就知道这个设计思想的贯彻有多彻底:

  • src/core/ 目录下的10个核心服务文件,半年来几乎没变过

  • src/plugins/ 下面30多个插件,改了无数轮

  • 没有任何一个核心服务依赖任何一个插件

  • 所有的插件都只依赖核心服务的接口定义

这就是架构的力量——不变的内核,万变的插件

🚄 二、设计思想二:总线即操作系统

如果说微内核是人的心脏,那么服务总线就是连接全身的血管。

2.1 服务总线不是消息队列,是操作系统内核

很多人看ServiceBus代码,说这不就是个消息转发吗?

大错特错!

服务总线本质上,就是我们在进程内实现了一个极简版的 “操作系统”

/** * 服务总线设计思想: *  * 1. 服务注册表 ≈ 进程表 *    每个注册的服务就是一个"进程" *  * 2. 消息发布订阅 ≈ IPC进程间通信 *    插件之间通过总线发消息就是进程间发消息 *  * 3. RPC调用 ≈ 系统调用 *    跨插件方法调用就是执行系统调用 *  * 4. 健康检查 ≈ 进程监控 *    挂了的服务自动重启 *  * 5. 熔断器 ≈ 内存保护 *    一个进程崩了不影响其他 */class ServiceBus {    // 这不是设计模式,这是操作系统原理};

2.2 熔断器设计的哲学

很多人做熔断器只做了 “熔断”,没做 “自愈”

我的设计思想是:熔断器不是用来”防雪崩”的,是用来”给次机会”的。

// 三种状态的人生哲学://// Closed  →  相信你,让你过//           ↓出问题了// Open    →  你先歇会儿//           ↓冷静一下// HalfOpen →  再给你一次机会

现场设备最怕什么?不是出问题,是出了问题没人管。熔断器给了出错的模块一次 “重新做人” 的机会,也给了运维人员 “发现问题” 的时间。

工业软件,容错就是生产力。

2.3 RPC调用的超时与异步处理

RpcResponse ServiceBus::rpc(const QString& serviceName,                            const QString& method,                            const QVariantListargs                            int timeoutMs){    // Step 1: 检查熔断器    if (!checkCircuitBreaker(serviceName)) {        return RpcResponse{            generateRequestId(),            false,            QVariant(),            "Circuit breaker is open",            0        };    }    QElapsedTimer timer;    timer.start();    // Step 2: 获取服务实例    IService* service = getService(serviceName);    if (!service) {        recordFailure(serviceName);        return RpcResponse{            generateRequestId(),            false, QVariant(),            QString("Service %1 not found").arg(serviceName),            timer.elapsed()        };    }    // Step 3: 执行调用(带超时检测)    QVariant result = service->call(methodargs);    // Step 4: 更新熔断器状态    if (result.isValid()) {        recordSuccess(serviceName);    } else {        recordFailure(serviceName);    }    updateStatistics(result.isValid(), timer.elapsed());    return RpcResponse{        generateRequestId(),        result.isValid(),        result,        result.isValid() ? "" : "Method call failed",        timer.elapsed()    };}

📝 三、设计思想三:日志即数据库

在工业现场,“出了问题能找到原因”比”不出问题”更重要

3.1 结构化日志的真正价值

// 分级日志定义:Trace/Debug/Info/Warning/Error/Critical/Fatalenum classLogLevel{    Trace = 0,    // 跟踪级别:详细的程序执行流程    Debug = 1,    // 调试级别:调试信息    Info = 2,     // 信息级别:正常运行信息    Warning = 3,  // 警告级别:潜在问题    Error = 4,    // 错误级别:错误但可恢复    Critical = 5// 严重错误    Fatal = 6     // 致命错误};// 多目标同时输出:Console/File/Database/Remoteenum classLogTarget{    Console = 0x01,   // 控制台输出(带颜色区分)    File = 0x02,      // 文件输出(自动轮转)    Database = 0x04,  // 数据库存储(批量写入)    Remote = 0x08     // 远程上报(工厂级日志平台)};

现场出问题的时候:

  • 我要 “运动控制模块昨天下午3点到5点所有的Error日志”

  • 我要 “某个ID为1234的产品” 所有相关日志

  • 我要 “某个线程” 所有的日志

  • 我要 “某台设备” 所有Fatal日志

如果你的日志就是一串文本,你查去吧!

3.2 性能设计的哲学

工业软件日志,日志系统卡了业务逻辑,那就是犯罪

我的性能设计三原则:

  1. 调用方绝对不阻塞 —— 进队列就返回

  2. IO线程做真正的写操作

  3. 队列满了丢老日志,绝对不OOM

    // 这行代码背后是血的教训:while (m_logBuffer.size() > MAX_BUFFER_SIZE) {    m_logBuffer.dequeue();  // 丢最老的,保住最新的}

设备死机了,最新的日志才值钱。

🔌 四、设计思想四:面向契约,依赖抽象

插件化的精髓是什么?不是用了Qt的Plugin框架,那只是技术手段。

插件化的精髓,是面向接口编程,是依赖倒置原则

4.1 接口设计的艺术

// IPlugin接口设计思想解读:virtual QList<Dependency> dependencies() const = 0;// ↑ 你需要什么,清清楚楚写明白virtualboolinitialize(const QVariantMap& config) = 0;// ↑ 给你什么配置virtual QObject* getService(const QString& serviceName) = 0;// ↑ 你提供什么服务virtualboolhandleMessage(const QString& messageType, const QVariant& data) = 0;// ↑ 你处理什么消息

就是这么4个方法,定义了插件与外界所有的边界。

一个插件,你从加载进来,我就知道你是谁,你需要什么,你能做什么。

4.2 依赖管理的哲学

做插件化最容易踩的坑是什么?循环依赖!

我的设计思想:依赖就是承诺,承诺就要负责到底。

// Kahn算法拓扑排序的意义是什么?// 不是算法本身,而是检测循环依赖。//// A → B → C → A// 只要发现循环依赖,直接加载失败//// 编译期放过你,加载期绝对弄死你。

工程师写代码的时候图方便写了个循环依赖,架构师必须在架构层面给你把关。

技术债务不能妥协,就是对质量犯罪。

4.3 热插拔设计的哲学

热插拔不是给客户装X用的!

热插拔是:

  • 现场紧急加个统计报表,不用停设备

  • 某个小功能有Bug,修复了不影响生产

  • 客户现场调试,加几行代码看个变量

在半导体工厂,设备停一小时,损失是以万为单位计算的。

🎨 五、设计思想五:分离关注点

工业软件UI层90%的烂代码,都源于一个问题:

在按钮的clicked()槽函数里写了800行运动控制的代码。

5.1 View干View的事,ViewModel干ViewModel的事,Service干Service的事

谁也别越界!

为什么要分这么清楚?

  • 刚毕业的小姑娘写QML画界面,她不用懂运动控制

  • 十年经验的老法师写运动控制算法,他不用懂QML

  • 测试工程师直接测ViewModel,不用跑UI自动化

分工,就是生产力。

5.2 ViewModel基类设计

class BaseViewModel : public QObject{    Q_OBJECT    Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)    Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorChanged)    Q_PROPERTY(bool hasError READ hasError NOTIFY errorChanged)public:    virtual void initialize() = 0;    virtualvoidcleanup() = 0;protected:    voidsetLoading(bool loading);    voidsetError(const QString& error);    IService* getService(const QString& serviceName);};

5.3 运动控制ViewModel示例

class MotionControlViewModel : public BaseViewModel{    Q_OBJECT    Q_PROPERTY(QVariantList axes READ axes NOTIFY axesChanged)    Q_PROPERTY(double currentPosition READ currentPosition NOTIFY positionChanged)    Q_PROPERTY(bool isMoving READ isMoving NOTIFY movingChanged)public:    // 暴露给QML调用的方法    Q_INVOKABLE void moveAbsolute(double position);    Q_INVOKABLE voidmoveRelative(double distance);    Q_INVOKABLE voidstop();    Q_INVOKABLE voidhome();private slots:    // 响应服务层的状态变化    voidonAxisPositionChanged(int axisId, double position);    voidonAxisStateChanged(int axisId, int state);};

这样做的好处是什么?

  1. View层的工程师不用懂运动控制,只需要绑定数据即可

  2. 业务逻辑的工程师写ViewModel,不用关心界面怎么画

  3. 单元测试可以直接测ViewModel,不需要UI自动化

5.4 数据绑定的性能优化

很多人做数据绑定,变量一变就notify,然后UI卡成狗

我的设计思想:变化小于编码器精度的,就别刷新了。

// 电机1毫秒发一次位置,UI60帧刷新就够了。// UI刷那么快,人眼也看不到。voidonAxisPositionChanged(int axisId, double position) {    // 位置变化小于0.1微米    if (qAbs(m_currentPosition - position) > 0.0001) {        m_currentPosition = position;        emit positionChanged();    }}

📊 六、设计思想落地效果验证

讲了这么多设计思想,到底有用吗?有用。

设计思想

带来的结果

微内核

核心半年不变,插件随便改

熔断器

单模块挂了设备照样跑

结构化日志

出问题5分钟定位

接口契约

并行开发不冲突

MVVM分层

新人两周上手

最终体现在:

  • 新功能开发速度提升4倍

  • 线上Bug率下降7倍

  • 设备MTBF从72小时 → 500+小时

🎯 总结:架构设计的本质

文章的最后,我想跟所有做工业软件同行说句掏心窝子的话。

我们做工业软件架构设计,不是为了炫技,不是为了面试能吹牛逼。

我们做的每一个设计决策,背后都是:

  • 现场工程师少熬一个夜

  • 客户少损失几十万

  • 操作员少担一点心

我总结这一套架构设计思想,最后都浓缩成三句话:

  1. 微内核大生态 —— 不变的内核,万变的插件

  2. 容错就是生产力 —— 出问题不可怕,可怕的是出了问题没人管

  3. 分工就是竞争力 —— 让专业的人做专业的事

我们写的每一行代码,最后都会跑到工厂里,跑到设备上,跑在客户的生产线上。对代码负责,就是对客户负责,就是对自己负责。