一个人 + AI,从"自研控件的快速试错"到"原生控件的稳健落地"
写在前面
上篇文章发出后,有朋友私信问我:"你之前说的自研控件库,到底做了多久?"
答案是:4天。
没错,不是半年,不是大半年,就4天。
这4天里,我用AI辅助写了DKButton、DKComboBox、DKDataTable、DKTabWidget等一套"完整"的自研控件库。代码跑通了,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 如果你也想做"配置驱动"客户端
我的建议顺序:
- 先用原生Qt把业务跑通(不要一开始就想自研控件)
- 抽离出配置层(WidgetFactory + EventExecutor,各100行以内)
- 验证配置驱动的可行性(改JSON就能改界面和行为)
- 如果配置层不够用了,再考虑局部自研
大概率你到第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
当前状态:配置驱动引擎稳定运行
加个关注,后续更新不会停~