DKERP客户端重构纪实:4天自研控件库的“短命”教训,以及为什么我坚定选择原生Qt

一个人 + AI,从"自研控件的快速试错"到"原生控件的稳健落地"

写在前面

上篇文章发出后,有朋友私信问我:"你之前说的自研控件库,到底做了多久?"

答案是:4天。

没错,不是半年,不是大半年,就4天。

这4天里,我用AI辅助写了DKButtonDKComboBoxDKDataTableDKTabWidget等一套"完整"的自研控件库。代码跑通了,demo演示了,甚至能拖拽、能配置、能换主题。

然后我决定:全部扔掉。

这篇文章,我想坦诚地聊聊这4天的经历------为什么我选择放弃,以及这个"失败"的尝试如何让我更坚定地走向原生Qt。

一、那4天发生了什么?

当时我刚好完成服务端重构,信心爆棚。心想:"既然后端能做到配置驱动,前端何不把控件也全自研了?这样就能彻底掌控一切!"

于是开始行动。

1.1 Day 1:框架搭建 + DKButton

让AI生成了控件基类DKWidget,然后派生出DKButton。支持:

  • 自定义外观(圆角、背景、边框)
  • 自定义事件绑定配置
  • JSON序列化/反序列化

代码跑起来了,感觉良好。

1.2 Day 2:DKComboBox + DKDataTable

下拉框和表格是最复杂的。DKComboBox要支持数据源配置、异步加载;DKDataTable要支持列配置、排序、分页。

AI生成了一两千行代码,功能基本可用。但编译时开始出现各种warning和偶发的崩溃。

1.3 Day 3:DKTabWidget + 集成测试

标签页控件写完,开始把整个客户端迁移到自研控件体系。

问题开始集中爆发:

  • 布局计算偶尔错位
  • 焦点切换逻辑混乱
  • 与QSS的兼容性问题
  • 内存泄漏(Qt父子对象关系被破坏)

1.4 Day 4:debug地狱

一整天都在追bug。修好一个,冒出两个。AI生成的代码我改不动------逻辑太复杂,牵一发动全身。

那天晚上,我盯着屏幕想了很久:

继续修,能修好吗?能。但要修到"敢给工厂用"的程度,还需要多少天?

答案是:不知道。可能一周,可能一个月,可能永远有新的bug。

二、为什么4天就放弃了?

2.1 成本收益严重不对等

维度 用原生Qt 自研控件(4天后) 自研控件(目标状态)
稳定性 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐(未知时间)
开发成本 0(直接用) 4天 未知(可能几周)
维护成本 低(Qt团队维护) 高(我一个人扛) 极高
功能完整度 极高 中(永远追不上Qt)
配置驱动能力 需额外封装 原生支持 原生支持
跨平台一致性 Qt保证 自己测 自己测(噩梦)

结论很清晰:自研控件的边际收益,远低于维护成本。

2.2 那4天最大的收获

虽然代码扔了,但这4天不是浪费。我搞清楚了三件事:

第一:自研控件库的真正价值不在于"替代Qt",而在于"理解边界"

只有在亲手写过控件后,你才知道Qt帮你做了多少事:事件循环、焦点管理、布局计算、样式系统、跨平台适配...

第二:AI生成的自研控件代码,比手写更难维护

因为AI生成的是"黑盒"------它遵循某种模式,但那个模式不是你设计的。你改不动,也不敢改。

第三:原生Qt + 配置层,才是最稳的架构

核心洞察:你不需要自研控件来实现"配置驱动"。你只需要在原生控件外面包一层"配置解释器"。

三、从4天教训到正确方案:原生Qt + 配置驱动

放弃自研控件后,我用了4个小时重新设计了一套方案。

3.1 核心架构:原生控件 + WidgetFactory + EventExecutor

scss 复制代码
┌─────────────────────────────────────────────────────────┐
│                      配置文件 (JSON)                      │
│  { "control_id": "btn_query", "type": "QPushButton", ... } │
└─────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────┐
│                    WidgetFactory                          │
│  // 只做一件事:根据type字符串创建原生Qt控件              │
│  if (type == "QPushButton") return new QPushButton();    │
│  if (type == "QTableWidget") return new QTableWidget();  │
└─────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────┐
│                   原生Qt控件                              │
│  QPushButton, QTableWidget, QComboBox, QLineEdit...     │
│  // 100%原生,0封装                                      │
└─────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────┐
│                    EventExecutor                         │
│  // 拦截信号,根据配置执行动作                           │
│  connect(btn, &QPushButton::clicked, [=]() {            │
│      executeAction(config["action"]);                   │
│  });                                                     │
└─────────────────────────────────────────────────────────┘

关键点:WidgetFactory不是控件封装层,只是一个"字符串 → 构造函数"的映射表。

3.2 配置驱动的实现(核心代码)

scss 复制代码
// 从JSON创建控件
QWidget* widget = WidgetFactory::instance()->create(config["type"].toString());
widget->setObjectName(config["control_id"].toString());

// 设置属性(利用Qt原生属性系统)
for (auto it = config.begin(); it != config.end(); ++it) {
    widget->setProperty(it.key().toUtf8(), it.value().toVariant());
}

// 绑定事件(利用Qt原生信号槽)
QString eventType = config["event_type"].toString();
if (eventType == "clicked") {
    connect(qobject_cast<QPushButton*>(widget), &QPushButton::clicked, 
            EventExecutor::instance(), [config]() {
                EventExecutor::instance()->executeAction(config["action"]);
            });
}

这段代码只有30行。没有继承、没有封装、没有黑盒。

3.3 效果对比

能力 自研控件方案 原生Qt + 配置层方案
配置驱动创建
运行时拖拽布局 ✅(通过ControlHandle包装器)
用户自定义样式 ✅(自研样式引擎) ✅(QSS,更成熟)
稳定性 ❌(4天后仍不稳定) ✅(Qt原生保证)
代码量 ~3000行 ~200行(配置层)
维护成本 极高 极低
功能完整性 追不上Qt 100% Qt原生功能

四、这次教训带来的几个核心认知

4.1 教训一:不要重复造轮子,除非轮子真的不合适

Qt原生控件的稳定性是经过几十年、几百万项目验证的。我4天的代码,不可能超越。

正确的策略:用原生控件做"地基",在上面盖"配置层"这栋房子。不要试图换地基。

4.2 教训二:AI生成代码的陷阱

AI生成自研控件的代码时,会陷入"过度设计"。因为它看到的训练数据里,控件库往往有很多抽象层。

我的经验:给AI的约束要更严格------"只生成原生Qt控件的创建代码,不要封装,不要继承。"

4.3 教训三:快速试错的正确姿势

4天就止损,是我这次做的最正确的决定。

如果当时我抱着"再修一天就好"的心态继续,可能会陷进去一个月。

试错的关键不是"能不能成功",而是"多久能判断出不成功"。

4.4 教训四:原生Qt + 配置驱动的真正威力

这套方案最妙的地方在于:你把所有"不稳定"的代码,都推到了配置层。

  • 控件的稳定性 → Qt保证
  • 业务逻辑的稳定性 → 你保证(但业务逻辑很少变)
  • 配置解析的稳定性 → 你保证(这段代码极其简单,几乎不可能出错)

每一层的职责清晰,风险可控。

五、现在客户端的状态

放弃自研控件后,客户端的进度反而加快了:

模块 状态 说明
配置驱动UI引擎 ✅ 完成 从JSON创建原生Qt控件,支持所有标准控件
WidgetFactory ✅ 完成 30行的极简工厂,支持20+种控件类型
事件绑定引擎 ✅ 完成 信号 → 配置 → 动作的完整链路
运行时拖拽布局 ✅ 完成 ControlHandle包装原生控件,不影响控件本身
AI主题生成 ✅ 完成 DeepSeek生成QSS,直接setStyleSheet
用户布局保存 ✅ 完成 加密存储,多账号隔离
服务端配置同步 🔥 进行中 从ConfigServer拉取最新配置

六、给同行的一些建议

6.1 如果你也想做"配置驱动"客户端

我的建议顺序:

  1. 先用原生Qt把业务跑通(不要一开始就想自研控件)
  2. 抽离出配置层(WidgetFactory + EventExecutor,各100行以内)
  3. 验证配置驱动的可行性(改JSON就能改界面和行为)
  4. 如果配置层不够用了,再考虑局部自研

大概率你到第3步就发现:原生Qt + 配置层,已经够用了。

6.2 关于AI辅助开发的提醒

AI很强大,但它有一些"惯性":

  • 它倾向于生成"教科书式"的代码(多层抽象)
  • 它不知道你的项目边界(容易过度设计)
  • 它生成的代码,你不敢随便改

应对策略:给AI极其严格的边界约束。比如:"不要继承QWidget,只创建和返回原生控件指针。"

写在最后

4天的自研控件库尝试,说失败也好,说教训也罢。但我一点也不后悔。

因为没有这4天,我不会这么深刻地理解:

Qt原生控件,已经是最好的控件库。你的任务不是重新发明它们,而是让它们变得更"听话"------用配置驱动它们,用事件系统调度它们,用QSS美化它们。

这4天教会我的另一件事:一个人做工业软件,最大的能力不是"能写多复杂的代码",而是"知道什么不该写"。

不该写的代码,写得越少,系统越稳。


目前客户端已完成主体重构,22小时跑通4万+行代码。下一步:对接ConfigServer,实现配置的云端同步和版本管理。

如果对架构细节感兴趣,或者你也踩过类似的坑,欢迎留言交流。

项目启动:2026-04-30
自研控件实验:2026-05-14(4天)
原生Qt重构完成:2026-05-15
当前状态:配置驱动引擎稳定运行

加个关注,后续更新不会停~

相关推荐
我叫黑大帅1 小时前
通过白名单解决 pnpm i 报错 Ignored build scripts
前端·javascript·面试
风止何安啊1 小时前
用 APP 背单词太无聊?我用 Trae Solo 移动端写个小游戏来准备 6级
前端·人工智能·trae
Summer不秃1 小时前
深入理解 Token 无感刷新:从并发雪崩到单例锁 + 请求队列的完整实现
前端·http
yingyima1 小时前
Git 实战:你必须掌握的 7 个常用命令
前端
次次皮2 小时前
代理启动前端dist包
java·前端·vue
星恒随风2 小时前
四天学完前端基础三件套(JavaScript篇)
开发语言·前端·javascript·笔记
guslegend3 小时前
第9节:前端工程与一键启动
前端·大模型·状态模式·ai编程
南囝coding3 小时前
Anthropic 内部数百个 Claude Code Skills,他们总结的这套方法值得看
前端·后端
Dxy12393102164 小时前
如何使用jQuery获取一类元素并遍历它们
前端·javascript·jquery