Files
Just-DataInspector/Documents/Code_Review_Plan.md
T
DeepSeek V4.1 Flash 221f9d2410 test(tools): 行数组不变式收紧为"18 个全部等于行数"
背景:`AppendRow()`(提交 85aaed5)已经成为新增面板行的唯一入口,
18 个平行数组由它一次补齐。因此旧的宽松判据("13 个必须等长 +
4 个重采样专属列允许为空")不再需要 —— 任何一列短于行数,
就是"有人绕过 AppendRow 手写 push"的直接证据。

改动:
* `panels` 检查:改为断言 18 个数组长度**全部**等于 `rowRefs.size()`,
  失败时打印全部 18 列的长度便于定位。
* `panelscroll` 检查:同样收紧(大文件 566 行时才会暴露的问题)。
* `Tools/README.md`:同步不变式描述。

文档:
* `Documents/Code_Review_Plan.md` 新增 §八(第 36 轮节点与硬约束)。
* `Documents/Code_Review_Progress.md` 新增第 36 轮(两次重构 + 三个搬移陷阱)。
* `Documents/Code_Review_Report.md` 新增 §13(拆分职责表、为何不做完整合并、
  失败可见性对比、后续建议)。
* `Documents/agent_mem.md` 新增第 36 轮规则。

验证:
* `make release` 全量零 error / 零 warning,产物 **785,408 B**。
* `UnitTests` → checks=260 failed=0;`OffscreenChecks -all` → 6/6 PASS,checks=101 failed=0。
* `panels -mode compare` → 7 个面板像素哈希与几何逐项不变。
* `panelscroll`(MF4 566 行)→ 18 个行数组全部 = 566。
2026-11-04 15:20:07 +08:00

274 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Code Review 计划(分支 `Code_Review`)
> 目标:在不引入性能回归、不改变用户可见行为的前提下,对全项目做一次**结构 + 性能 + 体积**
> 的查漏补缺,并把「改一个功能要动 40 处」的屎山点收敛成表驱动/单一实现。
>
> 基线(本计划开始时):
> - 分支 `Code_Review`,起点 `764e5be`(dev 已删除过时的 `UI_Refactor_*` 三份文档)。
> - `make release` 产物 **890,880 B**(`.text` 753,136 B、`.rdata` 36,936 B、`.rsrc` 20,656 B、
> `.pdata` 30,420 B、`.xdata` 29,496 B、`.idata` 12,856 B)。
> - 源码约 **5.6 万行**;最大单文件 `PlotViewPaint.cpp` 6,561 行、`PlotView.hpp` 4,654 行。
>
> 约束:
> - 每完成一个节点**编译验证(release + debug 零警告)**并**提交**,同时更新
> `Documents/Code_Review_Progress.md`。
> - 性能相关节点必须给出**同一次运行内的相对量**(历史教训:绝对毫秒阈值不可靠)。
> - 不占用用户的鼠标/键盘/屏幕(只用纯控制台探针或源码级核对)。
---
## 一、已确认的问题(本计划的事实基础)
### A. 体积
| 项 | 实测 | 说明 |
| --- | --- | --- |
| `dynamic_cast`(`Toolbar.cpp:370`) | **+75,264 B** | 最小复现程序:39,936 B → 115,200 B。它会链入 RTTI 元数据 + `cp-demangle.o`(map 里 **56,320 B**)。而该处 `Button::widget` **本来就是 `UiButtonWidget*`** ⇒ 转换完全多余。 |
| `-fno-rtti` | 未启用 | 移除上一条后即可开启,进一步去掉 `type_info` 表。 |
| `.pdata` + `.xdata` | 60 KB | `-fno-exceptions` 已开;SEH 展开表仍存在。可尝试 `-fno-asynchronous-unwind-tables`(历史上链接失败,需复核)。 |
| `libmingwex` 的 `strtodg/gdtoa/mingw_wpformat` | 27.8 KB | 来自 `wcstod` / `wprintf` 类调用;可评估用自写解析替代 `wcstod` 的收益(公式/输入框解析)。 |
| `.rsrc` | 20.7 KB | 图标(PNG 帧)+ manifest + 版本信息,已优化过。 |
| 重复常量/重复实现 | 多处 | `kFormulaEnvelopeBuckets` 已清理;仍有 `ClampDouble` 两份实现等。 |
### B. 性能(结构性热点)
1. **面板开关/参数处理是 6 个 `EdgePanelKind` × ~20–26 处 switch 分支**(实测每个 kind 出现
21–26 次,全部由手写 `if/switch` 维护)。这是**新增功能必然漏改 2–3 处**的根本原因
(历史已发生 4 次:`MARK` 漏拖动、`RESAMPLE` 漏拖动+关闭、`CLIP` 漏滚轮+缓存键)。
2. **`SceneContentHash` 里 6 段几乎相同的逐面板混入循环**(`PlotViewCacheKey.cpp`)。
3. **`EdgePanelLayoutFor` 是巨型分支**,`EdgePanelLayout` 结构体带 20+ 个按面板类型区分的
矩形数组;高度算式与几何算式分开写(历史上多减一个 pad 造成空白空白 BUG)。
4. **`PlotView` 有 30+ 个缓存**,每个都手写「键 + DC + 位图 + 贴图 + 失效」五件套;
失效路径散落在 8 个文件里,`m_sceneValid=false` 出现 6 处。
5. `ApplicationCommands.cpp` **2,165 行**混杂:标签页、数据派生、菜单、工具栏、键盘、鼠标。
6. `PlotView.hpp` 4,654 行(声明 + 内联实现),单文件认知负担极大。
### C. 过时/错误注释与命名(抽样已确认)
- `agent_mem.md` §3「目录结构」仍写着已不存在的 `Utilities/CsvTable/`、`Utilities/DataModel/`
等目录;§2 写 `-Os` 而实际是 `-Oz`。
- 源码中留有历史轮次的「本轮」「上一轮」叙述(对当前读者无意义),以及被推翻方案的注释。
- `PlotViewInternal.hpp` 里 `kFormulaEnvelope*` 注释已随实现迁移,需同步。
---
## 二、节点计划
> 优先级:**S**(安全、收益明确、风险低)→ **A**(结构调整,中风险,需逐步验证)→
> **B**(深度重构,必须有可验证的前后一致性)。
### 阶段 0:建立基线(节点 P0)
- P0.1 记录基线:release/debug 体积、`.text/.rdata` 等节表、关键路径的可复跑测量程序
(纯控制台,链接 `Build/Release/*.o`,不入库)。**已完成基线的部分见下表**。
- P0.2 建立 `Documents/Code_Review_Progress.md`,每节点完成后追加一行结论 + 数字。
### 阶段 1:体积(节点 P1–P5,安全优先)
- **P1 移除 `dynamic_cast` + 开启 `-fno-rtti`**
- 把 `Toolbar::BuildBoxes` 的 `dynamic_cast<const UiButtonWidget*>` 改为直接用
`Button::widget`(已是该类型)或给 `UiWidget` 加虚函数 `ContentBounds()`。
- Makefile `CXXFLAGS += -fno-rtti`;全仓确认无 `typeid`/`dynamic_cast`/`type_info`。
- 预期 **−70~80 KB**。验收:产物下降 ≥60 KB;功能逐项核对(工具栏按钮、布局边界调试)。
- **P2 体积审计第二轮**
- 复核 `-fno-asynchronous-unwind-tables`(历史上链接失败,试 `-fno-unwind-tables` 组合)。
- 评估 `wcstod` 替身(自写定点/浮点解析,仅在公式与输入框使用)去掉
`strtodg/gdtoa/gethex/hexnan`(约 20 KB)。
- 评估 `--gc-sections` 在 LTO 下是否已生效(读 map 确认无死节)。
- 验收:逐项给出「测得的字节数 + 是否采纳」的结论,**不接受「感觉变小」**。
- **P3 重复实现归并**
- `ClampDouble` 两份(`PlotViewInternal.hpp` / `UiLayout.cpp`)→ 统一到 `Shared`。
- 扫描全部 `static`/匿名命名空间重复工具函数,合并为单一实现。
- 重复字符串/常量字面量、重复的三元式开关表。
- **P4 死代码与未使用成员清理**
- 用非 LTO 对象 + `nm` 反查「定义但从未被引用」的函数;删除确认无引用的实现。
- 清理 `PlotView.hpp` 中已无人读取的成员/字段。
- **P5 资源与启动数据**
- `.rsrc` 复核(图标帧数、manifest、版本字符串);`.idata` 导入表复核。
### 阶段 2:性能(节点 P6–P10)
- **P6 建立可复跑的渲染性能探针(纯控制台)**
- 目标:能在**不弹窗口**的情况下测量 `BuildLaneVisuals` / `SceneContentHash` /
`EdgePanelLayoutFor` / 面板绘制 / 信号面板绘制的耗时。
- 方式:链接 `Build/Release/*.o` + 手工构造 `DataSet`/`TabConfig`,只调用纯函数与
`PlotView` 的非窗口方法(避免 `Application::Initialize` 触发模态框,见历史教训)。
- 所有断言用**同一进程内的相对量**。
- **P7 面板元数据表驱动(核心)**
- 新增 `EdgePanelSpec`(每 kind 一行:命令 id、默认尺寸、是否列表型、footer 类型、
滚动字段偏移、面板状态字段偏移、绘制函数指针、命中函数指针)。
- 把 6×~20 处 `switch (kind)` 收敛为「查表 + 3 个通用实现(列表型 / 表格型 / 固定行型)」。
- 验收:**逐像素**对比改造前后面板(离屏渲染哈希);新增一个假 kind 只需加一行表项即可
被绘制/命中/拖动/关闭/滚动全覆盖(用编译期断言或探针验证)。
- **P8 `SceneContentHash` 混入收敛**
- 把 6 段重复循环改为「按面板表遍历 + 每面板一个 `MixContent()`」。
- 验收:改造前后哈希值**逐位相同**(同一状态)。
- **P9 `EdgePanelLayoutFor` 表驱动 + 高度/几何同源**
- 高度算式与内容几何共用同一份 `PanelMetrics` 计算(消除重复减 pad 类 BUG)。
- 验收:探针打印「内容区下边界 vs 按钮上边界」的像素差,与改造前一致。
- **P10 缓存五件套抽象(低风险版)**
- 抽出 `OffscreenCache`(DC/位图/键/贴图/失效),**只替换三处结构完全相同的**
(derived / decor / panel),保留 scene 与 lane layer 不动(热路径最复杂)。
- 验收:逐像素一致 + 帧时间不回退。
### 阶段 3:架构(节点 P11–P14)
- **P11 `ApplicationCommands.cpp` 拆分**
- → `ApplicationTabs.cpp`(标签页)/ `ApplicationDerive.cpp`(派生/重采样/截取/克隆)/
`ApplicationCommands.cpp`(命令分发)/ `ApplicationMenus.cpp`(上下文菜单与设置菜单)。
- 纯搬移,零行为变更;Makefile 无需改动(按目录通配收集)。
- **P12 `PlotView.hpp` 拆分**
- 把 `EdgePanelLayout`、派生曲线缓存条目、图层结构体等**纯数据结构**搬到
`PlotViewTypes.hpp`;把 `PlotView` 的内联小函数按关注点分块放到 `PlotViewInline.hpp`。
- 目标:`PlotView.hpp` ≤ 2,500 行。
- **P13 UI 基础层职责核对**
- 核对 `UiWidget` / `UiComponent` / `UiLayout` / `UiBox` / `UiSurface` / `UiButtonWidget` /
`UiControls` 的边界是否重叠;合并确认冗余的(只保留一套矩形/命中/布局原语)。
- 明确依赖方向:`Components/*` → `Components/Shared` → `Utilities`(校验无反向)。
- **P14 绘制/功能组件边界**
- `PlotViewPaint` / `Raster` / `Panels` / `Features` / `Interaction` 的职责再核对;
把「面板几何」与「面板绘制」的交叉依赖理顺(几何 → 共享 `PanelMetrics`)。
- 把散落在 `PlotView` 里的「颜色/线宽/胶囊样式」常量收敛到 `Theme`/`PaintUtil`。
### 阶段 4:注释与命名清理(节点 P15)
- **P15 注释/命名审计**
- 删除「本轮/上一轮」叙述、被推翻方案的描述、与实现不符的注释。
- 统一命名:`*Dc`/`*DC`、`Lane*` vs `Grid*`、`Edge*` vs `Panel*` 等不一致处。
- 确保所有注释与当前实现一致(重点:`PlotViewInternal.hpp`、`PlotViewCacheKey.cpp`)。
- 同步更新 `agent_mem.md` 的 §2/§3(已经不准确的目录结构与 `-Os`)。
### 阶段 5:验证与报告(节点 P16)
- **P16 最终验证与报告**
- release + debug 零警告;产物体积对比;关键路径探针全部通过。
- 生成 `Documents/Code_Review_Report.md`:逐节点写「改了什么 / 为什么 / 效果 /
未做的事与原因」,并给出体积与性能的前后对照表。
---
## 三、验收标准(硬性)
1. `make release` 与 `make debug` **零 error、零 warning**。
2. 产物体积**必须下降**(目标:从 890,880 B 降到 ≤ 780 KB;若某项无法达成,在报告里
说明实测字节数与原因)。
3. 不改变任何用户可见行为(面板几何、绘制像素、交互语义一致)。
4. 每条性能结论都必须有**同进程相对量**或**逐像素/逐位一致性**证据。
5. 每个节点单独提交,提交信息用中文、说明改动与验证方式。
---
## 四、风险与对策
| 风险 | 对策 |
| --- | --- |
| 面板表驱动改造引入像素差异 | 改造前后用离屏渲染哈希对比;先改 `CLIP`/`MARK` 两个最简单面板做样板,再推广 |
| `-fno-rtti` 破坏第三方/库代码 | 全仓 grep 确认;先单独提交,失败可独立回退 |
| 缓存抽象拖慢热路径 | 只抽象非热路径(derived/decor/panel),scene/lane 不动;每次改完测帧时间 |
| 大文件拆分时误改逻辑 | 纯搬移(不重写函数体),逐文件 diff 核对;先拆 `ApplicationCommands` |
| 探针触发模态「内存不足」框 | 绝不在探针里调 `Application::Initialize()`(历史教训);只构造数据层/纯函数 |
---
## 五、进度索引
进度与每节点的实测数字见 `Documents/Code_Review_Progress.md`。
最终结论见 `Documents/Code_Review_Report.md`。
---
## 六、第 34 轮追加(2026-11-04):验证基础设施 + 后续建议落地
> 触发:§三/§10 之后剩下的三项建议(`EdgeScanFor` 哈希化、`EdgePanelLayoutFor` 表驱动化、
> `wcstod` 自写替身)都卡在**"没有可自证的验证手段"**上。
> 用户要求:先把验证工具做成仓库内的正式资产(`Tools/` 入库),再动被建议的代码。
### 6.1 节点与验收标准
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **T1** | `Tools/` 解除忽略;新增 `Tools/Makefile`;两个工具目录 + `README.md` | `make -C Tools` 构建全部、`make UnitTests` 构建单个、`make clean` 只清工具产物;改头文件/增删源文件自动重编重链 |
| **T2** | `Tools/UnitTests`:数据层语义单元测试 | `result=PASS`,断言数 ≥ 200 |
| **T3** | `Tools/OffscreenChecks`:隐藏窗口渲染 + 帧序列 + 面板像素基线 + 边沿统计参考实现 + 性能 | 零失败;全程不显示窗口、不动真实鼠标/键盘 |
| **T4** | 建议 #2:`EdgeScanFor` 状态查找哈希化 | **先量后改**;性能收益必须大于体积代价,否则回退 |
| **T5** | 建议 #3:`EdgePanelLayoutFor` 表驱动化 | 面板像素基线 A/B **逐像素一致**才允许合入 |
| **T6** | 建议 #4:`wcstod` 自写替身(省 gdtoa 链) | 体积收益可测且数值语义逐位一致 |
### 6.2 本轮新增的硬约束
1. **工具不得把产物写进 `Tools/`**:截图 / 基线 / 报告一律写 `Temp/tools/`(已被忽略);
`Tools/*/Build/` 同样不入库(其它协作者本地 `make -C Tools` 重建)。
2. **工具与本体 ABI 必须一致**:`-fno-exceptions`、`-fno-rtti`、链接时 `-flto -flto-partition=one`;
工具源码自身**不**编成 LTO 字节码(GCC 15.2.0 会在 `choose_baseaddr` 处 ICE)。
3. **"先量后改"**:任何以性能 / 体积为名的改动,必须在同一台机器、同一次会话内给出前后数字;
收益低于噪声、或换来体积上涨的,一律回退,并把数字写进 `Code_Review_Report.md`。
4. **像素基线只做本地 A/B**:哈希与 DPI/字体/数据文件绑定,**不入库、不当黄金图**。
---
## 七、第 35 轮追加(2026-11-04):浮动面板架构评估与收敛 + 整数格式化替换
> 触发:用户指出「7 个实验性功能面板观感/骨架高度一致,但每新增一种或改一次布局
> 总会出意料之外的错误」,要求先**评估**(能否抽公共基类 / 公共组件,浮动面板、
> 二级菜单、右键菜单是否算公共组件),再动手;同时把 §11.5 的
> 「替换 `std::to_wstring` / `std::to_string`」做掉。
### 7.1 节点与验收标准
| 节点 | 内容 | 验收标准 |
| --- | --- | --- |
| **P20** | 面板「特征层」:唯一分类 / 标题 / 滚动几何来源 | 面板像素基线与几何**逐项不变** |
| **P21** | 滚动设置收敛为唯一实现(7 个包装 + 2 条调用链) | `panelscroll` 新检查全绿 |
| **P22** | 全仓替换整数格式化(76 + 2 处) | 面板像素不变 + 单元测试逐字符对照 |
| **P23** | 其余审查清理(垃圾形参 / 重复判定) | 零 warning、像素不变 |
### 7.2 本轮新增硬约束
1. **新增一种实验性功能面板,只允许改三个地方**:
`LanePanelScrollOf` / `PanelStyleOf` / `PanelTitle`(都在 `PlotViewInternal.hpp` /
`PlotViewPanels.cpp`)。三者都是穷举 `switch` 且**不写 `default:`**,
漏登记会被 `-Wswitch` 编译报警。
**禁止**再在宽度 / 布局 / 绘制里各写一份 `kind == XXX` 组合判断。
2. **底部"有按钮行"与"是构建按钮"是两个概念**(`PanelStyle::footerButtons()`
vs `buildButton`)。混用会让面板高度与内容区高度不一致。
3. **整数转文本一律用 `Utf8Util::FormatInteger*`**,不要用 `std::to_wstring` /
`std::to_string`(它们把 MinGW printf 引擎拉进产物,且 libstdc++ 会强制
`__USE_MINGW_ANSI_STDIO=1`,命令行关不掉 —— 见 Makefile 注释)。
4. **改面板几何/绘制前后必须跑** `OffscreenChecks -run panels -mode record/compare`;
本轮它当场抓到了"截取面板少了 37px 高度"的回归。
---
## 八、第 36 轮追加(2026-11-04):§12.7 两项建议的落地
> 触发:用户要求「继续按照上轮报告的后续建议推进」。§12.7 列了两项:
> ① `EdgePanelLayout` 平行数组合并;② `PaintEdgeCursor` 拆分。
| 节点 | 内容 | 结论 |
| --- | --- | --- |
| **P26** | 拆分 `PaintEdgeCursor` 三段内容绘制 | ✅ 完成(新建 `PlotViewPanelPaint.cpp`) |
| **P27** | 平行数组错位隐患 | ✅ 用 `AppendRow()` 唯一入口消除(**未做**完整结构合并,理由见下) |
| **P28** | 收紧不变式 | ✅ 18 个数组**全部**必须等于行数(不再允许"重采样专属列可为空") |
### 8.1 本轮新增硬约束
1. **新增一种面板行,只能走 `EdgePanelLayout::AppendRow(...)`**。
18 个平行数组由它一次补齐;参数行 / 重采样行一律 `rowXxx.back() = …` **赋值**。
**禁止**在任何布局分支里手写 `rowXxx.push_back(...)`。
2. **面板内容绘制按排版类别分文件**:骨架(边框/标题栏/✕/按钮)在 `PlotViewPaint.cpp`,
内容在 `PlotViewPanelPaint.cpp`。新增一种面板 = 加一个 `PanelStyleOf` 分支 +
在对应内容函数里加一支。
3. **`OffscreenChecks -run panels` 的长度不变式现在是"全等"判定**:
任何一列短于行数即失败(这条检查是"绕过 `AppendRow`"的唯一探测器)。
### 8.2 为什么没有做「完整合并成 `std::vector<PanelRow>` + 列视图」
技术上可行(`PanelColumn<T>` 持有 `std::vector<PanelRow>*` + 成员指针,
读点只需在 `layout.rowXxx` 后加一对括号),但 **`EdgePanelLayout` 需要自定义拷贝构造/赋值**
来重新绑定视图;而它是 `EdgePanelLayoutFor()` 的返回值、又被存进 `std::vector`,
拷贝是热路径 —— 拷贝语义一旦写错,会产生**比原缺陷更隐蔽的悬垂视图**(原缺陷至少会被
长度不变式抓到,悬垂指针不会)。
本轮先用 `AppendRow()` 把**真实隐患**("某分支漏推一列")消除,
完整结构合并留待"逐行几何基线"检查就绪后再做。