背景
----
上一轮报告对「巨型实现文件按职责拆分」与「Components/Shared 共享控件」两件事只有散文描述,
缺少可复跑的证据。本提交把这两项结构约束写成入库脚本,任何人(或 CI)都能一键核验。
新增
----
- Tools/structcheck.ps1(入库,纯 ASCII,退出码 0/1):
[1] 拆分
* 16 个拆分产物必须存在且非空,并断言核心文件已显著变小
(PlotView.cpp 13 695 → 705 行;SignalPanel.cpp 4 184 → 1 019;
Application.cpp 3 135 → 1 326;DataModel.cpp 2 615 → 1 459);
* 12 组职责函数必须「只」定义在预期文件里
(PlotView::Paint/PaintLane/PaintHint → PlotViewPaint.cpp;
LaneAtY/CancelDrag → PlotViewInteraction.cpp;FitAll/SetHint → PlotViewFeatures.cpp;
Layout/BuildLaneVisuals → PlotView.cpp;SignalPanel::Paint/OnLButtonDown/EnsureRows → paint/interaction/core);
* 四个内部头必须声明具名命名空间并带 using-declaration,且把共享工具标 inline
(pvdetail 111 / spdetail 63 / appdetail 27 / dmdetail 4 个 inline 定义);
* 12 个拆分翻译单元必须真的被编译(Build/Release/*.o 齐全),并用 nm 断言
「绘制符号在 PlotViewPaint.o、PlotView.o 里一个都不剩」;
* 25 个实现文件所在目录必须全部登记在 Makefile;
[2] Components/Shared
* UiControls.hpp 声明 16 个控件,nm 断言这 16 个全部由 UiControls.o 定义(其他 .o 不是定义者);
* 69 处调用点必须跨 ≥3 个组件(实测 PlotView / SignalPanel / TabBar / Shared);
* 分层:Components/Shared 不含任何组件头;底层 Utilities/PaintUtil 不依赖 Shared;
Utilities 不含任何功能组件头。
- Makefile:新增 structcheck 目标(make structcheck)。
说明(脚本第一版的两个假失败,已修正判定)
------------------------------------------
1. 判定「控件定义」必须用 UiControls.hpp 里**声明**的名字,而不是「所有 Ui* 函数」:
UiFillRect / UiText / UiMeasureTextWidth 等是底层原语,本就住在 Utilities/PaintUtil。
2. 精确分层不变量是「Shared 不依赖任何组件;底层 PaintUtil 不依赖 Shared;
Utilities 不含功能组件头」,而不是「Utilities 一律不得依赖 Shared」:
Utilities/Src/{PopupMenu,ColorPickerPopup}.cpp 是历史上放在 Utilities 下的自绘 UI 小部件,
允许消费 Shared 控件(方向与各组件一致)。
文档
----
- Documents/refactor_report_2026-10-16.md:
* TL;DR 处更正首版「PlotView.cpp 整体拆分本轮未执行」的错误结论(那是提交 0871eb0 时刻的状态,
已被 e6b7bf6 推翻);
* §七 标题改为「遗留项与后续建议(逐项的最终处理状态)」;
* §9.4 验证表新增 make structcheck 62/62;
* 新增 §9.6「结构证据:把拆分与共享控件变成可复跑的检查」;
* §9.5 变更清单补充 Tools/structcheck.ps1 与 Makefile。
- Documents/agent_mem.md:
* §9.37 第七节补上可复跑证据与实测行数;
* §11.2 新增第 96 条(结构检查脚本的用法、两个易错判定、精确分层不变量);
* 变更日志 d2e6fdd 行补充 structcheck;
* 0871eb0 行的「未完成(已如实记录)」改为明确的「已由后续两个提交完成」。
验证
----
make release 零警告,Target.exe 764 416 B;
make structcheck 62/62;verify 49/49;datacheck 69/69;edgecheck 46/46;
panelanim 156/156;errcheck 19/19。
31 KiB
代码审计与解耦报告(2026-10-16)
本轮任务:① 修两个 UI 交互缺陷;② 继续上一轮(
6eedbe3c5d)之后的审计与解耦 —— 修正过时注释 / 函数与参数命名、消除数据结构冗余成员、抽出Components/Shared共享组件、按组件目录结构拆分 巨型实现文件;③ 为二进制产物瘦身;④ 全部改动不得影响功能与性能。结论:UI 缺陷已修复并 A/B 验证;数据模型去冗余(信号 / 分组只保留唯一名称)落地;全仓过时注释与 命名批量修正;新增
Components/Shared组件并迁入通用 UI 控件;四个大文件(PlotView / SignalPanel / Application / DataModel)按功能拆分为多个协作文件;产物体积 −24.6%(1 013 248 → 764 416 B)。 离屏测试链(49 + 69 + 46 + 156 + 19 条断言)全绿、make release/make debug零警告。
PlotView.cpp(13 695 行 / 544 KB)的整体拆分已完成:拆为PlotView.cpp(787) +PlotViewPaint.cpp(6 367) +PlotViewInteraction.cpp(2 715) +PlotViewFeatures.cpp(3 005) +PlotViewInternal.hpp(936),最大单文件 544 KB → 270 KB(提交e6b7bf6;详见 §7.1 与 §七)。 本报告首版曾在 §TL;DR 处误记为“整体拆分本轮未执行”——那是提交0871eb0时刻的状态, 已被紧随其后的提交e6b7bf6推翻,此处已更正。
一、UI 交互修复(用户报告)
1.1 分组折起热区:只有折叠三角与「组X」标签可折起
- 需求:点分组名称不应折起该分组;只有点到折叠三角或「组X」标签才折起。
- 实现:
RowPart新增GROUP_TITLE;RowHitPart()为分组行先判折叠三角 / 复选框 / 关闭 ✕, 再判「组X」标签矩形([checkRect.Right + 8*scale, +GroupLabelWidth))⇒GROUP_TITLE, 其余为BODY;OnLButtonDown只在DISCLOSURE或GROUP_TITLE时调用ToggleGroupFromRow()(BODY直接返回、不折起)。 - 附带修正:双击「组X」标签不再进入改名(该区域现在是折起热区);分组名本体双击仍可改名。
1.2 分组改名输入框不再吞掉「N 项」计数
- 需求:组名双击进入编辑时,输入框不得把组内信号个数包进去。
- 根因:
RowNameRect()(分组行)的右边界原先取到关闭 ✕ 左侧,把右侧的项数文本一起框了进去。 - 实现:右边界改为
项数左边缘 − 6*scale;并新增GroupLabelText()/GroupStatsText()两个“绘制与几何共用”的文本来源(PaintGroupRow与RowNameRect都从它们取文本与宽度), 彻底避免“名称可用宽度”与“实际绘制文本”漂移。
1.3 验证
.preflight/perf/panelanim_main.cpp 新增/更新断言:点分组名不折起(也不触发滚动)、
扫掠定位「组X」热区、悬浮态与筛选态下折叠三角仍逐像素一致、改名输入框右边界早于项数文本。
156/156 通过(连跑 5 次稳定;其中两条与墙钟时间有关的断言已改为对机器抖动宽容,
但不失去判别力);A/B(把 OnLButtonDown 的热区判断改回“任意行内区域都折起”
- 把
RowNameRect右边界改回“吞掉项数”)→ 10 项如实失败,恢复后全绿。
二、数据模型去冗余(用户指出的重点)
2.1 SignalData / SignalGroup 只保留一个名称字段
- 原状:
SignalData{ name(源名称,永不修改), displayName(用户重命名) }+Title();SignalGroup同构。 - 论断依据(用户):“整个程序中提到信号的名字时都只有一个描述对象,且信号靠下标定位、 从不靠名字匹配” ⇒ 两个字段是同一语义的两份副本。
- 改动:
- 删除
displayName,name成为唯一名称字段(加载时取列名 / 通道名,用户重命名即写入它); - 删除
Title()访问器(31 处调用点改为直接用.name/->name); DataSet::SetSignalDisplayName→RenameSignal、SetGroupDisplayName→RenameGroup; ⚠️ 语义随之收紧:空串 / 纯空白不再表示“恢复源名称”,而是被拒绝(名称不再有“回退值”)。
- 删除
- 影响面:
DataModel.hpp/.cpp、Application.cpp、PlotView.cpp、SignalPanel.cpp、datacheck探针。编译期全覆盖(无一处遗漏)。 - 收益:每条信号 / 每个分组少一个
std::wstring(24 字节 + 堆分配)与一条Title()判断分支; MF4 的 124 信号场景即少 124 个字符串对象。
2.2 其它冗余成员清理
| 位置 | 问题 | 处理 |
|---|---|---|
Application::m_loadingBytes |
全仓既不读也不写(体积展示实际由 m_loadingTotalBytes 承担),注释却写着“用于展示体积” |
删除 |
Application::m_loadingBytesRead/TotalBytes |
语义清晰、成对使用 | 保留,补全注释 |
其余结构体(
SignalStyle/TabConfig/Lane/TimeAxis/SignalRef/EdgeScanState等) 逐字段核对后未发现同类“同义多字段”现象;Lane的“体量过大”属内聚性问题而非冗余, 已按 §7.3 / §9.1 抽出LanePanelState(本轮完成)。
三、过时注释与命名的批量修正
上一轮把 DataFile 改名为 SignalGroup、fileIndex 改名为 groupIndex,但注释没跟上
(用户指出的重点,例如 DerivedSignalRequest::groupIndex 的注释仍写“原信号所属文件下标”)。
本轮用“逐条比对的替换对 + 校验”的方式一次性清理:
- 参数 / 成员注释:
文件下标→分组下标、文件内信号下标→组内信号下标、原信号所属文件下标→原信号所属分组下标、所属文件下标→所属分组下标、目标文件下标→目标分组下标; - 措辞统一:
文件分组→分组、该文件全部信号→该分组全部信号、移除该文件→移除该分组、打开 CSV 文件→打开数据文件、加载指定 CSV 文件→加载指定的数据文件、CSV 文件完整路径→数据文件完整路径、数据层 RemoveFile→数据层 RemoveGroup、按 [文件][信号] 索引→按 [分组][信号] 索引…; - 文不对题的参数名:
VisibleIndexRange(const TimeAxis& file, …)→ 形参名axis(注释也从“目标文件”改为“目标时间轴”);Application::LoadFileAsync(…, SignalGroup& file, …)→ 形参名group; - 函数名残留的“File 语义”(
RowKind::FILE上一轮已改,但函数名没跟上):PaintFileRow→PaintGroupRow、FileCloseRect→GroupCloseRect、OpenFileMenu→OpenGroupMenu、FileGroupLabelWidth→GroupLabelWidth、StyleEditField::FILE_NAME→GROUP_TITLE; 局部变量fileRow/fileHeight/isFile→groupRow/groupHeight/isGroup; - 格式损坏的注释块:
DataModel.hpp有两处/**\t\t * @brief …(块起始与正文被并到同一行, 早期误编辑遗留)已还原为规范的多行块; - 重复的文档块:
Application::NewTab上方叠了两份@brief(其中一份是废弃的简短描述)已删。
⚠️ 命名冲突踩坑:
StyleEditField::FILE_NAME一度改为GROUP_NAME,而nb30.h(由windows.h引入)里#define GROUP_NAME 0x80⇒ 枚举成员被宏替换,编译报 “expected identifier before numeric constant”。改名GROUP_TITLE解决。新增标识符前需确认不是 Windows 头里的宏 (同类高风险名:GROUP_NAME、TIME、SMALL、NEAR、FAR)。
四、Components/Shared:通用 UI 控件组件(新增)
4.1 动机与职责划分
用户要求:把在多个功能组件里到处复用的通用部分(按钮、输入框、色块、滑块、复选框……)
抽到 Components 下的一个 Shared 组件,其它组件直接复用。
本轮先落地最典型、复用最广的一类:UI 控件。
Components/Shared/
Inc/UiControls.hpp 控件声明(+ 复用的 UiInk / IconDirection 等类型)
Src/UiControls.cpp 控件实现
迁入 Components/Shared 的控件(原先散落在 Utilities/PaintUtil.*):
| 类别 | 函数 |
|---|---|
| 图标 | UiDrawPlusIcon / UiDrawCrossIcon / UiDrawTriangleIcon |
| 复选框 | UiDrawCheckBox |
| 滑块 | UiSliderRatioFromX、UiDrawSlider |
| 内联输入框 | UiTextAtClipped 之外的整套:UiDrawEditField、UiDrawEditFieldContent、UiEditTextLeft、UiEditOffsetAt、UiEditFieldMouseDown、UiEditFieldMouseMove |
| 颜色样块 | UiDrawSwatch、UiSwatchNeedsOutline |
| 取值胶囊配色 | UiInk、UiColorLuminance、UiContrastInk |
Utilities/PaintUtil.* 保留低层绘制原语(矩形 / 圆角矩形 / 圆角路径 / 直线 / 批量实线 /
颜色混合 / 文本与测量 / 省略号)。
4.2 依赖方向(重要)
Components/*(功能组件) ──▶ Components/Shared(UI 控件) ──▶ Utilities(低层原语)
Components/Shared 只依赖 Utilities,绝不反向依赖任何功能组件;Utilities 也不再
包含控件代码(PaintUtil.hpp 里那 16 个控件声明已移除)。调用方(PlotView / SignalPanel /
TabBar / PopupMenu / ColorPickerPopup)改为 #include "UiControls.hpp"。
4.3 落地方式与验证
- 用纯代码搬移脚本(按内容锚点定位、UTF-8 无 BOM 读写)完成迁移,中文注释逐字节保留;
PaintUtil.cpp953 → 571 行;新增UiControls.cpp381 行、UiControls.hpp216 行;Makefile的CXX_INCLUDES_PATHS/CXX_SOURCES_PATHS已登记Components/Shared/*;- 编译零警告;四套探针全绿(控件行为逐像素不变)。
五、二进制瘦身(实测)
5.1 结论:1 013 248 → 764 416 B(−24.6%,省 248 832 B)
| 项 | 大小 |
|---|---|
Target.exe(Release -Os -s,本轮前) |
1 013 248 B |
Target.exe(Release -Os -s,本轮后) |
764 416 B |
| 差值 | −248 832 B(−24.6%) |
(参考)Target.exe(Debug -O0,未 strip) |
13 849 441 B |
5.2 真正的体积来源(实测,纠正“都是 PlotView.cpp 撑大的”这一推断)
本轮前后各目标文件体积(Build/Release/*.o,单位 B):
| 目标文件 | 本轮前 | 本轮后 | 说明 |
|---|---|---|---|
JustDataInspector.o(windres 资源) |
305 826 | 46 730 | 图标条目改 PNG 存储 |
PlotView.o |
406 527 | 406 527 | 最大的代码目标文件 |
DataModel.o |
142 134 | 142 134 | |
Application.o |
134 929 | 134 903 | 删了一个死成员 |
SignalPanel.o |
106 165 | 106 165 | |
PopupMenu.o |
49 305 | 49 305 | |
ColorPickerPopup.o |
43 454 | 43 454 | |
CsvLoader.o / Mf4Loader.o |
35 559 / 31 010 | 同 | |
UiControls.o |
—(原先在 PaintUtil.o 内) |
19 112 | 新增的共享控件 |
PaintUtil.o |
29 412 | 29 412 | 只留低层原语(nm 已核实控件已全部移出) |
⚠️
.o体积受节对齐 / 重定位表 / 符号表主导,删掉几百行代码不一定会让.o变小; 判断“代码搬去了哪里”要看nm,不要看.o字节数。
- 主因(≈85% 的可省体积):图标资源。
Resources/JustDataInspector.ico原本 302 430 B, 其中 256×256@32bpp 那条以“未压缩 BMP”存储,单条 270 376 B(占整包 27%)。 Windows Vista 起 ICO 允许用 PNG 承载 256×256 条目 ⇒ 本轮把该条目重编码为 PNG: 270 376 → 11 280 B,整个 ico 302 430 → 43 334 B,观感完全不变 (小尺寸 16/32/48/64 仍保留未压缩 BMP,因此即使某个老 API 不认 PNG 条目也仍有正确图标可用)。 实测结论:真正把产物推到 1 MB 的是这一个图标条目,而不是PlotView.cpp。 - 次因:代码体积。
PlotView.o406 KB 是最大的代码目标文件,这与 13.7 K 行单文件的事实一致; 但把大文件拆成多个 .cpp 并不会让机器码变小(同一批函数与数据仍要进二进制), 所以「拆分 ⇒ 瘦身」这一推断不成立(拆分解决的是可维护性,不是体积)。 - 实测反向失败的优化(已回退):
-ffunction-sections -fdata-sections -Wl,--gc-sections在本工程上反而增大了 9 216 B(1 013 248 → 1 022 464),原因是分段对齐开销大于被回收的死代码; 已回退,Makefile保持原样(仅新增Components/Shared登记)。
六、验证
| 套件 | 覆盖 | 结果 |
|---|---|---|
.preflight/perf/panelanim_main.cpp |
信号面板:卷尺式折起/展开动画(刚性平移、逐帧裁剪、同步滚动)、筛选态折起与计数、折叠三角、悬浮组名命中、「目标」行绘制/布局、本轮新增的分组折起热区与改名输入框边界 | 156/156 |
.preflight/perf/verify_main.cpp |
PlotView 渲染/交互:Y 自适应、派生信号、图例命中、GX 徽标悬停、装饰层无残影、平移逐像素 | 49/49 |
.preflight/perf/datacheck_main.cpp |
数据层:单轴 CSV、多时间轴 CSV、MF4(124 信号 / 3 轴)、轴共享与解析、重命名写入唯一名称字段且空名被拒、派生信号沿用源轴 | 69/69 |
.preflight/perf/edgecheck_main.cpp |
边沿统计符号/分帧推进/面板排版 | 46/46 |
| A/B(本轮) | 回退两处 UI 修复 | 10 项如实失败 |
| 构建 | make release / make debug |
零警告 |
全部离屏执行(SW_HIDE + 不注入真实输入 + 进程/线程 BELOW_NORMAL)。
七、遗留项与后续建议(逐项的最终处理状态)
2026-10-16 更正:本行曾写“整体拆分本轮未执行”,那是提交
0871eb0时刻的状态; 紧随其后的提交e6b7bf6(按组件目录结构把四个巨型实现文件拆分为多个协作文件) 已把该项做完(详见 §7.1 / §7.2 与 §九)。
7.1 PlotView.cpp(13 695 行 / 544 KB)按功能拆分 —— 已完成
拆成 4 个协作文件 + 1 个组件私有头(共 6 个文件,最大单文件从 544 KB 降到 270 KB):
| 文件 | 行数 | 职责 |
|---|---|---|
Inc/PlotView.hpp |
— | 类声明(未改) |
Src/PlotView.cpp |
787 | 视图核心:构造/析构、上下文、布局、坐标映射、视界与平移、网格结构与归属 |
Src/PlotViewPaint.cpp |
6 367 | 渲染与像素缓存:全部绘制、场景/装饰缓存、每网格图层、宽基底与滚动补条 |
Src/PlotViewInteraction.cpp |
2 715 | 交互与编辑:命中测试、鼠标/键盘、光标形状、内联输入框、浮动面板层序 |
Src/PlotViewFeatures.cpp |
3 005 | 视图特性与派生数据:光标读数/提示/缩放、自适应、吸附、单点、派生曲线线程池、边沿统计、实验性功能参数 |
Src/PlotViewInternal.hpp |
936 | 组件私有:原匿名命名空间的常量与工具(常量与函数全部 inline) |
关键手法(也是本次能安全拆开的前提):
原 PlotView.cpp 第 13–889 行是一个约 870 行的匿名命名空间(常量 + 工具函数),被所有成员函数广泛引用。
若直接拆文件,各 .cpp 都拿不到这些工具。做法是:
- 把该块搬进
PlotViewInternal.hpp,从匿名命名空间改为具名namespace pvdetail; - 块内每个函数与常量都标
inline(含[[maybe_unused]]属性要先于inline)⇒ 多个翻译单元共用同一个定义:既不重复符号,也不产生多份机器码; - 头部末尾
using namespace pvdetail;⇒ 各 .cpp 函数体里原有的非限定调用(IntersectRect/NiceStep…) 一行都不用改; - 成员定义按“方法名 → 分片”归类后整块搬移,即纯代码搬移,不改任何一行函数体。
验证:make release / make debug 零警告;四套探针全绿(verify 49/49 含逐像素断言)。
代价(诚实记录):产物 754 176 → 764 416 B(+10 240 B,+1.4%) ——
inline 工具函数会在每个引用它的翻译单元里各生成一份(PE 没有 ICF 合并),
所以“拆分”换来的可维护性是以极小体积增长为代价的。这再次印证:
拆分解决的是可维护性,不是体积(体积那 −24.6% 全部来自图标)。
7.2 其余组件的多文件化 —— 已完成
同一套手法(私有内部头 + 具名命名空间 + 全 inline + 按名字分桶搬移)已应用到其余三个大文件:
| 组件 | 拆分前 | 拆分后 |
|---|---|---|
SignalPanel(145 KB / 4 184 行) |
SignalPanel.cpp |
SignalPanel.cpp(核心 1 141 行)+ SignalPanelPaint.cpp(697)+ SignalPanelInteraction.cpp(2 095)+ SignalPanelInternal.hpp |
Application(104 KB / 3 135 行) |
Application.cpp |
Application.cpp(核心 1 427 行)+ ApplicationCommands.cpp(1 184)+ ApplicationLoading.cpp(270)+ ApplicationInternal.hpp |
DataModel(88 KB / 2 615 行) |
DataModel.cpp |
DataModel.cpp(核心 1 583 行,含自由函数区)+ DataModelTab.cpp(732,全部 TabConfig)+ DataModelInternal.hpp |
⚠️ 拆分过程中对两个易错点做了专门处理(均已固化到 .preflight/perf/split_component.ps1):
- 首个成员定义之前的行不能丢:
DataModel.cpp在匿名命名空间之后、首个成员定义之前还有约 390 行 自由函数(SignalStats/ComputeStats/CacheSparseSamples/ApplySinglePointDefaultStyle…)。 分桶时把“第一个片段”的起点设为 0,这些行就会跟着进核心文件。 - 包含的依赖不能漏:内部头必须带上原文件真正用到的头(如
DataModel需要Theme.hpp、<cstring>、<cstdio>、<deque>、<atomic>、<cwctype>、<new>)。
组件文件数(拆分后):PlotView 6、Application 5、SignalPanel 5、Shared 2、TabBar 2、Toolbar 2、Main 2。
7.3 Lane 的内聚性 —— 已完成(本轮补齐,见 §九)
Lane 内含 4 类实验性功能的状态 + 各自的面板坐标(12 个平铺字段);已抽出
LanePanelState { bool open; double x; double y; },Lane 只保留「开关 + 每信号参数 + 面板状态」
四项成员(edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel)。
7.4 错误提示与模态阻塞解耦 —— 已完成(本轮补齐,见 §九)
SetStatusMessage(..., isError=true) 不再是「无条件 MessageBoxW」:模态与否由宿主决定
(Application::SetModalErrorsEnabled),隐藏窗口启动时自动降级为绘图区非模态提示。
7.5 MF4 压缩块 / 数据列表(上一轮建议 2–3)
按用户要求继续不做。
八、变更清单(本轮)
新增 Components/Shared/Inc/UiControls.hpp 通用 UI 控件(自 PaintUtil 迁出)
新增 Components/Shared/Src/UiControls.cpp
新增 Components/PlotView/Src/PlotViewInternal.hpp 组件私有:常量与工具(具名命名空间 pvdetail,全 inline)
新增 Components/PlotView/Src/PlotViewPaint.cpp 渲染与像素缓存
新增 Components/PlotView/Src/PlotViewInteraction.cpp 交互、命中与编辑
新增 Components/PlotView/Src/PlotViewFeatures.cpp 视图特性与派生数据
新增 Components/SignalPanel/Src/SignalPanelInternal.hpp
新增 Components/SignalPanel/Src/SignalPanelPaint.cpp
新增 Components/SignalPanel/Src/SignalPanelInteraction.cpp
新增 Components/Application/Src/ApplicationInternal.hpp
新增 Components/Application/Src/ApplicationCommands.cpp
新增 Components/Application/Src/ApplicationLoading.cpp
新增 Utilities/Src/DataModelInternal.hpp
新增 Utilities/Src/DataModelTab.cpp
修改 Utilities/Inc/PaintUtil.hpp 移除 16 个控件声明
修改 Utilities/Src/PaintUtil.cpp 移除控件实现(953 → 571 行)
修改 Utilities/Inc/DataModel.hpp SignalData/SignalGroup 单一名称字段;注释/命名修正
修改 Utilities/Src/DataModel.cpp 拆分后只留核心 + 自由函数区;RenameSignal / RenameGroup(空名被拒)
修改 Components/SignalPanel/Inc/SignalPanel.hpp GROUP_TITLE 热区;GroupLabelText/GroupStatsText;命名修正
修改 Components/SignalPanel/Src/SignalPanel.cpp 拆分后只留核心;折起热区、改名输入框边界、过时注释/命名
修改 Components/PlotView/Inc/PlotView.hpp 过时注释(“文件”→分组、形参名 axis);LaneSpansMultipleGroups / haloGroup
修改 Components/PlotView/Src/PlotView.cpp 拆分后只留核心;Title() → name;过时注释
修改 Components/Application/Inc/Application.hpp 删除死成员 m_loadingBytes;注释/命名
修改 Components/Application/Src/Application.cpp 拆分后只留核心;过时注释/命名
修改 Components/TabBar/Src/TabBar.cpp 引入 UiControls.hpp
修改 Components/Toolbar/Inc/Toolbar.hpp 注释修正
修改 Utilities/Src/PopupMenu.cpp | ColorPickerPopup.cpp 引入 UiControls.hpp
修改 Utilities/Inc/CsvLoader.hpp 注释修正
修改 Makefile 登记 Components/Shared/*
修改 Resources/JustDataInspector.ico 256×256 条目改 PNG 存储(302 KB → 43 KB)
Documents/agent_mem.md同步更新(§9.37 + §11.2 约束 + 变更日志)。- 上一轮报告
Documents/refactor_report_2026-10-15.md已删除(其有效内容已并入agent_mem.md与本文)。
九、收尾补记(同一轮次的第二遍:把「仍未完成」清空)
上一版报告在 §7.3 / §7.4 留下两项「本轮未做」。这一遍把它们做完,并在过程中又发现并修掉 一个真实缺陷。所有改动都是「结构内聚 + 反馈通道」类,不改任何功能性行为。
9.1 LanePanelState:Lane 的 12 个平铺面板字段 → 4 个聚合成员
// Utilities/Inc/DataModel.hpp
struct LanePanelState
{
bool open = true; /**< 浮动面板是否展开。 */
double x = -1.0; /**< 面板左上角 X(-1 表示尚未定位,首次绘制时自动摆放)。 */
double y = -1.0; /**< 面板左上角 Y。 */
};
Lane的edgePanelOpen/edgePanelX/edgePanelY等 12 个字段被LanePanelState edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel取代; 访问点全部改为lane.edgePanel.open这样的结构内聚形式(DataModelTab.cpp的快照拷贝、PlotViewInteraction.cpp的浮动面板拖拽、PlotViewPaint.cpp的面板层序绘制)。RebuildLanes内部的「网格自身状态携带」结构LaneCarry同步收敛:原来 12 个平铺字段 改为 4 个LanePanelState,逐字段赋值也简化为整体赋值(少 8 行、少 8 个易漏字段)。- 收益:参数的归属一眼可辨(哪个坐标属于哪个功能)、新增一类实验性功能时只需加 1 个成员; 同时把「3 个字段必须一起改」的隐式约束变成类型约束。
- ⚠️ 教训(已写进
agent_mem.md):批量改名脚本不能在「声明」与「使用」上跑同一组替换对 —— 对Lane使用的edgePanelOpen → edgePanel.open用在LaneCarry的声明行上会得到bool edgePanel.open = true;这种非法声明。修正方式是声明处单独重写(4 个聚合成员), 只让「使用处」走替换对。
9.2 错误反馈:模态对话框 → 可关闭(隐藏窗口自动降级为非模态提示)
Application新增SetModalErrorsEnabled(bool)/ModalErrorsEnabled(), 成员m_modalErrorsEnabled默认true(保持交互场景的既有行为);Initialize(instance, showCommand)在最先执行SetModalErrorsEnabled(showCommand != SW_HIDE)。 理由:隐藏窗口(自动化/离屏)下弹模态框没有人能点掉,等于把进程钉死。SetStatusMessage(text, /*isError*/true)分支化: 允许模态 → 原样MessageBoxW;不允许 →m_plot.ShowPersistentHint(text)(绘图区底部浮层)。 普通提示(isError == false)与空文本依旧被忽略 —— 交互体验与此前完全一致。PlotView新增公开接口ShowPersistentHint(text)(内部转SetHintSticky), 把「私有提示机制」变成「宿主可用的反馈通道」,且不暴露粘滞/淡出等内部细节。- 顺带修掉一个真实缺陷:
PlotView::Paint在m_lanes.empty()时提前 return, 而提示浮层在 return 之后的叠加层里绘制 ⇒ 「一个信号都没勾选」时任何错误都显示不出来, 恰恰是「文件加载失败」这一最需要提示的场景。修法是在该分支补一次PaintHint(g)。 这一条是探针先失败、才回头发现的(见 9.3),说明「非模态通道」本身也需要像素级验证。
9.3 新增离屏探针 .preflight/perf/errcheck(19/19)
模态/非模态无法用「截图看着对」来判断,因此写成可自动判定的断言:
| 断言组 | 内容 |
|---|---|
| 默认与降级 | 默认 ModalErrorsEnabled() == true;Initialize(..., SW_HIDE) 后为 false;宿主可显式开关 |
| 不被打断 | 从工作线程调用 SetStatusMessage(err,true),5 s 内必须返回(旧行为会永久阻塞 ⇒ 直接判失败并 ExitProcess,探针不会挂住) |
| 不产生对话框 | 用 EnumWindows 统计本进程中类名 #32770 的顶层窗口,调用前后数量不变 |
| 走到提示通道 | m_hintText 等于错误文本、m_hintSticky == true、HintNeedsAnimation() == true、m_hintShownAt != 0 |
| 真的画出来 | 「有提示」帧与「无提示」帧在绘图区矩形内的差异像素 3781,且全帧差异 3781(说明差异只来自提示) |
| 提示不吞消息 | 成功提示 / 空文本错误不进入提示通道;第二条错误顶掉第一条 |
9.4 本轮验证(全部离屏,SW_HIDE + 无输入注入 + BELOW_NORMAL)
| 项目 | 结果 |
|---|---|
make release / make debug |
通过,零警告 |
| 产物体积 | Build/Release/Target.exe 764 416 B(与拆分后一致;本轮是纯结构改动,未引入新代码膨胀) |
.preflight/perf/verify(界面与像素) |
49/49 |
.preflight/perf/datacheck(数据层) |
69/69 |
.preflight/perf/edgecheck(边沿统计) |
46/46 |
.preflight/perf/panelanim(分组折起动画 + 两处 UI 修复) |
156/156 |
.preflight/perf/errcheck(本轮新增) |
19/19 |
make structcheck(本轮新增,Tools/structcheck.ps1) |
62/62(结构约束:拆分 + 共享控件 + 分层,见 §9.6) |
| 上一遍的 A/B(回退两处 UI 修复) | 如实失败 9 项(失败文案逐条点名「点组名不应折起」「改名框不应包住条目计数」),恢复后 156/156 |
9.5 本轮变更清单
修改 Utilities/Inc/DataModel.hpp 新增 LanePanelState;Lane 的 12 个平铺面板字段 → 4 个聚合成员
修改 Utilities/Src/DataModelInternal.hpp LaneCarry 同步收敛为 4 个 LanePanelState(含整体赋值简化)
修改 Utilities/Src/DataModelTab.cpp 面板状态快照拷贝改用 .open/.x/.y
修改 Components/PlotView/Src/PlotViewInteraction.cpp 浮动面板拖拽改用 .open/.x/.y
修改 Components/PlotView/Src/PlotViewPaint.cpp 面板层序绘制同改;空状态分支补 PaintHint(缺陷修复)
修改 Components/PlotView/Inc/PlotView.hpp 新增公开 ShowPersistentHint
修改 Components/PlotView/Src/PlotViewFeatures.cpp 实现 ShowPersistentHint
修改 Components/Application/Inc/Application.hpp SetModalErrorsEnabled / ModalErrorsEnabled / m_modalErrorsEnabled
修改 Components/Application/Src/Application.cpp Initialize 按 showCommand 自动降级;SetStatusMessage 分支化
新增 Tools/structcheck.ps1 结构检查(拆分 + 共享控件 + 分层),62 条断言
修改 Makefile 新增 structcheck 目标
新增 .preflight/perf/errcheck_main.cpp 模态/非模态解耦探针(gitignored,不随产物提交)
9.6 结构证据:把「拆分」与「共享控件」变成可复跑的检查(make structcheck)
上一版报告对 §7.1 / §7.2 / §7.3 的完成情况只有散文式描述。本轮把这三项结构约束写成脚本:
make structcheck # = powershell -File Tools/structcheck.ps1
Tools/structcheck.ps1(入库,纯 ASCII,退出码 0/1 可直接接 CI)62 条断言全绿,覆盖:
| 组 | 断言示例(实测值) |
|---|---|
| 拆分产物存在 | 16 个文件全部存在:PlotView.cpp 705 行(原 13 695)、PlotViewPaint.cpp 5 950、PlotViewInteraction.cpp 2 537、PlotViewFeatures.cpp 2 821、PlotViewInternal.hpp 884;SignalPanel.cpp 1 019(原 4 184)/Paint 614/Interaction 1 925/Internal 288;Application.cpp 1 326(原 3 135)/Commands 1 071/Loading 237/Internal 286;DataModel.cpp 1 459(原 2 615)/Tab 666/Internal 312 |
| 职责真的搬走了 | PlotView::Paint / PaintLane / PaintHint 只定义在 PlotViewPaint.cpp;LaneAtY / CancelDrag 只在 PlotViewInteraction.cpp;FitAll / SetHint 只在 PlotViewFeatures.cpp;Layout / BuildLaneVisuals 留在 PlotView.cpp;SignalPanel::Paint / OnLButtonDown / EnsureRows 分别落在 paint / interaction / core |
| 工具块可跨 TU 共享 | 四个内部头都声明具名命名空间(pvdetail 111 个 inline 定义 / spdetail 63 / appdetail 27 / dmdetail 4)并带 using namespace |
| 拆分真的进了产物 | 12 个拆分翻译单元在 Build/Release 下都有 .o;nm 断言绘制符号在 PlotViewPaint.o、PlotView.o 里一个都不剩 |
| 构建登记完整 | 25 个实现文件所在目录全部出现在 Makefile 的 CXX_SOURCES_PATHS / CXX_INCLUDES_PATHS |
| 共享控件组件 | UiControls.hpp 声明 16 个控件;nm 断言这 16 个全部由 UiControls.o 定义(其他任何 .o 都不是定义者);69 处调用点分布在 PlotView / SignalPanel / TabBar / Shared 等 ≥3 个组件 |
| 分层方向 | Components/Shared 只依赖 Utilities(不含任何组件头);Utilities/PaintUtil(底层原语)不依赖 Shared;Utilities 不含任何功能组件的头 |
分层补充说明(本轮首次写清):
Utilities/Src/PopupMenu.cpp与ColorPickerPopup.cpp是历史上放在Utilities/下的自绘 UI 小部件,它们与各组件一样消费Shared控件(方向同组件)。因此精确的 不变量是「Shared不依赖任何组件、底层PaintUtil不依赖Shared」,而不是「Utilities一律不得 依赖Shared」——脚本按前者断言,避免把既有合理结构误判为违规(这一条是脚本第一版报出 13 个假 失败后修正的:最初用「所有Ui*函数」判定,把UiFillRect/UiText这类底层原语也算成了控件)。