Files
Just-DataInspector/Documents/Code_Review_Report.md
T
DeepSeek-V4.1-Flash c5d7a6fb91 docs(review): 新增 Code Review 最终报告
Documents/Code_Review_Report.md:逐项说明本轮改了什么、为什么、效果如何,
含体积节表前后对照(890,880 -> 794,112 B,-96,768 / -10.9%)、
Debug 构建缺陷(DEBUG_OPT 从未生效)、面板派发收敛与编译期保护、
ApplicationCommands 拆分、性能审查结论(含 3 个记录未做的优化点)、
注释/文档校正、验证清单、提交清单与后续建议。

同时把 Code_Review_Progress.md 补齐 P15/P16 与“搁置节点”的原因说明。
2026-11-04 03:08:45 +08:00

326 lines
15 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 报告(2026-11-04,分支 `Code_Review`)
> 计划:`Documents/Code_Review_Plan.md` | 逐节点记录:`Documents/Code_Review_Progress.md`
>
> 起点 `764e5be`(dev,已删除过时的 `UI_Refactor_*` 三份文档),终点 `f139440`。
---
## 0. 一句话结论
**体积从 890,880 B 降到 794,112 B(−96,768 B,−10.9%)**,同时
① 修掉一个"Debug 构建从来不是 Debug"的真实缺陷;
② 把"新增实验性功能面板必须手改 20+ 处"的屎山点收敛成**唯一派发点 + 编译期强制检查**;
③ 把 2,165 行的 `ApplicationCommands.cpp` 拆成 4 个职责分片;
④ 校正了项目记忆里已经失真的目录结构与编译设置。
性能方面:本轮**没有发现新的、可复现的卡顿热点**(上一轮公式映射的 4 处卡死已根治);
审到 3 个"可以再快一点"的点,但都因为**缺少可自证的验证手段**而只记录、不动手(见 §6)。
---
## 1. 体积:890,880 → 794,112 B
### 1.1 节表前后对照
| 节 | 改前 | 改后 | 差 |
| --- | --- | --- | --- |
| `.text`(代码) | 753,136 | **703,616** | −49,520 |
| `.rdata`(只读数据) | 36,936 | **26,456** | −10,480 |
| `.pdata`(SEH 展开) | 30,420 | **12,684** | −17,736 |
| `.xdata`(SEH 展开) | 29,496 | **10,156** | −19,340 |
| `.data` | 1,680 | 2,704 | +1,024 |
| `.idata`(导入表) | 12,856 | 12,692 | −164 |
| `.rsrc`(图标/清单/版本) | 20,656 | 20,656 | 0 |
| **文件总计** | **890,880** | **794,112** | **−96,768(−10.9%)** |
### 1.2 逐项来源
#### (1)`dynamic_cast` 是一个 +75 KB 的"体积炸弹"——提交 `5de3763`
`Toolbar::BuildBoxes` 里唯一的一处:
```cpp
const UiButtonWidget* raw = dynamic_cast<const UiButtonWidget*>(button.get());
```
而 `Button::widget` **本来就是 `UiButtonWidget*`** —— 这个转换毫无作用。
用最小复现程序量化(`g++ -O2 -fno-exceptions -static -s`):
| 最小程序 | 体积 |
| --- | --- |
| 纯虚函数 + 虚析构(无 `dynamic_cast`) | 39,936 B |
| 多一个 `dynamic_cast` | **115,200 B** |
| 差 | **+75,264 B** |
原因:`dynamic_cast` 需要 RTTI 元数据,并牵出 libstdc++ 的
`__dynamic_cast` → `__cxa_bad_cast` → verbose terminate handler → **`cp-demangle`**
(Release map 里 `cp-demangle.o` 占 `.text` 49,600 + `.rdata` 6,720)。
改为直接遍历 `m_buttons`(与 `m_root->Children()` 同源同序),并开启 `-fno-rtti`。
#### (2)只关 RTTI 不够:demangler 仍被 `vterminate.o` 拉入——同一提交
关掉 RTTI 后 **`cp-demangle.o` 依然在链接里**(实测只省 2 KB)。查 map 找到真凶:
```
libstdc++.a(eh_terminate.o) (__gnu_cxx::__verbose_terminate_handler())
libstdc++.a(vterminate.o) (__cxa_demangle)
libstdc++.a(cp-demangle.o) ...
```
`std::terminate()`(`eh_terminate.o`)引用 `__gnu_cxx::__verbose_terminate_handler()`,
该符号由归档成员 `vterminate.o` 提供,而它调用 `__cxa_demangle`。
**修法**:在 `Utilities/Src/MemProbe.cpp` 里**自行定义**该函数
(`std::terminate` 的未定义引用被我们满足 ⇒ `vterminate.o` 不再被拉入 ⇒ demangler 消失)。
> ⚠️ 必须定义在**全局** `__gnu_cxx` 命名空间,不能嵌在 `jdi` 里。
> ⚠️ 行为等效:应用启动后本来就会 `std::set_terminate` 换成自己的处理函数,
> 这里的默认实现只在"启动早期崩溃"时生效,同样给出内存不足提示后退出。
#### (3)Release 关闭异步展开表——提交 `94fc1c4`
`RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables`,**只**加在 Release 的两条编译规则上。
`-fno-exceptions` 下绝大多数函数不再需要 `.pdata`/`.xdata`:−36,352 B。
**代价(已评估并接受)**:Release 崩溃时 WER 拿不到可靠调用栈。
本程序不打印栈回溯,功能不受影响;Debug 构建保留完整展开表。
(`-fno-unwind-tables` 全关会链接失败,历史上踩过,不采用。)
#### (4)`.rsrc` 20,656 B 无优化空间
图标 17,404 B(已是 PNG 承载的多尺寸帧)+ manifest 1,955 B。
再压只能减图标帧或降尺寸,会影响观感,**不动**。
### 1.3 未采纳的体积方案(实测否决)
| 方案 | 实测 | 结论 |
| --- | --- | --- |
| `-fno-asynchronous-unwind-tables`(Debug 也开) | — | 否决:Debug 需要展开表才能调试 |
| `-fno-unwind-tables`(全关) | 链接失败 | 否决(历史已踩) |
| 替换 `wcstod` 以去掉 `strtodg/gdtoa/gethex/hexnan` | ≈20 KB | 暂缓:公式/输入框的数值语义必须与现状逐位一致,收益不足以承担风险 |
| 图标继续瘦身 | — | 否决:观感优先 |
---
## 2. 构建系统:修掉一个"Debug 从来不是 Debug"的缺陷——提交 `94fc1c4`
审查 Makefile 时发现:
```make
DEBUG_OPT = -O0 # 定义了
...
# Debug 目标文件编译规则
$(CXX_COMPLIER) -c $(CXXFLAGS) $(RELEASE_OPT) ... # ← 用的是 -Oz!
```
**`DEBUG_OPT` 从未被任何规则引用**,且全流程没有 `-g`。
所谓 "Debug 构建" 实际上只是"不 strip 的 Release",**根本没有调试信息、也不是 `-O0`**。
现在 Debug 规则改用 `$(DEBUG_OPT) -g`,链接也加 `-g`。
| 构建 | 改前 | 改后 | 说明 |
| --- | --- | --- | --- |
| Release | 890,880 | **794,112** | 体积优化 |
| Debug | 1,801,617 | **5,994,805** | 变**大**是**正确**的:真实 `-O0 -g` |
> ⚠️ 这意味着"Debug 构建更慢、更大"从此名副其实;如果以后仍在用 `Build/Debug` 做
> 日常运行,请改用 Release。
---
## 3. 架构:面板分派收敛为唯一派发点——提交 `7a3fefb`
### 3.1 问题(屎山核心)
`Lane` 里 7 种实验性功能(边沿计数 / 中心轴 / 均值滤波 / 弱峰调整 / 自由标记 /
重采样 / 选区截取)各持有一份 `LanePanelState` 与一个滚动偏移,共 14 个字段。
旧实现在**每一处**"按种类取用"的地方各写一遍嵌套三元式或 `switch`:
| 位置 | 旧写法 |
| --- | --- |
| `EdgePanelLayoutFor` 的 `enabled` / `open` / `collapsed` / `storedX` / `storedY` | **5 条 7 层嵌套三元式** |
| 标题栏拖拽 | 7 分支 `switch`(漏 `MARK`/`RESAMPLE` 各一次 ⇒ 面板拖不动) |
| ✕ 关闭 | 7 分支 `switch` + `default`(漏 `RESAMPLE` ⇒ 关掉的是别的面板) |
| 滚轮翻页 | 三元式链 + `switch`(漏 `CLIP` ⇒ 永远滚不到底) |
| 滚动条拖动 | `switch` + `default` |
| 折叠状态读写 | 2 个 `switch` |
| 首现错开序数 | 局部 `kOrder[]` + `switch` |
新增一种面板要改 **20+ 处**,漏一处就是运行时 BUG —— 历史上 `MARK`、`RESAMPLE`、
`CLIP` **三种面板各自都真实踩过**。
### 3.2 修法
`PlotViewInternal.hpp` 新增唯一派发点(各带可写/只读重载):
```cpp
LanePanelEnabled(lane, kind); // 功能是否启用
LanePanelStateOf(lane, kind); // 展开/折起/位置
LanePanelScrollOf(lane, kind); // 内容滚动偏移
```
调用点全部改为访问器:布局里 5 条嵌套三元式 → 3 行;标题栏拖拽 7 分支 → 1 次写入;
滚轮/滚动条/✕/折叠全部改为穷举。
### 3.3 ⭐ 关键收益:编译期保护
这些 `switch` **穷举 7 种且一律不写 `default:`** ⇒ `-Wswitch`(包含在 `-Wall` 里)
在新增 `EdgePanelKind` 而漏登记时**直接编译报警**。
已用最小程序验证该机制确实生效:
```
warning: enumeration value 'C' not handled in switch [-Wswitch]
```
⇒ **今后新增实验性功能面板,忘记登记会编译失败,而不是运行时静默出错。**
(对比:旧代码用 `case COUNT: default:` 兜底,`-Wswitch` 永远不会报。)
### 3.4 验证
release + debug 零 warning;**产物体积不变(793,088 B)** —— 这正好说明本次是
纯粹的等价结构收敛,没有引入额外代码。
---
## 4. 架构:拆分 2,165 行的 `ApplicationCommands.cpp`——提交 `8437100`
原文件混杂六类职责。按职责**纯搬移**(零逻辑改动、函数体一字未改):
| 文件 | 行数 | 职责 |
| --- | --- | --- |
| `ApplicationTabs.cpp` | 355 | 标签页新建/关闭/切换/重排、视图同步、窗口标题、标签栏回调、标签页右键菜单 |
| `ApplicationDerive.cpp` | 786 | 派生烘焙、重采样、选区截取(新建 / 就地)、克隆(含带调整) |
| `ApplicationMenus.cpp` | 409 | 绘图区右键菜单、菜单数值回写、主题切换、设置菜单、菜单结果派发 |
| `ApplicationCommands.cpp` | 642 | 分组/信号增删、移动与重排、文件对话框、工具栏动作、键盘鼠标分发、布局边界绘制 |
Makefile 按目录通配收集源文件,**无需登记**。
体积 793,088 → 794,112 B(+1,024,LTO 内联边界变化,< 0.13%)。
---
## 5. 审查中"没有发现问题"的项(同样重要)
这些是**主动查了但结论是干净**的,避免后续接手者重复怀疑:
| 检查项 | 方法 | 结论 |
| --- | --- | --- |
| 死代码(未使用的静态函数) | `-Wall` 零警告(GCC 会对未使用的 `static` 函数报警) | 无 |
| 死字段(只声明不使用) | 对 `PlotView` / `SignalPanel` / `Application` / `TabBar` / `Toolbar` / `DataSet` 的全部 `m_*` 成员做全仓引用计数 | 233+ 个成员**无一**只声明不使用 |
| TODO / FIXME / 临时方案 | 全仓 grep | 无(源码里的"未完成"全是"边沿统计进行中"的业务语义) |
| 布局文本测量是否重复开销 | 读 `UiMeasureTextWidth` 实现 | 已记忆化(键 = 字体 + 文本),不是热点 |
| 页面上是否残留指向已删文件的注释 | grep `UI_Refactor_*` / `refactor_report` / `structcheck` / `.preflight` | 仅 1 处(`PlotView.hpp` 的 `FormulaEnvelopeEntry` 注释),已删 |
---
## 6. 性能审查结论
### 6.1 结论
**没有发现新的、可复现的卡顿热点。** 上一轮(第 32 轮)的四处卡死同源于
"公式信号幅值范围/包络各扫一遍整条信号",已通过 `DataSet::FormulaTransformFor`
(一次扫描 + 缓存,命中 0.0015 ms)根治。
逐一复核了下列曾出过问题的路径,均仍走快路径:
| 路径 | 现状 |
| --- | --- |
| `BuildLaneVisuals` 的幅值范围 | 指纹缓存(`m_laneRangeKey`),命中时 O(1) |
| 场景/装饰/面板/图层像素缓存 | 键齐全,未发现遗漏项 |
| 边沿统计 | 按帧预算 + 每状态配额推进,暂停条件齐备 |
| 图层预建 | `EnsureLaneLayersPrefetched`(不再整批重建) |
| 信号面板行集合自愈 | `RowsSignature` 每帧 O(组数),组数远小于信号数 |
### 6.2 记录但未实施的三处"还能再快一点"
| 位置 | 量级 | 为什么不改 |
| --- | --- | --- |
| `EdgeScanFor` 的累积状态查找是**线性扫描** `m_edgeScans`(上限 2×信号数+余量)。288 信号时每帧约 16 万次指针比较 | ~0.3 ms/帧 | 改哈希索引需要重做"淘汰最旧已完成状态"的逻辑;收益与风险不匹配,且该路径已被预算化 |
| `EdgePanelLayoutFor`(957 行分支)整体改为"每面板一行元数据表" | 可读性 + 少量代码体积 | **没有离屏像素对比工具**(`.preflight` 已删,崩溃会弹模态框),纯逻辑重写无法自证"像素完全一致" |
| `SceneContentHash` 里 6 段逐面板混入循环可合并 | 可读性 | 合并会改变哈希值;虽然"多加字段安全、少加字段危险",但在无法逐像素验证的前提下不值得冒险 |
### 6.3 关于"性能验证手段"的重要说明
本轮**没有**做任何 GUI 探针,原因(也是给后续接手的约束):
* 用户要求不占用其鼠标/键盘/屏幕;
* `.preflight` 测试框架已被用户删除,且已明确"不要重建";
* `-fno-exceptions` + `MemProbe::OnTerminate()` 会在探针崩溃时弹**模态"内存不足"框**,
反复运行就是反复弹窗(第 26 轮的真实教训)。
因此本轮的体积结论全部来自**可复现的静态测量**(`objdump -h` 节表 + `Makefile` 变量 +
非 LTO 目标文件尺寸),架构结论来自**编译期检查**(`-Wswitch`)与**等价性论证**
(体积逐字节不变)。
---
## 7. 注释与文档
### 7.1 删除
* `Documents/UI_Refactor_Plan.md`、`UI_Refactor_Progress.md`、`UI_Refactor_Report.md`
(描述 2026-10-28 那一阶段,已被后续多轮实现推翻,与当前代码不一致)。
### 7.2 校正(`agent_mem.md`)
| 位置 | 原内容(错) | 现内容 |
| --- | --- | --- |
| §2 | Release 用 `-Os` | `-Oz` + LTO + `-s` |
| §2 | 未提体积开关 | 补充 `NO_EXCEPTIONS` / `NO_RTTI` / `LTO` / `RELEASE_ONLY_OPT`,并警告"改 Makefile 必须删 `.o` 全量重编"(本轮实测:只改 Makefile 不会触发重编,会误以为"优化无效") |
| §2 | 链接库少 `msimg32` | 已补 |
| §3 | 目录里写着早已不存在的 `Utilities/CsvTable/`、`Utilities/DataModel/`、`Utilities/PaintUtil/`、`Utilities/PopupMenu/`… | 按现状重写(`Components/Shared` 承载通用控件与 `PopupMenu`/`ColorPickerPopup`;`Utilities` 为数据与格式层),并写明依赖方向 |
| 末尾 | — | 新增「第 33 轮」完整记录 |
### 7.3 源码注释
* `PlotView.hpp`:删除指向已删除代码的 `FormulaEnvelopeEntry` 注释块。
* 新增/改写的注释一律说明**为什么**(尤其是 `-fno-rtti`、`__verbose_terminate_handler`
覆盖、`-fno-asynchronous-unwind-tables` 的代价),避免后续被"好心"改回去。
---
## 8. 验证清单
| 项 | 结果 |
| --- | --- |
| `make release`(删 `*.o`/`*.d` 全量) | **零 error、零 warning** |
| `make debug`(真实 `-O0 -g`,全量) | **零 error、零 warning** |
| Release 产物 | 794,112 B(< 1 MB 红线) |
| `cp-demangle.o` 是否仍占 `.text` | 已无 `.text` 段分配 |
| `__gnu_cxx::__verbose_terminate_handler` | 定义在**本产物内**(`0x140057ed1`),非库导入 |
| 导入 DLL 列表 | `comdlg32 / GDI32 / gdiplus / KERNEL32 / MSIMG32 / SHELL32 / USER32` + UCRT,无新增依赖 |
| 工作区状态 | 干净;4 个节点提交 + 1 个文档提交 + 1 个 dev 提交 |
---
## 9. 提交清单
| 提交 | 分支 | 内容 |
| --- | --- | --- |
| `764e5be` | `dev` | 删除过时的 `UI_Refactor_*` 三份文档 |
| `86ff029` | `Code_Review` | 新增 `Code_Review_Plan.md` / `Code_Review_Progress.md` |
| `5de3763` | `Code_Review` | 移除多余 `dynamic_cast` + 关闭 RTTI + 覆盖 terminate handler(−61,440 B) |
| `94fc1c4` | `Code_Review` | Release 关闭异步展开表(−36,352 B)+ 修正 Debug 构建参数 |
| `7a3fefb` | `Code_Review` | 面板分派收敛为唯一派发点(编译期保护) |
| `8437100` | `Code_Review` | 拆分 `ApplicationCommands.cpp` → 4 个职责分片 |
| `f139440` | `Code_Review` | 清理过时注释 + 校正 `agent_mem.md` §2/§3 |
---
## 10. 后续建议(按性价比排序)
1. **重建离屏验证手段**(不弹窗口、不占用用户输入)。
没有它,"重构面板几何 / 缓存键"这类工作无法自证正确性 —— 这正是本轮 §6.2
三项被搁置的唯一原因。建议做纯控制台 + `SW_HIDE` + 逐像素哈希比对,并且
**绝不调用 `Application::Initialize()`**(会 `LoadCommandLineFiles` → 失败 → 模态框)。
2. **`EdgeScanFor` 状态查找哈希化**(0.3 ms/帧 @288 信号)。
3. **`EdgePanelLayoutFor` 表驱动化**(需第 1 项就绪)。
4. 若要继续压体积:`wcstod` 自写替身(≈20 KB,需逐位一致性验证)、
图标帧裁剪(观感优先,不建议)。