--- name: coding-standards description: 检测代码异味、反模式和可读性问题。在实现功能、评审代码或重构时使用。 --- # 通用编码标准 ## 技术反模式(危险信号模式) 检测到以下任一模式时,暂停实现并记录:触发的模式、受影响的当前需求、最小的合规替代方案,以及恢复所需的验证。当替代方案消除该模式或有文档记录的需求证明保留该模式合理时,方可恢复。 ### 代码质量反模式 1. **相似代码编写 3 次或以上** —— 违反三次法则(Rule of Three) 2. **单个文件中混杂多种职责** —— 违反单一职责原则(SRP) 3. **在多个文件中定义相同内容** —— 违反 DRY 原则 4. **未检查依赖关系就进行修改** —— 存在意外影响的可能性 5. **用注释禁用代码** —— 应使用版本控制 6. **错误抑制** —— 隐藏问题会形成技术债 7. **用类型断言替代保证** —— 声明了既无运行时检查也无既有契约作为依据的类型 ### 设计反模式 - **“暂时能用就行”的思维** —— 技术债的累积 - **补丁式实现** —— 对现有代码进行无计划的追加 - **对不确定技术的乐观实现** —— 假设未知要素“大概能行”就进行设计 - **对症式修复** —— 不解决根本原因的表面修复 - **无计划的大规模变更** —— 缺乏渐进式方法 ## 基本原则 持续排查,直到依据能够确定在保持系统正确性和可维护性的前提下,以最低总复杂度交付所需的用户、运维或维护者价值的方案。 - **基于依据的重构范围** —— 只重构阻碍当前结果、被当前任务变更、或未通过适用质量检查的代码;使用保持行为不变的小步骤。对其他发现,连同其所属边界和依据一并报告,而不扩大当前变更范围 - **仅限当前需求的代码** —— 只有当当前需求、已验证的约束、或有依据支持的实质性风险要求时,才引入新的代码路径、能力、基础设施、抽象或推测性的边缘情况处理(YAGNI) - **设计收敛** —— 以最小的设计增量交付当前所需的结果。在引入持久状态、公共或跨边界契约、行为模式、可复用抽象或组件拆分之前,先记录现有能力已经交付了什么、它们在当前结果上未能交付什么,以及为什么该新增是能弥合这一差距的最小方案 对每个被激活的层面综合评估总复杂度:用户决策、设置、模式、概念、输出、持久状态和实现路径,以及它们各自在 UX、运行时、实现、测试、文档和维护方面的成本。只比较各可行方案之间存在差异的维度。当能以更低的总复杂度交付相同的已确认价值和验证结果时,优先选择复用或不引入新机制。 ## 注释编写规则 - **代码优先**:命名、类型和结构是主要的表达媒介;只有在注释能传达代码无法表达的信息时才添加注释。犹豫不决时,改进命名而不是加注释 - **注释“为什么”,而非“是什么”**:解释推理过程、权衡取舍、约束/边缘情况,或公共 API 契约 - **内容不过时**:注释包含当前的推理、约束、边缘情况或 API 契约;开发历史由版本控制保留 - **长期有效**:只写在任何阅读时刻都依然有效的内容 - **简洁性**:将说明控制在必要的最低限度 ## 错误处理基础 ### 快速失败原则 在出错时快速失败,防止在无效状态下继续处理。传播该失败,或返回带有原始诊断上下文的显式类型化错误。 关于详细实现方法(Result 类型、自定义错误类、分层错误处理等),请参考特定语言和框架的规则。 ## 三次法则 —— 代码重复的判断标准 根据 Martin Fowler《重构》一书处理重复代码的方式: | 重复次数 | 处理方式 | 理由 | |-------------------|--------|--------| | 第 1 次 | 内联实现 | 无法预测未来的变化 | | 第 2 次 | 考虑未来的整合 | 模式开始出现 | | 第 3 次 | 提取公共实现 | 模式已确立 | ### 提取公共实现的判断标准 **适合提取公共实现的情况** - 业务逻辑重复 - 复杂的处理算法 - 很可能需要批量修改的部分 - 校验规则 **应保持分离的情况** - 偶然一致(碰巧代码相同) - 有可能朝不同方向演化 - 提取公共实现会显著降低可读性 - 测试代码中的简单辅助函数 ## 变更边界与参考代表性 提示中给出的路径是调查的起点。当有依据表明仓库中的其他文件实现了被接受的结果、是必需的依赖或调用路径、或必须变更以维持受本次工作影响的契约时,将其纳入范围。调用方、使用方、测试、配置和数据流是有用的依据,而非必须逐项核查的清单。 在采用某种模式、API 或依赖时,检查具有相同职责和当前契约的相关功能及仓库中的其他使用之处。在该职责范围内优先选择兼容的实现。出现频率有助于定位候选方案,但并不能使某个模式因此具有权威性;当多种方案并存时,通过其调用方、生命周期和兼容性来区分当前模式与遗留或无关的模式。 从清单文件、锁文件和兼容的使用方中解析外部依赖版本。仅当这些来源无法解决影响兼容性或架构的选择时才上报处理。 ## 常见失败模式及规避方法 ### 模式 1:错误修复连锁反应 **症状**:修复一个错误导致产生新的错误 **原因**:未理解根本原因就进行表面修复 **规避方法**:修复前用五个为什么(5 Whys)找出根本原因 ### 模式 2:绕过类型保证 **症状**:用 `any` 或 `as` 声明了没有任何检查或契约作为依据的类型 **原因**:想要规避类型错误的冲动 **规避方法**:应用“类型安全基础”中关于依据的判断标准。 ### 模式 3:测试不充分的实现 **症状**:实现后出现大量 bug **原因**:忽视 Red-Green-Refactor 流程 **规避方法**:以能够展示所需结果的失败测试开始行为变更 ### 模式 4:忽视技术不确定性 **症状**:引入新技术时频繁出现意外错误 **原因**:未事先调查,假设“照官方文档应该能行” **规避方法**: - 在任务文件开头记录确定性评估 - 当仓库依据、与版本匹配的一手资料,或可运行的本地检查都无法确认与结果相关的行为时,将确定性视为低;在实现前先创建能解决该行为问题的最小验证 ### 模式 5:对现有代码调查不足 **症状**:重复实现、架构不一致、集成失败、采用过时模式 **原因**:实现前对现有代码理解不足;仅参考附近文件而未核实其代表性 **规避方法**: - 实现前,使用领域、职责和配置模式相关的关键词搜索类似功能 - 发现类似功能 -> 当该实现满足当前契约时,复用或扩展它 - 类似功能属于技术债 -> 当它阻碍当前结果、由当前变更引起、或位于已确认范围内时予以修复;否则单独报告。当修复需要架构决策时创建 ADR - 不存在类似功能 -> 按照现有设计理念实现新功能 - 将每个决策及其理由记录在当前工作流为其指定的产物中 - **参考代表性核查**:参见上文“变更边界与参考代表性”一节 ## 调试技巧 ### 五个为什么 —— 根本原因分析 将每个回答追溯到已观察到的依据,直至找到一个修正后能防止原始故障的原因。记录每个问题、依据以及最终的因果链;当下一个回答将只是推测时停止,并指出还需要哪些依据。 ## 类型安全基础 **类型安全原则**:类型收窄应以运行时检查或既有契约为依据。类型守卫保证的类型应与实际检查内容一致。 - 对结构尚未确定的输入使用 `unknown`,并验证使用方需要的属性。 - 使用泛型、联合类型或交叉类型表达类型关系及变体。 - 将基于已验证的 SDK 或框架契约的类型断言放在对应边界。当静态分析无法表达该契约时,将抑制限定于相关规则,并说明契约依据和断言的适用范围。 **类型复杂度管理** - 字段数量:最多 20 个(超过则按职责拆分,外部 API 类型除外) - 可选字段比例:最多 30%(超过则将必填/可选分离) - 嵌套深度:最多 3 层(超过则扁平化) - **外部 API 类型**:放宽约束,按实际情况定义(在内部适当转换) ## 重构技巧 **基本方针** - 小步前进:每次保持行为不变的重构后,确保最相关的适用测试和静态检查仍然通过 - 安全变更:一次只改变一个重构职责,并在进行下一个职责之前验证其可观测行为 - 行为保证:确保现有行为在过程中保持不变 **实现流程**:理解现状 -> 渐进式修改 -> 行为验证 -> 最终确认 **优先级**:删除重复代码 > 拆分大函数 > 简化复杂条件分支 > 提升类型安全 ## 实现完整性保证 ### 影响追踪 开始实现前,追踪所变更代码的调用方、依赖以及数据流(生成 -> 修改 -> 引用),直到再多一个文件也无法改变“变更边界与参考代表性”所界定的变更边界为止。将实现或其验证所依赖的直接与间接影响带入后续工作。 ### 未使用代码的删除规则 检测到未使用的代码时,在任务完成前根据当前需求和可达的调用路径判断它是否被使用。 - 是 -> 将其接入该调用路径并验证需求 - 否 -> 删除它;版本控制会保留之前的实现 对象:代码、文档、配置文件 ## Red-Green-Refactor 流程(测试先行开发) **推荐原则**:以因预期原因而失败的测试开始行为变更 **开发步骤**: 1. **Red**:为预期行为编写测试(测试失败) 2. **Green**:以最小实现使测试通过 3. **Refactor**:在保持测试通过的同时改进代码 **可直接验证的情况**: - 纯配置文件变更(.env、config 等) - 仅文档更新(README、注释等) - 生产环境紧急事故响应(事后必须补充测试) ## 测试设计原则 ### 测试用例结构 - 测试由“Arrange(准备)”“Act(执行)”“Assert(断言)”三个阶段组成 - 测试名称应说明触发条件和可观测结果 - 一个测试用例只验证一种行为 ### 测试数据管理 - 在专用目录中管理测试数据 - 定义测试专用的环境变量值 - 对测试中的凭据、令牌、个人数据和支付数据,使用合成的、非敏感的值 - 保持测试数据最小化,只使用与测试用例验证目的直接相关的数据 ### Mock 与 Stub 使用策略 **推荐:在单元测试中对外部依赖进行 mock** - 优点:确保测试的独立性和可复现性 - 实践:对数据库、API、文件系统等外部依赖进行 mock **单元测试边界**:对外部连接使用确定性的替代品;在为该契约选定的集成测试或 E2E 测试中,实际调用真实的外部边界 ### 测试失败应对的判断标准 **修正测试**:预期值错误、引用了不存在的功能、依赖于实现细节、仅为测试而存在的实现 **修正实现**:合理的规格、业务逻辑、重要的边缘情况 **两种解读在现有需求下都说得通**:返回未解决的行为决策 —— 说明两种候选行为、能够裁定哪一种正确的来源,以及在不做选择之前应停止的条件 ## 测试粒度原则 ### 核心原则:只验证可观测行为 **通过可观测边界进行测试**:公共 API、返回值、异常、外部调用和持久化状态。只能通过这些可观测边界间接触及私有方法、内部状态和算法细节。 ## 安全原则 ### 安全默认值 - 通过环境变量或专用的密钥管理器存储凭据和密钥 - 对所有数据库访问使用参数化查询(预处理语句) - 使用语言或框架提供的成熟加密库 - 使用密码学安全的随机数生成器生成安全关键值(令牌、ID、nonce) - 使用标准协议对静态和传输中的敏感数据进行加密 ### 输入与输出边界 - 在系统入口处校验所有外部输入的预期格式、类型和长度 - 根据渲染上下文(HTML、SQL、shell、URL)对输出进行适当编码 - 错误响应中只返回调用方所需的信息;详细诊断信息记录在服务器端日志中 ### 访问控制 - 对所有处理用户数据或触发状态变更的入口点应用身份验证 - 对每次资源访问都进行授权校验,而不仅仅在入口处 - 只授予操作所需的最小权限(文件、数据库连接、API 作用域) ### 知识截止日期补充(2026-03) - OWASP Top 10:2025 已从关注症状转向关注根本原因;新增了“软件供应链失效”(A03)和“异常情况处理不当”(A10) - 最新研究表明,AI 生成的代码在访问控制方面存在缺陷的比例较高 —— 应将身份验证和授权列为高优先级评审对象 - OpenSSF 发布了《面向 AI 代码助手指令的安全导向指南》—— 建议使用针对特定语言的可执行约束,而非泛泛而谈的建议 - 详细的检测模式请参见 `references/security-checks.md`