Files
DCU_DesktopController/docs/compose/spec/host-code-quality.md
T

237 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.
---
feature: host-code-quality
status: delivered
updated: 2026-09-23
branch: main
commits: 2c6235d..beeae8a(实施段 67dd170 / b70f5ed / 261caf5 / beeae8a)
---
# 上位机代码质量评估(冗余 / 复杂度 / 屎山风险)
## Report
**What was built** — 全仓质量评估 **+ 按 R1–R16 分优先级整改**。评估结论:综合 **3.5/5**(改出来的屎山,仍可救)。实施后:`main.js` 4647→**1894** 行;`esp32_server.py` 1536→**986** 行(逻辑进 `ecat_client.py` / `ecat_proc.py`);正式 `scripts/` 128→**16**;根目录报告迁入 `docs/reports/`。
### 整改对照
| ID | 状态 | 落点 |
|----|------|------|
| R1 非幂等不重发 | **done** | `ecat_client.py` `_ecat_target_idempotent`;副作用路径 autostart 后不自动重发 |
| R2 网卡默认 I210 | **done** | README + `ecat_proc.ECAT_DEFAULT_ADAPTER` + `js/common/ecat-bus.js` |
| R3 跨进程互斥/复用 | **done** | `_EcatProcXLock` + 先复用健康监听实例 |
| R4 拆 main.js | **done** | `scope/ota/seq/conn-poll.js`,入口只装配 |
| R5 版本/参数下沉 | **done** | `waitVersion` / `queryNodeIdParam` 进 transport |
| R6 ECAT bootstrap 单点 | **done** | `js/common/ecat-bus.js` |
| R7 toast/quiet 去重 | **done** | `toast-stack.js` + utils |
| R8 拆 server | **done** | `ecat_client.py` + `ecat_proc.py` |
| R9 桥/代理 path 归一 | **done** | 客户端统一路径解析 |
| R10 双路径 | **done(文档化)** | 桥与代理最终同 ecat_client,未强行合并 |
| R11 小冗余 | **done** | BASE_DIR 单次等 |
| R12 scripts | **done** | 出库 31 个,保留 16 个正式脚本 |
| R13 lab | **done** | dump 出库,保留实验页 |
| R14 报告归档 | **done** | `docs/reports/` |
| R15 README | **partial** | 结构/清单/CSS 所有权已改;长篇 ECAT 实测未整段迁出 |
| R16 CSS | **partial** | 所有权注释 + `?v=`;两套 btn/toast 刻意分域未强并 |
**Verification** —
- `py_compile esp32_server.py ecat_client.py ecat_proc.py` PASS
- `test_esf_pack.py` PASS;`test_app_lease.py` ALL PASS(7);`test_motor_refresh.mjs` 62/62 PASS
- `test_ecat_autostart.mjs` 24 通过 / **2 失败 PRE-EXISTING**(拆分前已存在,与 250µs 硬约束相关)
- 全部新/改 JS `node --check` PASS
- 真机 `e2e_web_send_latency.py` / APP 点发**未跑**(需设备;按仓库规则后续应补)
**Journey log** —
- worktree add 被沙箱拦 → 用户授权 main
- P1/P2 子代理中断后主会话接手,验证后补提交
- `Measure-Object -Line` 丢空行 → 用 `ReadAllLines().Length`
- autostart 2 失败标 PRE-EXISTING 而非回归
- 顺序:先止血(R1–R3)再拆文件,避免在坏边界上大搬家
## [S1] Problem
用户要求评估 `G:\Robot_v2\DCU\tools\ESP32` 这套 ESP32 调试上位机在持续迭代后,是否已经变成「屎山」:有没有冗余、过度复杂、功能堆叠和不可维护的结构。随后要求**先提交基线,再按 R1–R16 逐项整改**。
## [S2] Design
### 评估范围与方法
- **范围**:本仓全量——生产路径(`pages/` `js/` `css/` `esp32_server.py` `tabs.html`)+ `scripts/` `lab/` `_local/` + 根目录报告。姊妹仓 `tools/ecat` 仅评估本仓对其的耦合面,不深挖 native 源码。
- **方法**:文件体积与行数盘点 → 模块职责/函数热点扫描 → 三路只读审计(电机前端、Python 服务端、脚本杂物)→ 对照 README/spec/gitignore 与近期 churn 核验证据。
- **判定标准**:
- **领域复杂度**:协议/双链路/实时性约束本身带来的必然复杂度,不可删。
- **结构债**:上帝文件、重复状态机、越界补丁、文档漂移——可治理。
- **卫生债**:一次性脚本、采样 dump、根目录报告——可归档删除。
### 总判定
| 维度 | 评分 (1=干净 … 5=屎山) | 一句话 |
|------|------------------------|--------|
| 电机前端 `js/motor/main.js` | **3.5** | 有意识治理中的上帝文件;再拖一轮需求会到 4+ |
| Python 服务端 `esp32_server.py` | **3.5** | 有纪律的补丁山:注释极好,职责爆炸 |
| 脚本 / lab / _local 杂物 | **4.0** | 策略(.gitignore)清楚,磁盘堆积严重 |
| **全仓综合** | **3.5 / 5** | **尚未不可救药,但已明显「改出来的屎山」;不是乱写,是架构边界跟不上迭代速度** |
**结论**:领域复杂度真实存在(CAN FD 协议、WiFi/EtherCAT 双链路、PDO 实时约束、pywebview 双进程),transport 抽象与大量设计注释说明有人在治理。真正的屎山信号是:**上帝文件未拆分、连接/总线状态机多处同源重复、服务端替 tools/ecat 扛了大量进程治理、杂物与文档漂移**。属于「有纪律的腐化」,还能救,但优先重构必须先做。
### 关键发现(证据)
#### A. 电机前端 —— 上帝文件 + 半截双链路
| 位置 | 问题 | 严重度 |
|------|------|--------|
| `js/motor/main.js` **4647 行(含空行)/ 219KB / 顶层 `function`+`async function` 声明 178 个** | 文件头自承「由 motor.html 内联脚本机械外提,逻辑未改」——只搬了位置,没拆职责 | major |
| 同文件六域同居 | UI 持久化 (~L203–526)、连接+轮询 (~L940–1768)、命令序列 (~L2503–2997)、版本双路径 (~L2998–3284)、OTA+IDB (~L3580–4211)、示波器 IIFE (~L4257–end) | major |
| 链路特判仍散落业务层 | `failThresholdFor` L157、probe 复核 L1291、在线语义 L1340、**版本等待双实现** L3156/L3244、**参数应答双通道** L3353/L3384 | major |
| 两套并行连接状态机 | main.js 自建 failCount/probe/pollGen(不 import `connection.js`);`connection.js` 另有 updateConnectionUi/testConnection | major |
| ECAT 总线 bootstrap 重复 | `device-link.js` `bootstrapEcatBus` L167–231 注释自承与 `transport-ecat.js` `ensureOpen/ensureStarted`「同源」 | major |
| 小重复 | `toast/showModal` main.js L566 自实现,其他页用 `common/toast.js`;`setClsIfChanged`/`setTextIfChanged` 与 connection.js 重复;`fetchDeviceJson` ≈ transport-wifi `getJson` | minor |
| transport 抽象本身 | `transport.js` 契约清晰(L11–26),wifi/ecat 实现分离得当;**这是正确决策,不是债** | (正向) |
#### B. Python 服务端 —— 单文件九职责 + 越界补丁
| 位置 | 问题 | 严重度 |
|------|------|--------|
| `esp32_server.py` **1536 行** | 静态服务、reload 注入、UI-state、ECAT 代理、autostart、ESF1 快发、GUI 桥、app-lease、窗口双进程/Job/单实例 —— 九类职责同居 | major |
| 模块级全局 14+ | `g_ecat_*` / `g_fast_*` / `g_app_*` / `g_win` / `g_job` 等(L136–156 起) | major |
| 同一后端三通道 | HTTP 代理 `_ecat_proxy` + GUI 桥 `_GuiApi.ecat_api` + ESF1 TCP `ecat_send_raw_frames`,路径归一化重复 | major |
| **代理失败后自动 autostart 并重发同一 POST** | L762–775;L1028–1030 注释自己承认「带副作用的请求假超时 = 再写一遍」;仅连接失败/超时异常路径触发,非任意错误码 | **critical**(设计风险) |
| 越界替 tools/ecat 补课 | 端口漂移探测、`--parent-pid`、taskkill 全杀、RealTime 提权、stdout 写到他仓、硬编码 `--substr I210 --mask 0x50`(L638) | major |
| 跨进程单实例不互斥 | GUI 进程与服务子进程**各自**可能 `_kill_all_ecat_native`;`g_mutex` 只在进程内(L592) | major |
| 文档漂移 | README L323 写默认网卡 **I226**,代码/页面/测试默认全是 **I210**(`device-link.js:31`、`motor.html:39`、`esp32_server.py:638`、`test_ecat_autostart.mjs`) | minor(但误导) |
| 冗余小点 | `BASE_DIR` 定义两次(L56 与 L134);motor/lab ui-state 路由近重复;前台窗口逻辑三处复制;端口遍历两份 | minor |
| 正向 | app-lease 接口面干净;锁与原子写有意识;双进程是实测踩坑后的合理架构;注释质量极高 | (正向) |
#### C. 脚本 / lab / _local —— 卫生债最重
| 路径 | 体量 | 判定 |
|------|------|------|
| `scripts/` | **128 个文件 / ~30MB** | 正式测试(README 点名)**仅 12 个**;`_tmp_`/`probe_`/`skip_`/`anal_`/`collect_` 约 **55+** 个属一次性排障残留 |
| `lab/` | **114 文件 / 57.4MB** | 大头是采样 dump(`ecat_passive_*.csv` 各 ~25MB 且疑似双份、`skip_meter/` 61 个 json);tabs 真正用到的只有 `spi_test.html`、`lan9252_pm.html` |
| `_local/` | 27 文件 / 1.6MB | 整目录已 gitignore;截图 + dbg 脚本,一次性 |
| 根目录报告 | `ECAT_*.html/md`、`GD32_OTA_FLOW_COMPARE.html`、`REPORT_*.md` ~130KB+ | 无 tabs/README 页面表引用,应归 `docs/` 或删 |
| `lab/spi_test.html` | **2325 行全内联**(非空 2258) | 单文件复杂度高于 motor.html;README 已标「临时功能」 |
| css/ | 结构合理(base/layout/components/can/motor/responsive) | 跨文件重复选择器约 40 个(`.btn`/`.toast`/`.header-bar`);can.css 内部 11 处重复 |
`.gitignore` 策略本身是清楚的(采样/探针/`_local` 不入库)——问题不在策略,而在**磁盘未清理 + 部分实验产物仍可能被误跟踪 + 根目录报告未归档**。
#### D. 跨层架构观察
```mermaid
flowchart TB
subgraph pages["页面层"]
tabs["tabs.html 外壳"]
motor["pages/motor.html"]
other["index / can / lab"]
end
subgraph motorJS["电机前端(上帝文件)"]
main["main.js 4647行<br/>状态+UI+OTA+序列+波形+轮询"]
tp["transport.js 契约"]
wifi["transport-wifi"]
ecat["transport-ecat"]
end
subgraph commonJS["common"]
dl["device-link ConnectionHub"]
conn["connection.js(OTA页用)"]
toast["toast.js"]
end
subgraph server["esp32_server.py 九职责"]
static["静态+reload"]
proxy["/ecat/api 代理"]
gui["_GuiApi + ESF1"]
lease["app-lease WS"]
win["窗口/双进程/Job"]
end
native["tools/ecat<br/>ecat_native.exe"]
tabs --> motor
motor --> main
main --> tp
tp --> wifi
tp --> ecat
main -. "不 import,自建状态机" .-> conn
main -. "自实现 toast" .-> toast
main --> dl
ecat -->|桥或HTTP| proxy
ecat -->|桌面桥| gui
dl -->|bootstrapEcatBus 与 transport 同源重复| proxy
proxy --> native
gui --> native
lease -->|停循环| proxy
```
**正确且该保留的决策**:
- 双链路 transport 抽象(业务共用、I/O 可换)
- tabs 多 iframe 保活
- app-lease 关窗停总线
- PDO 快发堵死慢发(pdo-fast-send)
- 丰富的实测注释与 compose 规格
**真正的屎山信号**:
1. `main.js` 只做了「机械外提」,六域仍同居
2. 连接状态机 ×2、ECAT bootstrap ×2、DOM diff 助手 ×3、toast ×2
3. `esp32_server.py` 替 ecat 扛进程治理,三通道同一后端,非幂等 POST 可重发
4. 版本/节点参数回执未下沉 transport,业务层链路特判 ~16 处
5. README(395 行 tribal knowledge)与代码默认值漂移(I226 vs I210)
6. scripts/lab 杂物 80%+ 与生产无关
### 重构清单(优先级,本回合不实施)
> 下列为建议 backlog,不构成本 feature 的未完成任务。实施前请单独开 compose feature 或直接授权改码。
#### P0 — 正确性 / 风险(先做)
| ID | 动作 | 验收 |
|----|------|------|
| R1 | `_ecat_proxy` 失败 autostart 后**禁止重发非幂等 POST**(`motor/start`/`flash`/`ota/ecat`/`eeprom/*` 等);仅幂等读路由可重试一次 | 源码无副作用路径二次 POST;回归 `test_ecat_autostart.mjs` + 人工确认 flash 路径 |
| R2 | 统一 ECAT 网卡默认值文档与代码(I210 vs I226 二选一) | README、`device-link.js`、`motor.html`、`esp32_server.py`、相关测试默认一致 |
| R3 | 跨进程对 `ecat_native` 的 kill/autostart 加文件锁或让 native 侧唯一 | GUI 进程与服务子进程不会互相 kill 对方刚拉起的实例 |
#### P1 — 拆上帝文件 / 收敛重复状态机
| ID | 动作 | 验收 |
|----|------|------|
| R4 | `main.js` 拆出 `motor/ota.js`、`motor/seq.js`、`motor/conn-poll.js`(或等价命名);入口只做装配 | main.js total 行数 &lt; 2000(目标);`test_motor_transport` / `browser_check_*` 全绿;引用处 `?v=` 已 bump |
| R5 | 版本等待与节点参数回执**下沉 transport 能力接口**,删除 main.js 内 wifi/ecat 双实现分支 | `rg 'tpId\\s*===\\s*\"ecat\"' js/motor/main.js` 在版本/参数相关函数中无命中 |
| R6 | ECAT `bootstrapEcatBus` 只保留一处(建议 device-link 为唯一 Owner,transport 只调用) | 全仓仅一处 health→open→motor/start 策略代码 |
| R7 | 电机页连接状态机与 common 收敛:要么复用 device-link 健康快照,要么抽 `conn-policy.js`;删除重复 DOM diff / toast 实现 | main.js 不再自实现 toast;failCount/probe 语义单一 |
#### P2 — 服务端边界
| ID | 动作 | 验收 |
|----|------|------|
| R8 | 从 `esp32_server.py` 拆出 `ecat_client.py`(端口探测 + 裸 socket + ESF1)与 `ecat_proc.py`(autostart/kill/parent-pid/RT) | server.py 只保留 HTTP/静态/lease/窗口;`test_esf_pack.py` 仍 PASS |
| R9 | 收敛 GUI 桥与 HTTP 代理的 path/body 归一化为同一函数 | 桥与代理共用路由表,无重复剥前缀逻辑 |
| R10 | 桌面 App 模式下设备状态/命令的 GUI 代理路径评估是否可统一(减少 desktop vs browser 双路径分支) | 文档明确两路径必要差异,或实现单一入口 |
| R11 | 删除/合并小冗余:`BASE_DIR` 双定义、ui-state 双路由参数化、前台焦点 helper 共用 | 源码 diff 可核对;无行为变化 |
#### P3 — 卫生与文档
| ID | 动作 | 验收 |
|----|------|------|
| R12 | 清理 `scripts/`:保留 README/spec/gitignore 白名单引用的测试;`_tmp_`/`skip_`/`anal_`/无引用 `probe_` 移出或删除 | scripts 目录正式脚本 ≤ 25 个(保留清单可一页列完);README 自测命令仍可执行 |
| R13 | 清理 `lab/` 采样 dump(csv/json/skip_meter);保留 tabs 引用的实验页 | lab 总体积 &lt; 5MB;`spi_test` / `lan9252_pm` 仍可从 tabs 打开 |
| R14 | `_local/` 与根目录报告归档:报告进 `docs/` 或删除;`_local` 保持 ignore | 仓库根无散落 REPORT/ECAT_*.html |
| R15 | README 瘦身或拆分:把长篇实测细节沉到 `docs/`,README 只留架构/入口/红线 | README 行数明显下降且默认值与代码一致 |
| R16 | CSS 去重:公共 chrome(`.btn`/`.toast`/`.header-bar`)收到 `components.css`;页面 CSS 只留本域 | 对 `.btn`/`.toast` 的重复定义处数可清点下降 |
### [S3] Out of Scope
- 本回合**不修改任何业务代码**(只产出本评估文档)
- 不深挖 `tools/ecat` 内部实现,只评估本仓耦合面
- 不做性能基准测试(延迟数字引用 README/既有报告)
- 不实施 R1–R16
- 不评估固件仓库(ESP32_COMM / GD32)
## Tasks
- [x] T1: 全仓体积/文件/churn 盘点 — acceptance: 产出 scripts/js/server/lab 量化摘要 (covers: S2)
- [x] T2: 电机前端结构审计 — acceptance: main.js 热点、双链路残留、重复状态机带 file:line (covers: S2)
- [x] T3: esp32_server 职责与耦合审计 — acceptance: 九职责分段、越界项、critical 风险带 file:line (covers: S2)
- [x] T4: scripts/lab/_local 卫生审计 — acceptance: 分类数量、可删清单、必须保留清单 (covers: S2)
- [x] T5: 汇总评估文档 + 优先级重构清单 R1–R16 — acceptance: 本文档可独立评审 (covers: S2)
- [x] T6: 独立评审 — acceptance: 评审者对照证据复核 critical/major 发现与清单可行性;pass 无 critical (covers: S2; depends: T1–T5)