243 lines
13 KiB
Markdown
243 lines
13 KiB
Markdown
# 工程规范(Engineering Norms)
|
||
|
||
> 本文件是**语言无关**的通用工程规范:适用于本仓库中任何语言的源代码、脚本、前端资源与文档。
|
||
> 它不描述任何具体项目的事实。项目目标、技术选型、安全不变量、禁止事项、测试验收清单、
|
||
> 稳定 ID 注册表在 [`docs/charter.md`](docs/charter.md)。
|
||
>
|
||
> 两者冲突时:**项目事实与安全不变量以 `docs/charter.md` 为准,质量规则以本文件为准。**
|
||
|
||
> **版本基线** 填写本仓库的基线 commit:<`<代码行号锚点基线>`(例如 `b70d188`)|`<文档章节引用基线>`(例如 `e0882c7`)>,由持有门禁结果的提交者在合并该批改动时更新。
|
||
|
||
> 校验命令(`GATE-DOCS` / `GATE-P0`)的具体可复制版本写在 [`docs/charter.md`](docs/charter.md) §5。
|
||
|
||
---
|
||
|
||
## 1. 优先级与冲突仲裁
|
||
|
||
规则从高到低,下位不得覆盖上位:
|
||
|
||
1. 可复核的代码事实
|
||
2. `docs/charter.md` 的项目事实与安全不变量
|
||
3. 本文件的质量规则
|
||
4. 文档表述
|
||
5. 任何 AI 输出的推测
|
||
|
||
- **文档与代码冲突时以代码为准**,并在同一改动内修正文档。
|
||
- **两份文档互相矛盾时**,以权威来源声明更明确的一方为准;若均无声明,以更靠近可执行事实
|
||
(代码、测试、校验器)的一方为准。
|
||
- **未验证不得断言。** 任何声称「已经实现 / 已经修好 / 门禁已通过」的句子,必须来自本轮亲自执行的
|
||
命令输出或代码阅读。禁止把历史结论、推测、"应该是"写成事实。
|
||
- 无法核实的部分必须明确写「未验证」,并说明缺什么(凭据、环境、依赖版本、外部服务)。
|
||
|
||
---
|
||
|
||
## 2. 代码编写规范
|
||
|
||
### 2.1 文件规模
|
||
|
||
- 单文件规模控制在 **500 行**以内(软上限)。
|
||
- 接近 400 行必须评估拆分。确需超过 500 行时,文件头部必须有一行以 `SPLIT-EVAL:` 开头的注释,
|
||
写明拆分评估结论与当前不拆的理由(例:内聚性优先——A 与 B 共享 C,拆分会引入 N 处重复)。
|
||
- **未带 `SPLIT-EVAL:` 标记的超限文件视为待办项。** 超限不自动合规。
|
||
- 不得为凑合规而机械拆分,也不得为压低行数把逻辑塞进不可读的长函数。
|
||
|
||
### 2.2 注释与代码一致
|
||
|
||
- 注释描述**行为**,不描述意图、计划或愿望。
|
||
- 禁止在注释里写「未来会改为 X」「计划支持 Y」这类未实现的东西。
|
||
- 占位实现必须显式标注为占位,并说明「真实实现需要什么环境 / 凭据」。
|
||
- **注释里出现的标识符必须真实存在。** 函数名、类型名、键名、环境变量名、文件路径,
|
||
每个都要能被检索到定义。检索零命中即为**幻影标识符**,属阻塞缺陷——它比一个 bug 更坏,
|
||
因为它会诱导人去做不存在的实现,或误以为某个保护已经存在。
|
||
|
||
### 2.3 禁止死代码与全局抑制
|
||
|
||
- **不得引入死代码**:定义但无任何调用点的函数、常量、导出、字段,一律删除。
|
||
- **禁止全局抑制警告**(整文件 / 整模块级别的 warning allow)。
|
||
- 确需保留未使用符号时,用**局部、点名、带理由**的方式标注,写清保留原因与预期何时删除。
|
||
理由写不出来的,删掉。
|
||
|
||
### 2.4 依赖诚实
|
||
|
||
- **不得在文档、注释或代码中引用没有实际引入的依赖、模块或 API。**
|
||
- 引入新依赖前写清它解决的唯一问题;已有实现能解决的不引入。
|
||
- 依赖版本以**锁文件**为准。描述某依赖的能力时,以锁文件里的实际版本 API 为准,
|
||
不得照抄设计文档或凭记忆。
|
||
- 文档写「已引入 / 未引入」时必须逐字对照依赖清单文件。
|
||
|
||
### 2.5 引用必须真实存在
|
||
|
||
- 文档、注释、表格中的**文件路径**必须真实存在。表格里的模块路径不要靠 grep 推断——
|
||
逐个用文件系统存在性检查验证。
|
||
- **行号锚点必须指向对应内容**,不能只指向「文件里那一行」。文档写「某文件第 120 行是
|
||
重试上限」,第 120 行就必须是重试上限;行号漂移而描述跟着跑,比行号写错更危险。
|
||
- **校验器只能证明「该行号存在且非空」,不能证明该行内容符合描述。** 内容级核对必须人工读。
|
||
|
||
### 2.6 分层与依赖方向
|
||
|
||
- 领域逻辑不得依赖任何传输层 / 渠道层 / SDK 类型;渠道 SDK 只能出现在渠道适配层。
|
||
- 依赖只能指向更下层,不得反向引用。
|
||
- 领域层对外暴露纯函数,使业务规则可以被不依赖任何外部服务的测试直接调用。
|
||
|
||
---
|
||
|
||
## 3. 配置与密钥规范
|
||
|
||
### 3.1 配置分层
|
||
|
||
- **进程配置**(连接地址、监听端口、密钥)只来自环境变量;必填项缺失时快速失败。
|
||
- **业务配置**(用户可调的行为参数)只存在于持久化状态存储中,由受保护的管理接口写入。
|
||
- 两者不得混用;**不得为了让本地开发方便而让业务配置静默回退到环境变量。**
|
||
- **不得引入通用配置框架。** 配置解析用最小必要代码,手写解析可以接受。
|
||
- 配置热加载必须显式(由受保护接口触发),进程不得轮询或监听配置文件。
|
||
|
||
### 3.2 密钥
|
||
|
||
- **不得在代码、文档示例、日志或错误响应中回显密钥、令牌或密码。** 示例一律用占位符。
|
||
- 凭据只在内存中使用:不写本地文件、不进入提交记录、不出现在 URL 查询串。
|
||
- 比较凭据必须用**常量时间比较**,避免时序侧信道。
|
||
- **信任根单一。** 管理凭据、会话凭据、外部服务令牌各自独立,不得互相推导;
|
||
不得用一个凭据(如存储连接密码)兼任另一个角色(如用户界面认证)——
|
||
无密码的部署形态会让被兼任的角色永久不可用。
|
||
|
||
### 3.3 环境构建规范
|
||
|
||
- 构建与测试必须在**最小发行版**中执行,不得在功能完整的环境里通过门禁。
|
||
- 门禁命令必须在文档中给出**完整可复制**版本(含用户、挂载、环境变量、工作目录)。
|
||
- 构建产物必须是静态链接、非 root、只读文件系统友好。
|
||
- 依赖缓存目录不得进入版本控制,也不得进入构建镜像。
|
||
- **门禁范围不得缩小。** 不得为了让门禁变绿而删除测试、跳过校验、放宽检查级别或缩小检查范围。
|
||
|
||
---
|
||
|
||
## 4. 生产运行约束
|
||
|
||
- **禁止数据库**:不得引入关系型或文档型数据库。
|
||
- **禁止本地文件写入**:一切状态只存在于外部托管的状态存储。
|
||
- **禁止本地文件状态**:不得用本地文件持久化业务状态;进程重启必须可以完全无损。
|
||
- **禁止非 stdout 日志**:日志只写 stdout(错误信息可写 stderr),不得写日志文件。
|
||
- **禁止长连接**:不得引入 WebSocket / SSE / 长轮询;所有通信走短请求-响应。
|
||
- **禁止有状态恢复流程**:实例崩溃后不得依赖本地或内存状态「续做」未完成工作;
|
||
恢复必须来自外部状态存储中的可重放记录。
|
||
- **fail-closed 是默认姿态**:依赖不可用、配置缺失、校验失败时返回可诊断的失败,
|
||
不得降级成「看起来成功」。
|
||
- 错误响应结构全仓统一(统一 envelope + 可关联的请求 ID);可重试的失败必须告知重试时机。
|
||
|
||
---
|
||
|
||
## 5. 安全与鉴权规范
|
||
|
||
### 5.1 默认拒绝
|
||
|
||
- 所有外部入口默认拒绝,白名单放行。
|
||
- 白名单必须穷举,禁止通配(无 `*`、无前缀通配)。
|
||
|
||
### 5.2 鉴权分离
|
||
|
||
- **不同调用方不得共享同一认证凭据。** 用户界面会话、外部服务推送、机器对机器任务、
|
||
负载均衡转发必须各自持有独立凭据。
|
||
- 会话必须是随机的、有短 TTL 的、可撤销的。
|
||
- **凭据摘要可以落库,凭据本体不得落库。**
|
||
|
||
### 5.3 诊断面
|
||
|
||
- 诊断 / 联调面必须默认**绝对关闭**,且关闭态必须做到「路由不存在」(落到通用 404),
|
||
而不是「路由存在但拒绝」。
|
||
- 开启必须要求**两个独立因子同时满足**(一个启动期显式开关 + 一个密钥环境变量)。
|
||
- 诊断面不得暴露在任何公共网关 / CDN 白名单内,只能直连后端 origin 访问。
|
||
- 诊断面必须是只读或纯函数式探测:不得写入外部系统状态,不得修改配置。
|
||
- 若含测试性写操作,必须要求显式目标标识;**空目标必须视为拒绝而非放行**——
|
||
「空白名单 = 放行一切」是必须显式校验的坑。
|
||
|
||
---
|
||
|
||
## 6. 测试与门禁规范
|
||
|
||
### 6.1 变更验证闭环
|
||
|
||
任何涉及运行时行为或可核对事实的改动,必须**同一改动内**完成:实现 → 校验 → 修文档。
|
||
|
||
- 改动不得只存在于代码、也不得只存在于文档;不得留到「以后补」。
|
||
- 改代码 → 改测试 → 改文档,三者一起提交。
|
||
|
||
### 6.2 测试要求
|
||
|
||
- 每个行为变更都要有测试覆盖;纯重构不得改变测试总数。
|
||
- **每条硬性安全不变量都要有一条测试或断言能证明它成立**,而不是只写在文档里。
|
||
- 门禁必须全部通过才算交付:格式化检查、依赖锁校验、静态检查(warning 当错误)、测试。
|
||
- **禁止**删除测试、跳过测试、放宽检查级别、删除访问控制来通过门禁。
|
||
- **不得提交未验证的代码。** 门禁结果必须来自本轮实际执行,不得沿用历史结论。
|
||
|
||
### 6.3 测试与实现同路径
|
||
|
||
- 探针、测试、健康检查等「验证某依赖可用」的代码,**必须与真实调用走同一条路径**
|
||
(同样的认证、同样的 URL 归一化、同样的协议细节)。
|
||
- 简化版验证代码会误报不可用,或更糟——误报可用。误报不可用会让入口层停发流量,
|
||
后果是全站不可用。
|
||
|
||
### 6.4 外部依赖测试
|
||
|
||
- 无外部服务凭据时,测试用 mock 或本地回环服务。
|
||
- 真实环境集成测试必须显式标注为可忽略,且缺凭据时**静默跳过**(不泄露凭据、不判失败)。
|
||
- **不得声称已通过真实环境验证,除非本轮真的连过。**
|
||
|
||
### 6.5 跨层改动
|
||
|
||
- 改公共 API / 状态存储键与 TTL / 配置项 / 错误码 / 用户可见文案时,
|
||
**文档、后端、前端或网关侧必须同轮同步**。
|
||
- 网关白名单改动必须同步路由文档与白名单测试。
|
||
- **文案是产品行为的一部分。** 面向用户的提示若与后端实际行为不符,就是一个 bug——
|
||
填错的用户无法自助排障。
|
||
|
||
---
|
||
|
||
## 7. 文档治理规范
|
||
|
||
### 7.1 文档分工与权威
|
||
|
||
- 每个文档只回答一个问题;**不得跨文档复制表格、清单或数值。**
|
||
- 必须明确声明哪个文档是哪类事实的**唯一权威来源**。
|
||
- 其他文档引用事实时给稳定锚点(文件 + 稳定 ID),不复制数值本身。
|
||
|
||
### 7.2 同轮同步
|
||
|
||
- 改公共 API、状态存储键名与 TTL、配置项、错误码、用户可见文案时,**同轮**更新相关文档。
|
||
- 中英双语文档必须信息对等(覆盖相同的事实集,不要求逐句对应)。
|
||
|
||
### 7.3 稳定 ID 与引用
|
||
|
||
- 项目用**稳定 ID** 做跨文档引用;注册表属于项目文档,不属于通用规范。
|
||
- **跨文档禁止用 `§x.y` 章节号做引用**(章节号随编辑漂移);用稳定 ID。
|
||
- **同文档内**允许 `§x.y` 引用。
|
||
- **新增稳定 ID 必须先登记到注册表。** 未登记 ID 不得出现在注释、文档或标识符中。
|
||
|
||
### 7.4 自动化校验
|
||
|
||
- 文档必须经过仓库内的文档校验器校验,通过才算完成。
|
||
- **校验器只能证明「可达」**:行号存在、引用可解析、表格列数一致。
|
||
**它不能证明语义正确。**
|
||
- 报告校验结果时必须同时说明这个区分,不得把「0 error」说成「内容已验证」。
|
||
- **新增文档必须加入校验器的文档清单**,否则它永远不会被校验——这是静默失效。
|
||
- **文档不得出现不存在的文件路径**——`audit_paths` 逐个检查反引号内的路径型 token 是否真实存在,
|
||
`docs/retired.md` 同样在检查范围内。曾被讨论但从未落地的路径只以文字描述其形态,不逐字写出。
|
||
已删除但 git 历史可证的文件(如 `docs/todo.md`)可以引用;无法证明存在过的不能。
|
||
|
||
---
|
||
|
||
## 8. 变更流程
|
||
|
||
1. 确认 `docs/charter.md` 的安全不变量未被违反。
|
||
2. 实现 + 测试。
|
||
3. 跑门禁,全部通过。
|
||
4. 跑文档校验器,全部通过。
|
||
5. 同轮同步文档(代码 ↔ 测试 ↔ 文档 ↔ 前端 / 网关)。
|
||
6. 提交。
|
||
|
||
**不得**:
|
||
|
||
- 为了过门禁而删测试、跳校验、放宽级别、删访问控制、缩小检查范围。
|
||
- 把未验证的东西写进文档(「已通过」「已实现」「已验证」一律需本轮证据)。
|
||
- 在文档或注释里引用不存在的依赖、函数、类型、键、路径或行号。
|
||
- 把「计划做」写成「已完成」;把「推测」写成「结论」。
|
||
- 只改代码不改文档,或只改文档不改代码。
|