尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Serial-Studio 规格驱动开发实战:2.1 万行 God-class 按职责拆分 TU 的有序任务清单与验证配方

Serial-Studio 规格驱动开发实战:2.1 万行 God-class 按职责拆分 TU 的有序任务清单与验证配方 Serial-Studio 规格驱动开发实战2.1 万行 God-class 按职责拆分 TU 的有序任务清单与验证配方【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本文以 Serial-Studio 规格库中的任务清单 tasks.md 为主体完整拆解该项目如何把三个合计 2.1 万余行的上帝文件ProjectModel / ProjectEditor / ProjectHandler拆分为按职责划分的编译单元TU。读完本文你将掌握一套可复用的 C 大文件物理拆分方法如何把纯移动定义成可 grep 验证的纪律、如何用未限定名查找技巧实现零调用点修改的跨 TU 共享、以及每个阶段结束都是合法构建状态的分阶段验收体系。背景四阶段规格驱动流程中的第三阶段Serial-Studio 对非平凡改动采用 spec - plan - tasks - implement 的四阶段规格驱动工作流每阶段产出物需人工批准后才能进入下一阶段详见 spec-driven.md。本任务清单是该流程的第三阶段Phase 3 of 4定位是有序检查清单the ordered checklist第一阶段 spec.md 回答 WHAT 与 WHY问题、目标、编号需求、验收标准AC1–AC6与 CANNOT-MOVE 不变量第二阶段 plan.md 回答 HOW受影响文件、验证配方、风险与回退策略第三阶段即本文主角 tasks.md把 plan 拆成小、有序、可独立验证的任务单元供/ss-implement自上而下执行并保持复选框状态更新门禁规则人工在 tasks.md 上标记approved之前不得开始/ss-implement。问题本身的规模由 spec 记录如下God 文件原行数职责簇ProjectModel.cpp8,297 行持久化、加载遗留迁移、source CRUD、实体 CRUD、workspace 合成、folder CRUD、表格/寄存器、选中态、磁盘监视、自动保存、图表调用、状态/锁ProjectEditor.cpp6,677 行外部接线、树模型、MQTT 表单、实体表单、提交处理器、多选、选中镜像、摘要ProjectHandler.cpp6,534 行若干命令族file/snapshot、entities、parser/painter/dry-run、batch 约 60 个文件级 static 辅助核心思路是先做零行为变化的物理 TU 拆分S1–S3把函数体逐行搬到按职责命名的兄弟.cpp之后再单独做协作者对象抽取S4–S5。类本身是 QML 与 API 层绑定的公共门面facade问题在文件而不在接口——最便宜、零风险的第一步是让每次改动只重编一个小 TU而不是整个 God 文件。任务清单的三条约定Conventionstasks.md 开头用三条约定约束整个执行过程这是该清单最值得借鉴的部分一个任务 一个聚焦、可评审的 diff在本文语境下即一个新 TU 或一次 CMake 编辑Verify 是本单元通过后才继续的标准plan 中的逐阶段 grep 配方加上python scripts/code-verify.py --check files该脚本见 code-verify.pyDeps 列出必须先落地的任务 ID任务按树在任何阶段之后都是合法早晨状态valid morning state排序——S1、S2、S3 相互独立按风险顺序 S1 - S2 - S3 执行。执行状态S1–S3 完成偏差如实留痕文档的 Run status2026-07-06 夜间自主运行记录了三个有教育意义的偏差deviation体现了记录实际发生的事而非计划的留痕纪律S1/S2 共享头精简sanitizeFolderTree、serializeFoldersProjectModel 侧与buildFolderTree、accumulateFolderEnabledProjectEditor 侧本计划提升到共享头ProjectModelShared.h/ProjectEditorShared.h但审计发现每个只有单一调用者于是降级为所属 TU 内的文件级static而非入共享头——跨 TU 才值得升格S2bCustomModel.hT2.11当次未执行class CustomModel留在ProjectEditor.hS5c 随之保持阻塞S5c 的复选框最终勾选说明该项在后续运行中补齐S3 的ProjectApiSupport最终是纯头文件14 个跨族辅助函数以inline自由函数形式落在ProjectApiSupport.h无.cppregister*Commands构建器与全部 65 处registerCommand调用留在残余ProjectHandler.cpp的registerCommands()中只有命令体移入各命令族 TU。S4、S5 在当次运行中仅停留为规格spec-only其验收标准记录在清单中、待后续运行实现。S1ProjectModel.cpp 的七族 TU 拆分T1.1–T1.9S1 把 8,297 行的ProjectModel.cpp拆为 7 个新.cpp 1 个共享头 残余门面 TU。任务粒度、移动范围原文件行号与验证要点如下任务新文件移动内容原文件行号关键验证T1.1ProjectModelShared.h6 个跨 TU 辅助folderExists、folderIsSelfOrDescendant、sanitizeFolderTree、serializeFolders:67-154、nextDuplicateTitle:159-194、seedDefaultFrameParser:1503-1516提升为namespace DataModel中的 inline/模板自由函数grep \btr(零命中每个辅助全仓库唯一模板保留template、非模板加inlinecode-verify --checkT1.2ProjectModelPersistence.cpp:1779-2027askSave..serializeToJson :7639-7868autoSave..finalizeProjectSave定义对称性include 闭包超集纯移动 diffwatcher 重挂watchProjectFile()完好T1.3ProjectModelLoading.cpp:2334-3176加载 transform 扫描 statics :2715-2900 遗留迁移 :758-905seed/dedup/migrate statics remapWaterfallYAxisIdstatic 闭包纯移动 diffT1.4ProjectModelSources.cpp:1517-1778source CRUD/settings :4849-4982frame-parser setters定义对称性纯移动 diffT1.5ProjectModelCrud.cpp:3366-3591、:3595-4029含detail::RefAnchor、:4036-4824含detail::ThreeAxisLayoutpopulateThreeAxisDatasets:275-317、:7877-8055、:8063-8297detail 命名空间 ODRThreeAxisLayoutRefAnchor只存在于本 TUT1.6ProjectModelWorkspaces.cpp:5055-5224、:5824-6030、:6781-7095、:7122-7480 statics :196-273、:322-349tally/append/collect/push/buildAutoRefsForGroup按函数而非 banner 分区6781-7480 的自动 workspace/隐藏组机制归此族而非 FoldersT1.7ProjectModelFolders.cpp:6032-6779三块 folder CRUD 提示框 :7097-7121sanitize*Folders定义对称性纯移动 diffT1.8ProjectModelTables.cpp:5231-5451、:5461-5497、:5591-5822表格/寄存器 提示框 CSV 导入导出定义对称性纯移动 diffT1.9app/CMakeLists.txt7 个新.cpp加入SOURCES约 :308ProjectModelShared.h加入HEADERS约 :439各恰好一次每个新文件仅列一次S1 全配方步骤 1–7通过残余文件保留 ctor/单例、status/lock、getters:725-1512、setupExternalConnections、newJsonFile等依赖关系上T1.2–T1.8 均依赖 T1.1共享头先行T1.9 收尾并依赖 T1.1–T1.8。T1.2 还留痕了一条CANNOT-MOVE #4 的偏差处理autoSave()/syncRuntime()体按计划本应留在门面.cpp它们涉及m_runtimeDirty竞态区实际却随持久化族搬到了ProjectModelPersistence.cpp文档说明这是行为中性的成员分派与 TU 无关ctor 的 QTimer 接线两种写法都能解析且 #4 的本意——把它们排除在 S4 的AutoSaveController协作者之外——仍然成立。S2ProjectEditor.cpp 拆九族 ItemIds 头 S2b CustomModelT2.1–T2.12S2 额外抽出两个头文件处理私有枚举块和类定义的搬家任务新文件内容与要点T2.1ProjectEditorItemIds.h原样移动ProjectEditor.cpp:51-273的私有 typedef-enum 块TopLevelItem、ProjectItem、kDatasetView_*、kGroupView_*…。非Q_OBJECT无需 HEADERS 条目验证要求每个使用kView_*的文件都 include 它T2.2ProjectEditorShared.h计划提升 4 个 inline 辅助folderDisplayPath、buildFolderTree、accumulateFolderEnabled、busTypeIcon实际偏差只交付 2 个folderDisplayPath、busTypeIcon另两个因单调用者降级为文件级 staticT2.3ProjectEditorWiring.cpp移动 wire*:281-760。wireProjectModelRebuilds()的 connect 拓扑与QueuedConnection:285-289形状逐字保留CANNOT-MOVE #8T2.4ProjectEditorTree.cpp移动 :1310-2021、:2419-2537、:5717-5782T2.5ProjectEditorMqtt.cpp移动 :1191-1214、:2023-2418#ifdef BUILD_COMMERCIAL区域逐字保留每 TU 的 open/close 必须配平T2.6ProjectEditorForms.cpp移动 :2538-3023、:3205-3443、:3443-4005、:5497-5640T2.7ProjectEditorCommit.cpp移动 :3024-3204、:4116-4749、:5641-5715onDataset*/onGroup* 提交处理器。标题编辑不变量就地更新、禁止逐键击变更模型必须保持CANNOT-MOVE #9这些处理器只有配合 ItemIds 头才可移动T2.8ProjectEditorMultiSelect.cpp移动 :4757-5065T2.9ProjectEditorSelection.cpp移动 :4088-4114、:5073-5495。m_selected*镜像变量仍声明在门面CANNOT-MOVE #6PM-PE 循环回调拓扑不变T2.10ProjectEditorSummaries.cpp移动 :5784-6560、:6567-6677T2.11 (S2b)CustomModel.h把class CustomModelProjectEditor.h:635-680搬到独立头含Q_OBJECT需入 HEADERS 供 automoc仅ProjectEditor.cpp与FrameParserModel.cpp两个使用点加 includeroleNames()不得变化。S5c 的前置条件T2.12app/CMakeLists.txt新.cpp入SOURCES约 :309CustomModel.h入HEADERS约 :440四个 Q_ENUMCurrentView/EditorWidget/CustomRoles/ItemKind必须留在ProjectEditor.hCANNOT-MOVE #2从当前仓库源码结构可以看到这套拆分的落地形态core/Ui/ProjectEditor/ 目录下存在EditorWiring.cpp、EditorTree.cpp、EditorMqtt.cpp、EditorForms.cpp、EditorCommit.cpp、EditorMultiSelect.cpp、EditorSelection.cpp、EditorSummaries.cpp等按职责命名的族文件以及 ProjectEditorItemIds.h与 S2 任务表一一对应而残余的 ProjectEditor.cpp 从规格文档记录的 6,677 行缩减到当前 1,394 行正好落在 plan 预估的残余约 1,300 行ctor、accessors :765-1290、generateComboBoxModels、transform-editor 胶水的量级。S3ProjectHandler.cpp 的14 个跨族 static与 STOP-RULET3.1–T3.6S3 是全清单风险最高的一站ProjectHandler.cpp有约 60 个文件级 static是 static 密度最高的文件。清单的策略是先审计出确凿跨族的 14 个其余按族聚合并用一条硬性 STOP-RULE 限制爆炸半径STOP-RULE任何调用点横跨两个命令族、且不在 14 个名单内的 static —— 该族整体不拆。允许部分拆分partial split但要记录。任务新文件内容T3.1ProjectApiSupport.h(/.cpp)14 个已验证的跨族 static 移入namespace API::Handlers并去掉staticattachProjectEpoch、captureProjectEpoch、appendStaleProjectWarning、appendUnknownFieldsWarning、buildDatasetObject、datasetOptionsBitflag、summarizeProjectJson、summarizeCurrentProject、takeParam33 处调用点、makeScriptEngine、detectLanguageMismatch、frameParserCompileHint、applySimpleAlarmFields、appendDatasetWidgetTypes。验证每个辅助只定义一次33 处takeParam站点零调用点修改即可解析T3.2ProjectHandlerFile.cppregisterFile* file/snapshot/validate/template 专属 statics:3683-3860T3.3ProjectHandlerEntities.cppgroup/dataset/action/outputWidget applyDataset*Fields:4773-4930 成员辅助:157-162T3.4ProjectHandlerParser.cppparser/painter/dry-run 引擎 statics:5847-6450T3.5ProjectHandlerBatch.cppbatch list/resolver/moveT3.6app/CMakeLists.txt新.cpp入SOURCES约 :256各恰好一次新增 残余的registry.registerCommand计数之和等于原值registerCommands()留在残余 TU每族任务的验证配方都包含 static 闭包 STOP-RULE BUILD_COMMERCIAL配平 registerCommand计数贡献记录。从当前源码结构看core/Api/API/Handlers/ProjectHandler.cpp 仍作为保留注册表的残余 TU 存在与注册入口不动、命令体按族外迁的设计一致。S4 / S5协作者抽取的验收标准状态归属 信号策略S4–S5 是文件拆完之后、在不变门面背后抽真实协作者对象的设计阶段。清单对每个子阶段都写明了拥有的状态Owned state与验收标准AC并规定统一的信号策略协作者发出窄信号门面 ctor 把它连到既有 NOTIFY 名上——QML 零修改。S4ProjectModel 侧均依赖 S1子阶段协作者拥有的状态 / 验收标准要点T4aProjectFileGuardm_fileWatcher、m_diskCheckPending、m_diskPromptActive、m_diskFileHash约 150 行。AC写/加载/新建后watchProjectFile()重挂不变量完好磁盘变化仍经同一门面信号上报QML 不动T4bAutoSaveControllerm_autoSaveTimer、m_autoSaveSuspended约 90 行。ACautoSave()/syncRuntime()/m_runtimeDirty留在门面CANNOT-MOVE #4自动保存节奏与挂起/恢复行为不变不引入新竞态。依赖 T4aT4cWorkspaceSynthesizer无状态纯函数。AC合成的 workspace 与当前输出字节级一致自动重生成触发顺序ctor 围栏CANNOT-MOVE #3不变T4dLegacyMigrations无状态自由函数。AC每条遗留文件路径产出相同迁移后 JSON迁移顺序保持T4eProjectUiStateStoreUI 状态簇约 350 行:1165-1512。AC所有 getter/setter 重发同一组 NOTIFY 信号QML 绑定不变T4fProjectSerializer/Loader尾部约 1,100 行的 serialize/load 对。AC先解决ProjectDocument聚合体 vsfriend class的开放问题序列化输出与加载结果字节级一致watcher 重挂完好。依赖 T4a–T4eS5ProjectEditor 侧均依赖 S2每个 controller 都要重申标题编辑不变量就地更新条目绝不逐键击变更模型子阶段协作者范围 / 验收标准要点T5aComboBoxCatalog约 250 行combobox 模型内容一致恢复竞态守卫if (count 0) return保留T5bProjectTreeController约 2,100 行——树 选中 展开必须一起搬groupsChanged - buildTreeModel保持QueuedConnection逐字不变CANNOT-MOVE #8T5cper-entityFormControllers约 3,000 行每个接收CustomModel*、构建行、处理on*ItemChanged提交。前置条件为 S2b 的CustomModel.h依赖 T2.11T5dMultiSelectionController约 320 行批量删除/复制/移动行为一致选中镜像CANNOT-MOVE #6完好这些拥有的状态 AC式写法使得每个协作者抽取在实现前就可被评审状态边界先于代码存在行为等价以字节级一致/信号名一致/连接类型不变这类可检查的判据表述而非看起来没坏。验证纪律grep 配方、code-verify 与 Definition of Done该清单最硬核的部分是不依赖编译器也能验证纯移动的配方S1 七步S2/S3 在其上叠加增量检查定义对称grep -cE DataModel::ProjectModel::及 editor/handler 等价形式旧值 各新 TU 之和每个头声明成员恰好在一个族文件中定义static 闭包每个^static/^template辅助的调用点在同 TU 或共享头内可解析只有已审计的共享头辅助可以跨 TUinclude 闭包每个新 TU 的 include 是原文件的超集纯移动 diff所有移动片段的拼接 所有删除片段的拼接被升格辅助上static-inline除外detail 命名空间 ODRThreeAxisLayoutRefAnchor留在唯一的 TUCrud跨 TU 无重名类型grep \btr( ProjectModelShared.h- 零命中共享头不得拖入翻译上下文;CMake 恰好一次每个新文件在SOURCES/HEADERS中只出现一次。S2 追加(a) 每个kView_*使用文件 include ItemIds 头(b)BUILD_COMMERCIAL每 TU open/close 配平(c) 恰好一个class CustomModel定义、roleNames()不变。S3 追加registerCommand计数总和守恒 STOP-RULE 遵守记录。Definition of Done 汇总为 8 条全功能门禁spec.md 中每个验收标准在 S1–S5 全部落地后勾选AC6 在 S4/S5 落地前保持开放python scripts/code-verify.py --check对全部改动文件干净无新增错误qt-cpp-review跑过 C diff发现项已处理或留痕热路径未触碰——--benchmark-hotpath为正确性无需重跑维护者运行既有 project-editor / API-handler 的pytest套件清单在 plan.md 中列出——一旦失败即说明某个函数体被改动而非被移动python scripts/sanitize-commit.py见 sanitize-commit.py跑过工作树无 lint 债务diff 是所要求之事且仅此而已——纯移动、无范围蔓延、不碰外来文件spec.md状态仅在 S1–S5 全部完成后置done中间运行保持in-progress当前 spec.md 的 frontmatter 即为status: done作者 Alex Spataru。这条测试失败 体被改而非被移的判读规则是整套纪律的闭环因为行为按构造不变回归测试没有新增用例唯一的信号源就是动了不该动的字节。当前仓库中的落点与后续演化从当前仓库结构看这次拆分已成为后续重构的地基且项目目录在 0002 之后经历了整体搬迁任务清单中的app/src/...现对应core/...core/Pipeline/DataModel/Project/ 目录汇聚了数据模型侧的按职责族文件如ProjectPersistence.cpp770 行、ProjectLoader.cpp1,410 行、ProjectWorkspaces.cpp1,314 行、ProjectEntities.cpp1,375 行、ProjectFolders.cpp1,147 行、ProjectSources.cpp、ProjectTables.cpp、ProjectBulkOps.cpp等从命名与行数分布可以推断它们对应并进一步演化了 S1 的族划分Persistence/Loading/Workspaces/Crud/Folders/Tables/Bulk残余门面 core/Pipeline/DataModel/ProjectModel.cpp 现为 1,292 行对照 spec 记录的原始 8,297 行验证了每次改动只重编一个小 TU的目标仓库还沉淀了支撑该纪律的工具链scripts/tu-cutter.py 是一个确定性 TU 切分器按清单manifest逐块移动除非解析出的块能逐行重构原文件非空行、条件指令除外否则拒绝切分并对每个产出文件校验花括号与#if配平——正是 tasks.md 中纯移动 配平配方的自动化形态。结语把重构安全性变成可检查的断言0002 这份任务清单给出的核心方法论可以概括为三句话先把风险降为零再谈收益物理 TU 拆分通过 moc 中性成员定义可放在任意 TUQ_OBJECT元对象在不动的头文件里与未限定名查找把跨 TU 共享的static升格为共享头中的inline自由函数33 处takeParam调用点零修改两条语言级性质变成grep 对称 纯移动 diff即可验证的操作每个完成的前缀都是合法状态S1/S2/S3 相互独立、按风险排序任一阶段被打断树上留下的都是可构建、可测试的中间态偏差是文档的一部分单调用者降级为 static、header-only 交付、计划外搬移的autoSave()全部以 Deviation 字段留痕并解释为何仍满足不变量——这让清单本身就是可审计的执行记录。对任何持有数千行.cpp的 Qt/C 项目这套规格 - 计划 - 有序任务 - 逐阶段验证的清单式拆法以及配套的 CANNOT-MOVE 不变量与 STOP-RULE都提供了一个不依赖大规模测试基础设施也能保证移动即安全的工程模板。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表