Files
STM32F4-Base/docs/门禁说明/coding-standard.md
T
wandering1 d64bbdc57f feat(wave): 故障录波数据面、接线与单通道回放页(软件闭环交付)
数据面(App/algo,纯逻辑,双端编译)
- wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask
  唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃
  兜底——下一次真故障无波可录比丢一段更贵
- wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带
  锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度
- 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式
- 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样
  纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉
- test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条
  自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合
- 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定)

接线(任务层)
- adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图),
  段头按最低有效位精确记账,不经浮点回算
- adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实;
  台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行
- defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到
  关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms
  补一条告警并记历史峰值(喂狗余量可核)
- Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留
  超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的,
  半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进
- Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到
  上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数;
  段文件字节布局零改动,三份合成段哈希逐字节不变
- protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体
  零改动

人机(160×160 单色屏)
- 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器
  得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30
- 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与
  垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、
  单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据
- ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、
  手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口
- 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有
  60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形
- 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变
- 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏
  22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、
  仪器用已在跑的单轮工作耗时探针,不新加埋点

台账与文档
- 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936
  字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名;
  散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建
  重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置)
- 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据);
  §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径
  风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」
- 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项
- 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划
  索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动)
- 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件
  形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记
  「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场
- 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行
  227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退

为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与
体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账
同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道

编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串
生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
2026-10-29 19:23:15 +08:00

129 lines
9.7 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.
# `coding-standard` — 编码规范 V1.0 的可机械化子集
> 一句话:它只查规范里**能用形状判定的 20 条**(命名、宏、注释块、括号、禁用函数、goto、
> 递归、可变参数……)。规范里"名字要达意、函数要短、职责要清"这些它一条都管不了。
> 规范正文 = `docs/代码规范/嵌入式C语言代码规范_V1.0.md`。**第 20 条(禁 C 库堆分配)出处不在规范正文**
> —— 规范 §6.1 的示例自己就写着 `malloc`/`free`(通用 C 模板遗留,已同日在示例下加批注),
> 它的出处是平台约束:MicroLIB 的 C 堆只有 512 B。
- 配置:`entry: python scripts/check_coding_standard.py`、`pass_filenames: true`、
排除 `test/`、CubeMX 生成的 `Src/`、`Inc/`、第三方与 `Lib/`。
## 1. 它在查什么(逐条对应规范条款号)
| 规则号 | 内容 | 条款 |
|---|---|---|
| CODE-RULE-01 | `.h` 必须有 `XXX_H` 形式的保护宏,禁止 `__` 前缀 | 2.3.1 |
| CODE-RULE-02 | 文件开头必须有文件头注释(含 模块名称 / 作者) | 5.2 |
| CODE-RULE-03 | `#define` 宏名全大写 + 下划线 + 数字 | 3.4.1 |
| CODE-RULE-04 | `typedef` 类型名以 `_t` 结尾 | 3.5.1 |
| CODE-RULE-05 | `switch` 必须有 `default` | 6.3.2 |
| CODE-RULE-06 | 禁用不安全函数 `strcpy/strcat/sprintf/gets/scanf/strtok` | 7.5 |
| CODE-RULE-07 | 禁止 `goto` | 6.3.3 |
| CODE-RULE-08 | 禁止 Tab 缩进(4 空格) | 4.1.1 |
| CODE-RULE-09 | 函数定义必须有函数注释(含「函数功能」) | 5.3 |
| CODE-RULE-10 | 函数名全小写(`HAL_` 厂商回调名豁免) | 3.3.1 |
| CODE-RULE-11 | 禁止相对/绝对路径 include | 2.3.5 |
| CODE-RULE-12 | 禁止可变参数函数 | 7.6 |
| CODE-RULE-13 | 禁止递归函数 | 7.7 |
| CODE-RULE-15 | 文件作用域指针变量必须初始化为 `NULL` | 6.1.4 |
| CODE-RULE-16 | 文件作用域非 static 全局变量必须 `g_` 前缀 | 3.2.2 |
| CODE-RULE-17 | 非 static 函数必须在配对 `.h` 声明(无配对头则跳过) | 2.2 |
| CODE-RULE-18 | 禁止 Yoda 条件(字面量必须在运算符右侧) | 6.3.1 |
| CODE-RULE-19 | 函数定义左大括号与右括号同行 | 4.4.2 |
| CODE-RULE-20 | **禁止 C 库堆分配** `malloc/calloc/realloc/strdup/strndup/free/aligned_alloc` | 平台约束(**非规范 V1.0 条款**) |
规则号里 **CODE-RULE-14 是缺的**:条款作废后编号不回收,留着是为了让历史文档里的"14"不指向别的东西。
## 2. 为什么要它
- 这 19 条如果靠人看,评审成本极高且必然漏;机械化之后评审可以把注意力留给机器管不了的部分。
- 有硬约束背景的两条特别值:**禁止递归**与**禁止可变参数**——本仓 `DBG_*` 每个调用点内联约
90 B Flash + 216 B 栈,栈与 Flash 都是红线资源;`g_` 前缀和指针初始化则是最容易埋雷的静态状态。
- **CODE-RULE-20 有真事垫背**(2026-10-23 落地):MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的
`Heap_Size EQU 0x200` = **512 B**,而它的踩坑方式是**模拟器全对、真机必炸**:配置引擎 2026-09-18
那次堆分配纠偏(4 处 KB 级 `malloc` 落在 C 堆上、真机返 NULL,见
`docs/子系统设计/配置文件设计说明.md` v1.11 记录)与 lftpd 的 `malloc` 隐患(`docs/内存管理/RAM使用分析与优化方案.md` §4.2.1)都是
**上线后才发现**的那类。宿主照不出来的缺陷,靠评审等于没管。
## 3. 触发面 / 看不见什么
- 只看**改动的 C/H 文件**(`pass_filenames: true`);`test/`、`Src/`、`Inc/`(CubeMX 生成)、
第三方、`Lib/` 不查。
- **后果**:同一份规范对 `test/board/`(编进固件的 6300+ 行)不生效,对 CubeMX 生成区也不生效。
`test/` 的静态检查由 `test-cppcheck` 兜一半,形状规范没人管。
- 逐文件判定:跨文件的事实(声明在 A、定义在 B、调用在 C)它只看到眼前这一个文件。
- CODE-RULE-20 跑在**掩码后**的文本上(注释与字符串里写 `malloc` 不报);换来的代价是:
把分配藏进**宏展开**里它也看不见。
- CODE-RULE-20 有一处**点名豁免**(`simulator/sim/cfg_file_store_sim.c` 的 `malloc`/`free`):
那是平台分配后端的**宿主侧实现**(MCU 侧同名后端用 `pvPortMalloc`),不是漏网。
## 4. 报错怎么读
```
Drivers/BSP/X/x.c:42 [CODE-RULE-09] 函数 foo_bar 缺少函数注释(应含「函数功能」)
```
- 单独跑:`& '.venv\Scripts\python.exe' scripts/check_coding_standard.py <文件...>`。
- 报的是**形状**,通常一眼能改;改不动的(例如厂商回调必须叫那个名字)走脚本里的显式豁免名单,
别在源码里塞空格绕开正则。
## 5. 好处
- 形状统一,diff 干净;新人不必记住整份规范也能过门禁。
- 规则号可追溯:报错直接给出"规则号 + 条款号",能查到出处。
## 6. 限制与盲区
1. **"有注释"≠"注释对"**:`return-doc` 类判据(见 `bsp-err`)与本门的 CODE-RULE-09 都只查
关键词在不在,写了"函数功能:见调用方"照样通过。
2. **豁免靠形状**:`HAL_` 前缀直接放过函数名规则——任何以 `HAL_` 开头的项目内函数也一并被放过。
3. CODE-RULE-17 在**没有配对 `.h`** 时跳过:新写的 `.c` 忘了建头,就不会被这条抓到
(双端注册由 `registration-check` 管,声明完整性没人管)。
4. 命名"达意"、函数长度、圈复杂度、职责边界:完全没有机械检查。
5. `test/` 与生成区整目录不查(§3),这三类目录的规范只靠人。
6. 逐文件 + 正则形状:把违规写进字符串字面量、注释、宏展开里就看不见;反过来说,宏里真正的
违规代码也可能看不见。
7. CODE-RULE-20 **大小写敏感**:这是特性也是盲区——`pvPortMalloc`/`vPortFree` 因此不误报,
但写成 `Malloc(` 就绕过了(同形后缀也一样:`buf_free(` 因词边界不成立而不算违规)。
8. CODE-RULE-20 **只管堆分配函数名**:`alloca` 与变长数组(VLA)是**栈**上的坑,不归它;
分配不释放(泄漏)、释放后继续用(UAF)也不归它——本条只判"别碰 C 堆"这一件事。
9. **CODE-RULE-09 的回看窗口只有 25 行**:脚本 `scripts/check_coding_standard.py:194` 取的是函数定义行
往前 25 行(`window = lines[idx-26:idx-1]`),在里面找「函数功能」关键词。于是**函数头注释块一旦写
到 25 行以上,关键词就被挤出窗口**——注释本身完全合规,也照样报「函数缺少函数注释块」。2026-10-29
一次真实触发(lead 复跑抓到 6 处、已改 3 个生产文件,全部是**纯注释重排、无一行逻辑改动**):录波
落盘道与 UI 道上四个带长口径注释的函数(`wave_store_drain`、`s_log_record_edge`、`App/task/ui_task.c`
的录波按键 helper、`simulator/main.c` 的合成喂拍 helper)各撞一到两处。**写法上的规避**:把长口径
另起一块放在函数注释块**之前**(两块分开),或把细节搬进函数体内——别让「函数功能」那一行离定义行超过 24 行。
10. **不带文件参数就是空跑**:脚本 `:321` 是逐 `sys.argv[1:]` 处理的,**不给文件参数时一个字节都不查、
直接返回 0**。手工复现这条门禁时把那个 RC=0 读成「过了」是错的——必须显式列出要查的路径(pre-commit
里由框架喂 staged 文件,所以钩子本身不会空跑)。同族坑见 `docs/代码规范/pre-commit使用说明.md` §2。
补录(判据细节,迁移自原手册):
- 报错形态:`L123 [CODE-RULE-03 3.4.1] 宏名须全大写+下划线: net_htonl`(规则号与规范条款号一起给)。
- CODE-RULE-11 细化过的判定形态(2026-10-11):禁的是**用路径找文件**——引号 include 带斜杠、
任意形态带 `..` 或反斜杠、绝对路径或盘符。
- **`Drivers/BSP/GD5F2GQ5UE/nand_ftl.*` 也在排除清单里**(dhara/FatFs 接口适配层,函数名与形态由
第三方接口决定,不该按我们的命名规范判它)。
- 明确列为"不在检查范围"的语义类条款:函数长度与单一职责、参数数量、错误处理方式、被注释掉的代码。
## 7. 豁免与基线
- **没有基线**:硬拦截,新写的代码必须合规;因此**放宽任何一条都会立刻降低约束**,需谨慎。
- 豁免方式只有两种:脚本里的显式豁免名单(按名字形状,如 `HAL_` 回调)、或改代码。
- CODE-RULE-20 的豁免是脚本里的 `HEAP_ALLOC_ALLOWLIST`,按**文件路径 + 具体函数名**双条件放行,
现仅一条:`simulator/sim/cfg_file_store_sim.c` → `malloc`/`free`(平台分配后端的宿主侧实现,
设计依据见 `docs/子系统设计/配置文件设计说明.md` 的 MCU 端/宿主端两段式后端)。
⚠ 刻意**不做目录级豁免**:下次谁要 `malloc` 就得往这里加一行,评审看得见。
## 8. 改这个门禁本身
- 新增规则前先在**全库跑一次**统计违规数;有存量的规则要么先清存量,要么走"基线只减不增"
的模式(参考 `bsp-err`、`layer-deps`),不要直接挂上硬拦截卡住所有人。
- 规则作废**不回收编号**(见 CODE-RULE-14)。
- 新增规则前先全库普查违规数(本节上一条已写)。CODE-RULE-20 落地时的普查结果:钩子范围内
C/H 共 **151 个文件**,命中只有 `cfg_file_store_sim.c` 那两行(即 §7 那条豁免)→ 直接**硬拦截、
零基线**;`pre-commit run coding-standard --all-files` 实测 Passed。自证:临时样例里
`malloc/calloc/free` 各报一处,`pvPortMalloc/vPortFree/buf_alloc(`、注释、字符串 全部不误报。