Qt项目一旦从小工具长成产品,最先拖慢开发的往往不是功能难度,而是目录越堆越乱、模块边界含糊、一个改动牵一串编译与联调。结构搭得清爽,后面加功能、做重构、补测试都会顺很多,尤其是团队里有人新加入时,上手速度差别非常明显。
一、Qt项目怎么搭结构
先把仓库的骨架定下来,再决定每类文件该落在哪一层,最忌讳的是一开始图省事把所有东西都丢进同一个src,半年后就很难分拆。
1、先定单仓还是多仓的落地方式
如果是一个产品加多个工具加一套公共库,优先单仓,公共能力集中维护,版本对齐更省心;如果你们有多个产品线共用但发布节奏完全不同,再考虑把公共库独立成仓库,用包管理或子模块来引用,避免每次小改动都牵动所有发布。
2、先把顶层目录分成可一眼识别的几类
建议把顶层直接分成apps、libs、plugins、tools、third_party、cmake、tests、docs、scripts、assets这类,名字用英文小写,任何人打开仓库都能马上判断代码在哪里改、资源在哪里放、构建脚本在哪里找。
3、每个可编译单元都当成一个独立包来放
apps里只放可执行程序入口与壳层代码,尽量薄;libs里放可复用库,按功能域拆分;plugins里放可选能力,按插件类型拆分;tools里放内部辅助工具,例如数据转换器、日志查看器、打包小工具,这些不应该混进主应用。
4、在Qt Creator里用一次性标准化动作把工程创建好
在Qt Creator里点击【File】→【New Project】,先选你们统一的模板,例如Qt Widgets Application或Qt Quick Application;创建完成后立刻在【Projects】面板里把Kit固定好,尤其是Qt版本、编译器、调试器要与团队一致,避免同一份代码在不同人机器上表现不一致。
5、资源与生成物从第一天就分流
源码只进版本库,生成物不进版本库;把构建输出固定到build目录或out目录,统一用同一规则命名,例如build-debug、build-release;把assets放到专门目录并按用途分层,例如assets/icons、assets/qml、assets/fonts、assets/translations,避免资源散落在src里后面找不到。
6、给每个模块准备一份最小自述文件
每个libs子目录下放一个README,写清楚模块职责、依赖列表、对外暴露的头文件范围、典型使用方式与常见坑点;这份文件不是写给外部用户,而是写给半年后的自己和新同事,能减少很多口口相传的隐性成本。
二、Qt模块划分与目录组织怎么定
模块划分的目标不是把文件分得越碎越好,而是把变化频率相近的东西放一起,把依赖方向固定住,让大多数改动都停留在一两个模块内结束。
1、先按业务与技术两条线划分,再决定模块颗粒度
业务线适合拆成domain类库,例如订单、用户、设备、配方;技术线适合拆成foundation类库,例如日志、配置、线程、网络封装、序列化;两条线交汇时用接口层衔接,避免业务模块直接依赖某个具体网络实现或存储实现。
2、把UI层从业务层剥离出来
无论你用Widgets还是QML,都建议把UI做成单独模块,UI只负责展示与交互,业务规则放到domain或service里;这样你后面换界面框架、改交互流程、做自动化测试时,不会被一堆UI细节绑死。
3、把Qt依赖做分层,能不依赖GUI就别依赖GUI
底层库优先依赖QtCore,少把QtGui和QtWidgets带进去;QML相关的代码集中到qml模块或ui模块;这样可以降低编译量,也能让一部分库在命令行工具里复用。
4、统一每个模块的内部目录形态,别让人每次都重新适应
建议每个libs下的模块采用同一套目录骨架,例如include放对外头文件,src放实现,resources放qrc相关文件,qml放QML模块,tests放该模块的单测;对外API尽量只从include导出,避免别人直接去引用src里的私有头文件。
5、目录命名与命名空间绑定,减少重名与误引用
模块名尽量与命名空间一致,例如lib名叫core_utils则命名空间也用core::utils;文件名用小写加下划线或驼峰保持一致,类名遵循你们现有风格,但要有一套明确规则,避免同一仓库里出现三种命名习惯。
6、提前约束跨模块依赖,谁能依赖谁写成硬规则
建议固定一个依赖方向,例如app依赖ui与service,ui依赖service与domain,service依赖domain与foundation,domain只依赖foundation,foundation不依赖上层;遇到必须反向访问时,用接口与回调把依赖反转,不要直接include上层头文件。
三、Qt构建链路与依赖边界
目录与模块定好后,真正决定你们开发体验的,是构建链路是否干净、依赖边界是否可控,很多卡顿与冲突其实都源自这里。
1、让每个模块都可单独编译并能被单独链接
每个libs模块都应该能独立生成一个库,应用只通过链接库来使用它,而不是把源码到处add进去;这样你定位链接错误、符号冲突、重复编译时会快很多,也能控制编译时间增长速度。
2、对外头文件与私有头文件分离,禁止跨模块直接拿私货
把对外头文件集中放在include并作为唯一暴露入口,私有实现头文件放在src并不对外可见;一旦发现其他模块引用了src里的头文件,就等于边界破了,后面改内部实现会非常痛苦。
3、把第三方依赖集中管理,避免散落引用导致版本漂移
third_party里按库名分目录,保留版本信息与许可说明;如果你们用包管理,就把配置集中到cmake或脚本目录,不允许某个模块私自引入另一份同名库;遇到必须升级第三方库时,也能快速评估影响面。
4、把调试与发布的产物规则写死,减少口径不一致
把debug与release的输出目录固定,日志级别、断言开关、符号剥离规则也固定;在Qt Creator里通过【Projects】面板把Build Steps和Run Environment整理成统一口径,避免有人本地跑得通但CI跑不通。
5、用最小集成测试守住模块边界
tests目录里至少要有两类测试,一类验证domain与service的核心规则不被改坏,另一类做少量端到端用例确保主流程能跑通;测试不需要铺天盖地,但要能在结构被破坏时第一时间报警。
6、留出扩展点给插件与可选能力,别把未来堵死
如果你们有可能出现行业定制或功能组合差异,plugins目录提前预留结构,例如按接口版本分层、按插件类型分组;核心应用只认识插件接口与加载机制,不把某个插件实现写死进主工程里。
总结
Qt项目结构要先把仓库骨架搭清楚,再把模块按业务与技术分层,最后用构建与依赖边界把规则钉死。你把apps、libs、plugins、tools这些层次分开,把UI与业务拆开,把Qt依赖控制在合适层级,再配合统一的目录形态与对外接口约束,后续扩展与重构都会更稳,也更不容易陷入越改越乱的循环。