注释与文档

共 20 题
📑 题目列表 20 题
#
★★★

1. "注释撒谎"问题中注释与代码不同步时如何通过 CI 检测(markdownlint、Vale、textlint 校验文档与链接,扫描失效引用与过时示例)?

注释与代码不同步("注释撒谎")是常见问题。如何通过 CI 检测与文档链接校验来发现注释过时、失效引用与过时示例?

  • 注释撒谎的成因与危害
  • CI 中 markdownlint、Vale、textlint 等文档校验工具
  • 失效链接、过时示例的自动扫描

(1)注释撒谎的成因:注释描述旧行为、引用已删除的 API、示例代码已不编译。读者若信注释会用错,不信则失去注释价值。核心是"注释与代码不同步"。 (2)CI 文档校验工具链:a) markdownlint 校验 Markdown 语法与风格(标题层级、代码块);b) Vale(或 textlint)做散文风格与拼写检查;c) textlint 校验中文/日文等语法的规则。这些作为 lint 步骤接入 CI。 (3)失效引用扫描:用 markdown-link-checklychee 扫描文档中的外链与内链;用文档目录(如 Nav)校验站内引用;对 API 文档,用 tsdoc/javadoc 的 lint 检查 @link 引用的符号是否真实存在。 (4)过时示例检测:把文档中的代码示例纳入编译/单测(doctest),CI 中跑一遍确保示例可运行;示例与真实 API 签名用"示例即测试"(如 Javadoc 的 snippet 标签、Rustdoc doctest)绑定,防止示例与 API 漂移。 (5)定期兜底:对注释做"离线检测"——用复杂度分析(如注释与代码行的比例、注释引用的符号不存在时告警)在 CI 中拦截明显的撒谎注释。

注释撒谎的根因是"文档与代码双份维护"。CI 工具链(markdownlint/Vale/textlint 校验格式、link-check 校验引用、doctest 校验示例)把"注释与代码一致"从人治变成机器校验,让过时注释在 PR 阶段即暴露。

# lint 配置(示例)
- uses: markdownlint-cli2
- run: vale .
- run: textlint --rules textlint-rule-ja-no-mixed-period docs/
- run: lychee --offline docs/assets/  # 链接失效扫描
#
★★★

2. Javadoc / GoDoc / docstring 中"@throws"与"@param"的契约化使用中当异常类型在多个子类型之间动态分发时文档如何既不撒谎又不冗余

Javadoc/GoDoc/docstring 中 @throws 与 @param 是契约化标签。当异常在多个子类型间动态分发时,文档如何既不撒谎又不冗余?

  • @throws/@param 的契约语义
  • 异常多态分发下的文档准确性
  • 避免"撒谎"(过度承诺)与"冗余"(重复实现)

(1)契约语义:@param 描述参数约束(非空、范围、格式),@throws 描述方法可能抛出的异常及触发条件。它们是"调用方契约",调用方据此处理。 (2)问题:若方法内部按运行时类型动态抛不同子类异常(如根据 ErrorCodeBusinessException 的不同子类),文档若逐个列子类会冗余且易漏;若只写超类又太泛,调用方无法精确处理。 (3)不撒谎又不冗余的做法:a) 文档站在"抽象契约"层面——@throws BusinessException 注明"按 cause/code 区分,参见错误码表",把具体子类分发交给错误码文档而非 Javadoc;b) 用 @throws 描述"何时抛"(触发条件)而非"抛什么类型",避免把实现的分发细节写死;c) 对稳定且重要的子类单独列,其余归入超类。 (4)关键:文档承诺"契约"(触发条件、可处理码),不承诺"实现细节"(每个子类)。这样实现里抛什么子类都不影响文档准确性,也不会因新增子类而被迫改文档。

动态分发的异常最难做文档。正确取向是"文档描述触发条件与可处理契约,类型细节交给错误码文档",既避免逐个子类列出的冗余,又避免只写超类的谎言。契约与实现解耦。

/**
 * 提交订单。
 * @param order 待提交订单,非空
 * @throws BusinessException 当订单校验失败时抛出,具体原因见 code 字段
 *                          (错误码表:ORD-1001 库存不足、ORD-1002 余额不足)
 */
public void submit(Order order) { ... }
#
★★★

3. KDoc / DocC(Swift) / TSDoc / Rustdoc 的交叉链接与代码示例可执行化(runnable doctest)的 CI 集成策略

KDoc/DocC/TSDoc/Rustdoc 如何支持交叉链接与可执行示例(doctest)?CI 如何集成确保示例可运行?

  • 各语言文档工具的交叉链接(@link、doc links)
  • 可执行示例(doctest)的机制
  • CI 集成策略防止示例漂移

(1)交叉链接:Kotlin 的 @see/@link、TSDoc 的 @link 标签、Swift DocC 的 - ``Symbol`` 链接、Rustdoc 的 [text](crate::module::Item) 内链。它们让文档形成可导航的 API 图谱,读者可跳转。 (2)可执行示例(doctest):Rustdoc 的 rust ``` 代码块默认作为 doctest 在 cargo test 运行;Kotlin 的 Dokka 示例可通过 gradle 检查;TSDoc 示例需配合 `tsd`/`ts-node` 或自定义 runner 执行;Swift DocC 的 `@Snippet` 引用真实源码片段。 (3)CI 集成:a) 把"文档示例编译并运行"作为独立测试任务(如 `cargo test --doc`、`gradle dokka` + 示例执行),失败则 CI 红;b) 用 `@Snippet`/`snippet` 指向真实源码,避免示例与实现双份维护;c) 交叉链接在构建文档时校验(断链即报错),如 Rustdoc 的 `--broken-intra-doc-links` 警告。 (4)策略:示例优先引用真实代码片段(single source of truth),其次是独立可运行示例但纳入 CI 编译;交叉链接用构建期校验断链。这样示例会随代码演进自动失效并暴露。

交叉链接与可执行示例是"文档即代码"的落地。关键在于"示例可运行"(接入 CI 编译执行)与"示例引用真实源码"(snippet 避免双份维护),交叉链接则用构建期断链校验兜底。

/// 计算两数之和。
///
/// ```
/// let sum = add(1, 2);
/// assert_eq!(sum, 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
// CI:cargo test --doc 会编译并运行该示例
#
★★★

4. OpenAPI / GraphQL 描述与代码注解(@Schema(description=...))双向同步工具链

OpenAPI/GraphQL 描述与代码注解(如 @Schema(description=...))如何双向同步,避免描述与代码漂移?

  • code-first 与 design-first 的同步
  • 代码注解作为描述源(@Schema、@SchemaField)
  • 从注解生成文档、从文档生成代码的双向工具链

(1)双向同步的核心是"单一事实来源"。code-first 下注解是源,设计文档/描述由代码生成;design-first 下契约文件是源,代码由契约生成。不能两边都手写。 (2)code-first:用 @Schema(description=...)@SchemaField 注解在代码中标注意义,用 springdoc/swagger-annotations 生成 OpenAPI;GraphQL 用 @description 指令或 schema-first 的 .graphql 文件。代码与描述同源,天然同步。 (3)design-first:.yaml/.graphql 契约是源,用 openapi-generatorgraphql-codegen 生成客户端/服务端脚手架与注解,确保契约与代码一致。 (4)双向校验工具:CI 中 diff 检查"生成的 OpenAPI 与仓库中维护的 OpenAPI 是否一致"(如 swagger-diffopenapi-diff);对 GraphQL 用 schema registry(如 Federation)快照校验。文档或代码任一变更导致不一致,CI 即报错。

双向同步的要点是"要么 code-first 要么 design-first,确立单一事实来源",并用 CI 的 diff 校验防止双向漂移。注解(@Schema)是 code-first 的载体,契约文件是 design-first 的载体,二者不可并存两套手写。

@Schema(description = "订单资源")
public class OrderDto {
    @Schema(description = "订单状态", example = "PENDING")
    private OrderStatus orderStatus;
}
// springdoc 自动生成 OpenAPI,description 与代码同源
#
★★★

5. TODO/FIXME 等临时标记注释的生命周期治理中如何用门禁、超期提醒与 issue 关联避免"临时注释"沉淀为永久技术债?

TODO/FIXME 注释常被沉淀为永久技术债。如何用门禁、超期提醒与 issue 关联治理其生命周期?

  • TODO/FIXME 的生命周期管理
  • 门禁(数量/期限)、超期提醒、issue 关联
  • 避免临时注释沉淀为技术债

(1)问题:TODO/FIXME 注释若无人跟进,会永久沉淀。治理目标是把"临时备忘"纳入可跟踪的工作流。 (2)issue 关联:TODO 注释必须关联 issue/工单(如 // TODO(PRJ-1234): ...),让注释成为 issue 的指针而非独立备忘。无关联的 TODO 视为坏味道。 (3)门禁与统计:CI 中统计 TODO/FIXME 数量,设置阈值(如不允许新增无 issue 的 TODO);用 todo-tree/git-todo 扫描并生成报告,超龄 TODO 触发告警。 (4)超期提醒:给每个 TODO 标注日期或 deadline,定期脚本(如 cron 扫描)找出超期未清的 TODO 并推送提醒到 owner;或设置"新 TODO 必须设置到期时间"。 (5)清理机制:定期回溯 TODO 清单,完成的移除、过期的提升为真 issue 或删除。让"临时注释"有明确归宿,不沉淀。

TODO 治理的本质是"让临时注释可追踪、有期限、有主人"。issue 关联解决"谁来管",门禁与超期提醒解决"何时管",定期清理解决"是否该留着"。三者配合避免技术债无声累积。

// TODO 关联 issue + 期限
// TODO(PRJ-1234): 遗留 validation,需在 2026-09 前替换为 schema 校验
final class LegacyValidator { ... }
#
★★★

6. 公共 API 的文档覆盖率门禁中如何统计缺失 Javadoc/TSDoc 的公开符号并纳入 CI,防止文档欠账无声累积?

公共 API 的文档覆盖率如何统计并纳入 CI 门禁,防止文档欠账累积?

  • 文档覆盖率统计(公开符号的 Javadoc/TSDoc 缺失)
  • 覆盖率门禁纳入 CI
  • 防止欠账无声累积

(1)统计对象:公共 API 的公开符号(public 类/方法/字段、导出的 TS 符号)是否有文档注释。用工具扫描(如 japicmp/javadoc-Xdoclint、TSDoc 的 eslint 规则 require-jsdoctypedoc 的报告)统计缺失比例。 (2)覆盖率门禁:设定基线(如新公开符号 100% 有文档),CI 中对比 PR 新增的公开符号是否都有文档,缺失则告警或阻断。防止"文档欠账"随新代码累积。 (3)纳入 CI:a) 用 lint 规则在 PR 阶段拦截未文档化的公开符号;b) 定期生成覆盖率报告(如 tsc --declaration + typedoc 输出),阈值下降即失败;c) 对存量欠账用"豁免清单 + 增量收敛"。 (4)关键:门禁针对"新增"而非"存量",避免一次全量补文档阻塞开发;存量欠账用清单跟踪逐批收敛。

文档覆盖率门禁的核心是"阻止新增欠账"。工具统计公开符号缺失文档,CI 在 PR 阶段拦截新增未文档化符号,存量用豁免清单渐进收敛。这样欠账不会随代码增长而无声扩大。

// eslint 配置(TSDoc 部分)
{
  "@typescript-eslint/require-jsdoc": ["error", {
    "publicOnly": true,
    "require": { "MethodDefinition": true, "ClassDeclaration": true }
  }]
}
#
★★

7. 架构决策记录(ADR)与代码注释的边界中哪些"为什么"应放在 ADR、哪些应留在行内注释;变更时如何避免漂移

架构决策记录(ADR)与代码注释的边界如何划分?哪些"为什么"放 ADR、哪些放行内注释?变更时如何避免漂移?

  • ADR 与行内注释的职责边界
  • "为什么"的层级(架构级 vs 局部级)
  • 变更时避免漂移

(1)ADR 适用于:影响面大、跨模块、有取舍的架构决策(选型、分层、数据模型),记录"决策背景、备选项、结论、理由"。这类"为什么"关乎全局,放 ADR 供回顾与评审。 (2)行内注释适用于:局部的、实现级的不明显原因(为什么这样写、为什么不用另一种写法),简短解释"这里为什么"即可,贴近代码。 (3)判定标准:若理由影响"整个架构方向"→ ADR;若只解释"某一行/某段代码的局部原因"→ 行内注释。ADR 引用的行内注释可用 // 见 ADR-0012 链接,避免重复。 (4)避免漂移:a) ADR 记录"决策时的理由",代码注释记录"当前实现因果",二者用链接关联;b) 变更决策时更新 ADR 并同步相关注释;c) 在 CI 中检测"ADR 引用的符号/链接是否仍存在"。

边界是"全局决策 vs 局部实现"。ADR 承载架构级"为什么"(供决策回顾),行内注释承载实现级"为什么"(贴近代码读者)。用链接关联避免双份维护,变更时以 ADR 为源同步注释。

// 行内注释:局部实现原因
// 使用缓存而非实时 DB,避免热路径高延迟(决策见 ADR-005)
Money total = cache.get(key).orElseGet(() -> computeTotal());
#
★★

8. 注释中包含敏感信息(内部地址、密钥提示、员工姓名)的发现与自动清除工具(gitleaks、talisman、trufflehog)

注释中可能残留敏感信息(内部地址、密钥提示、员工姓名)。如何用 gitleaks、talisman、trufflehog 等工具发现并自动清除?

  • 注释/代码中的敏感信息泄露风险
  • 密钥扫描工具(gitleaks、talisman、trufflehog)
  • 自动清除与预防

(1)风险:注释中的内部地址、密钥端口、员工姓名一旦提交(尤其公开仓库),会泄露信息。比代码更隐蔽,因为注释不显眼。 (2)工具:a) gitleaks 用正则/规则扫描并检测密钥(regex + entropy 检测);b) talisman(pre-commit 钩子)在提交前拦截疑似密钥;c) trufflehog 用深度扫描(secret 检测)在全仓历史中找泄露。三者可组合。 (3)落地:a) 在 pre-commit 钩子接入 talisman/gitleaks,阻止含密钥的提交;b) CI 中跑 gitleaks 全仓扫描,发现即失败;c) 对历史泄露用 trufflehog 扫描 + 重写历史(git filter-repo)清除。 (4)预防:a) 规范禁止在注释中写敏感信息;b) 用环境变量/配置中心存放密钥,注释只写"见配置";c) 定期的密钥轮换与泄露扫描。

敏感信息清查靠"插桩 + 扫描 + 重写"。talisman/gitleaks 在提交与 CI 阶段拦截新泄露,trufflehog 排查历史,git filter-repo 清除历史。同时用规范预防(密钥不放注释,走配置中心)。

# pre-commit 钩子
- repo: https://github.com/trufflesecurity/trufflehog
  rev: v3.0.0
  hooks: [ { id: trufflehog } ]
# CI
- run: gitleaks detect --source . --report-format json --report-path gitleaks.json
#
★★

9. 示例代码(example、sample)随版本漂移的问题中如何在 CI 中运行/编译所有文档中的代码示例(doctest、mdbook、Swagger Editor)

文档中的示例代码随版本漂移。如何在 CI 中运行/编译所有文档中的代码示例(doctest、mdbook、Swagger Editor)?

  • 示例代码漂移的成因
  • 各类文档示例的编译/运行机制
  • CI 兜底防止漂移

(1)漂移:示例代码引用的 API 已改名/删除,示例不能运行但仍在文档中,误导读者。根因是示例与实现双份维护。 (2)按文档类型接入 CI:a) Rustdoc/markdown 的 doctest——cargo test --doc 编译运行;b) mdbook——用 mdbook test 执行示例;c) Javadoc/TSDoc——用 javadoc 的 snippet 或 TSDoc 配合 ts-node 执行;d) OpenAPI——用 Swagger Editor/swagger-cli 校验示例的合法性。 (3)策略:a) 示例纳入 CI 编译运行,任一示例失败即 CI 红;b) 优先用"示例引用真实源码"(snippet),避免手写示例与实现分离;c) API 示例用契约校验(OpenAPI lint)确保示例与 schema 一致。 (4)兜底:定期全量跑文档示例,并统计"文档中示例的 API 引用是否仍存在"(dead-link 校验)。

示例漂移的根治是"示例可执行 + 纳入 CI"。doctest/mdbook test 编译运行示例,Swagger Editor 校验 API 示例,snippet 引用真实源码避免双份维护。任何示例与实现漂移都会在 CI 暴露。

# CI 中运行文档示例
cargo test --doc                       # Rust 文档示例
mdbook test                            # mdbook 示例
swagger-cli validate openapi.yaml      # OpenAPI 示例校验
#
★★

10. CHANGELOG 维护(Keep a Changelog)与语义化发布(SemVer)的联动中 Added/Changed/Deprecated/Removed/Security 分类如何映射到 minor/major

Keep a Changelog 的 Added/Changed/Deprecated/Removed/Security 分类如何与 SemVer 的 minor/major 联动?如何维护?

  • Keep a Changelog 的分类结构
  • SemVer 版本号变化(major/minor/patch)
  • 分类与版本级联的映射

(1)Keep a Changelog 分类:Added(新增)、Changed(变更)、Deprecated(弃用)、Removed(移除)、Fixed(修复)、Security(安全)。它按"变更类型"组织,读者可快速定位。 (2)SemVer 映射:a) Removed(移除功能)→ major(不兼容);b) Deprecated(弃用)→ 在 major 前提示,通常随下次 major 才会 Removed;c) Changed(行为变更)→ 若破坏兼容则 major,否则 minor;d) Added(向后兼容新增)→ minor;e) Fixed(bug 修复)→ patch;f) Security(安全修复)→ 视严重性通常 patch 或紧急 minor。 (3)联动:发布时先看 CHANGELOG 的变更分类,决定版本号跳动;反过来,版本决策也要求 CHANGELOG 记录对应分类。CI 可校验"出现 Removed 未 bump major"等规则。 (4)维护:用 standard-version/release-please 从 Conventional Commits 自动生成分类,人工润色补充"为什么"。

CHANGELOG 与 SemVer 是"变更记录"与"版本语义"的联动。分类决定版本号(重大变更→major,兼容新增→minor,修复→patch),版本号也约束 CHANGELOG 的完整性。二者用工具自动生成 + 人工润色。

## [2.0.0] - 2026-08-01

### Removed
- 移除已弃用的 `/v1/orders` 接口(major)
### Added
- 新增订单详情字段(minor)
#
★★

11. README 的必备要素(快速开始、架构图、贡献指南、许可证)及其在 CI 中的可读性检查(死链检测、代码示例可运行)

README 的必备要素有哪些?在 CI 中如何做可读性检查(死链检测、示例可运行)?

  • README 必备要素(快速开始、架构、贡献指南、许可证)
  • CI 中的死链检测与示例可运行性检查
  • README 的可读性维护

(1)必备要素:a) 项目简介与用途;b) 快速开始(Quick Start,安装/运行步骤);c) 架构图/结构说明;d) 贡献指南(Contributing);e) 许可证(License);f) 常用命令、配置、FAQ。这些让新人能快速上手并参与。 (2)CI 死链检测:用 markdown-link-checklychee 扫描 README 中的外链与内链,失效即失败;用 markdownlint 保证结构规范。 (3)示例可运行性:把 README 中的命令/代码示例纳入 CI 执行(如 make verify-docs 跑 Quick Start 命令),确保示例不会因版本漂移而失效。 (4)可读性检查:用 Vale/textlint 做语言与拼写检查;检查 README 的标题结构、必要章节是否齐全(可写脚本校验"必备 section 是否存在")。

README 是项目第一印象,必备要素保证信息完整;CI 用死链检测、示例执行、结构校验保证其"可读且不撒谎"。README 是活文档,需通过 CI 持续维护。

# CI
- run: lychee README.md docs/          # 死链
- run: markdownlint README.md
- run: make quickstart-smoke            # 运行快速开始示例
#
★★

12. CHANGELOG 与 Conventional Commits 的自动生成(standard-version、release-please)与人工润色的边界

CHANGELOG 如何用 Conventional Commits 自动生成(standard-version、release-please)?人工润色的边界在哪?

  • Conventional Commits 约定(feat/fix/breaking)
  • standard-version、release-please 自动生成 CHANGELOG
  • 自动生成与人工润色的分工

(1)Conventional Commits:feat:(新功能)、fix:(修复)、feat!:/BREAKING CHANGE:(破坏性变更)等类型,让提交信息可被工具解析。 (2)自动生成:standard-versionrelease-please 扫描 Conventional Commits,自动归类到 Keep a Changelog 的 Added/Changed/Fixed 等分类,并自动 bump SemVer 版本、打 tag、生成 CHANGELOG。 (3)人工润色边界:自动生成提供"条目骨架",但"为什么要这样改"的上下文、跨多个 commit 的聚合说明、Breaking Change 的迁移指引,需要人工补充。工具负责"有无、归类、版本",人负责"为什么、影响、迁移"。 (4)实践:a) 强制 Conventional Commits(lint 提交信息);b) 自动生成后人工润色 CHANGELOG 的摘要与迁移说明;c) 保持"生成 + 润色"分离,避免人手写全部导致遗漏。

自动生成解决"记录完整、归类一致、版本正确",人工负责"补充 why 与迁移指引"。二者结合既保证 CHANGELOG 完整又保证可读。工具不替代人的判断,只是底座。

# Commit 规范
git commit -m "feat(orders): add discount computation"
# 自动生成
npx release-please release-pr --repo-url=... --token=...
#
★★

13. 注释的黄金法则中解释"为什么"而非"是什么",避免过时注释?

注释的黄金法则是解释"为什么"而非"是什么"。为什么?如何避免过时注释?

  • 注释解释"为什么"而非"是什么"
  • 代码本身可读时无需注释"是什么"
  • 避免过时注释

(1)黄金法则:注释应解释"为什么"(权衡、约束、坑),而非"是什么"(代码已自明)。"是什么"用命名/结构表达,"为什么"是命名/结构无法表达的上下文。 (2)反例:// 加 1 让索引从 1 开始 这种"是什么"注释是噪音;// 使用 1-based 索引以匹配外部报表约定 才是"为什么"。 (3)避免过时注释:a) 只写"为什么"(代码变更时"为什么"更稳定,但也会过时);b) 注释与代码同 PR 提交,不脱离修改;c) CI 中检测注释引用的符号/行为是否仍存在;d) 难以维持的注释宁可删除。 (4)判断标准:注释是否补充了代码无法自明且重要的信息?若无,删除;若"为什么"会随代码变化,及时更新。

"为什么 vs 是什么"是注释的黄金法则。代码用命名与结构自明"是什么",注释补足"为什么"的上下文。只写"为什么"可减少过时(因为"为什么"比"是什么"更稳定),但要用 CI 与及时更新兜底。

// 劣:解释"是什么"
int i = idx + 1; // 加 1
// 优:解释"为什么"
int i = idx + 1; // 使用 1-based 索引以匹配外部报表约定
#
★★

14. 文档与代码同步中文档即代码(docs-as-code)的实践?

"文档即代码"(docs-as-code)的实践有哪些?如何做文档与代码同步?

  • docs-as-code 的含义与分支
  • 文档与代码同源、同版本、同评审
  • 同步机制

(1)docs-as-code:把文档当作代码一样管理——用 Git 版本控制、走代码评审、集成 CI、可自动生成。文档与代码同生命周期。 (2)实践:a) 文档与代码同仓库、同分支、同 PR 评审;b) 文档由代码生成(code-first:javadoc/OpenAPI 从注解生成),确立单一事实来源;c) 文档纳入 CI(lint、死链、示例执行);d) 用 markdown/AsciiDoc 而非二进制格式,便于 diff 与评审。 (3)同步机制:a) 生成类文档(API 文档、类型文档)由代码驱动,天然同步;b) 叙述类文档(README、指南)用"链接 + 引用真实代码"避免双份;c) CI 校验生成的文档与代码一致(diff 检查)。 (4)价值:文档随代码演进而更新,避免"文档滞后于代码"的经典问题,降低维护成本。

docs-as-code 的核心是"文档与代码同源、同流程、同 CI"。生成类文档由代码驱动,叙述类文档引用真实代码并纳入 CI,让文档与代码同步而非双份维护。

# 文档即代码:CI 中校验文档与代码一致
- run: mvn javadoc:javadoc   # 从注解生成文档
- run: git diff --exit-code docs/  # 生成结果与提交的文档一致则通过
#
★★

15. 多语言团队的注释语言选择中注释用英文还是母语,对跨国协作、拼写检查与工具链的影响如何统一?

多语言团队注释用英文还是母语?对跨国协作、拼写检查与工具链的影响如何统一?

  • 注释语言选择(英文 vs 母语)
  • 对跨国协作、拼写检查、工具链的影响
  • 统一策略

(1)语言选择:跨国协作团队通常统一用英文,因为它是通用语言,任何成员都能读;纯本地团队可考虑母语,但会限制未来国际化与工具链。 (2)影响:英文注释可被 Vale/ESLint 的拼写检查、lint 工具与 AI 工具识别,且与开源生态一致;母语注释(尤其 CJK)较难做拼写检查,且跨时区协作时非母语者阅读困难。 (3)统一策略:a) 规范明确"注释与标识符用英文";b) 用 CI 的 Vale/textlint 强制英文拼写与语法;c) 若团队强烈偏好母语,至少保证标识符用英文、术语表维护双语,避免混合语言增加认知负担。 (4)关键:无论选哪种,必须"全仓统一",避免注释英中混杂、标识符乱入,导致工具链与读者都困惑。

注释语言选择要"统一 + 可工具化"。跨国协作推荐英文(工具链、拼写检查、生态兼容),本地团队可母语但须统一,标识符始终英文。统一是前提,工具强制是保障。

# Vale 配置:强制英文拼写
- run: vale --config=... .
#

16. "代码即文档"理念下何时仍需要写注释;常见的过度注释反例

"代码即文档"理念下,何时仍需要写注释?常见的过度注释反例有哪些?

  • 代码即文档的适用范围
  • 何时仍需注释(为什么、边界、非显然逻辑)
  • 过度注释反例

(1)代码即文档:多数"是什么"可由命名、结构、类型自明,无需注释。但并非所有信息都能由代码表达。 (2)仍需注释的场景:a) 解释"为什么"(权衡、约束、历史原因);b) 非显然的边界与约定(如 1-based 索引、并发安全假设);c) 外部依赖/业务约束(为什么必须这样调);d) 公共 API 的契约(@param/@throws)。 (3)过度注释反例:a) 注释重复代码(// 加 1);b) 给每个方法写"做什么"的冗余注释;c) 注释体比代码还长;d) 被注释掉的代码(应删除而非注释);e) 无信息量的 // init 式注释。 (4)判断:注释是否补充了代码无法自明且重要的信息?若无则删。好注释是"why"与"exceptional",不是"what"。

代码即文档的边界是"what 由代码表达,why 由注释表达"。仍需注释的是"为什么、边界、非显然逻辑",过度注释是重复代码、冗余 what、被注释掉的代码。评审时用"是否补充自明之外信息"判断。

// 过度注释反例
public void init() { // init
    this.ready = true; // 设置 ready
}
// 合理注释
// 用 1-based 索引以匹配外部报表约定(非显然边界)
int i = idx + 1;
#

17. 方法级注释的 4 段式结构(@param、@return、@throws、@since)的一致性检查

方法级注释常采用 4 段式结构(@param、@return、@throws、@since)。如何做一致性检查?

  • 4 段式结构(@param/@return/@throws/@since)
  • 注释与签名的一致性检查
  • 工具校验

(1)4 段式:@param 描述参数约束、@return 描述返回值、@throws 描述可能异常、@since 描述引入版本。它们构成公共 API 的契约信息。 (2)一致性检查:a) 每个参数都有 @param,且 @param 不遗漏、不虚构参数;b) 有返回值的都有 @return;c) @throws 与签名抛出的异常一致;d) @since 与版本规范一致。 (3)工具校验:javadoc-XdoclintcheckstyleJavadocMethod 规则、TSDoc 的 eslint 规则自动检查"参数/返回/异常是否与签名一致";缺失或多余即告警。 (4)价值:一致性检查防止"注释与签名漂移"(注释撒谎),让公共 API 契约可信。

4 段式注释是公共 API 的契约模板。一致性检查确保 @param/@return/@throws 与签名一一对应,用 javadoc/doclint/checkstyle 自动校验,防止注释与签名漂移。

/**
 * 根据 ID 查询用户。
 * @param id 用户 ID,非空
 * @return 用户信息,不存在时返回 Optional.empty()
 * @throws IllegalArgumentException 当 id 为空时
 * @since 1.2.0
 */
public Optional<User> findUser(Long id) { ... }
#

18. 注释的坏味道中冗余注释、被注释掉的代码与误导性注释?

注释的坏味道有哪些:冗余注释、被注释掉的代码、误导性注释?如何避免?

  • 冗余注释(重复代码)
  • 被注释掉的代码(死代码)
  • 误导性注释(撒谎)

(1)冗余注释:注释复述代码(// 加 1),无信息量,增加噪音。应删除,让代码自明。 (2)被注释掉的代码:注释掉的旧实现是死代码,读者无法分辨"是否还有用",且会诱导误用。应删除(用 Git 历史找回),而非注释保留。 (3)误导性注释:注释描述的行为与代码不符(撒谎),比无注释更危险。应删除或及时更正。 (4)避免:a) 评审中把"冗余注释、被注释代码、误导注释"列为可读性坏味道;b) 用 lint 检查注释密度与"被注释掉的代码"模式;c) 写注释前自问"是否补充自明之外的信息"。

三种坏味道的风险不同:冗余是噪音,被注释代码是死代码,误导是撒谎。根治靠"删除冗余、删除死代码、保证注释准确",评审与 lint 配合让这三类不进代码库。

// 冗余注释
int total = a + b; // a + b
// 被注释掉的代码
// if (old) { return oldValue; }
// 误导性注释
// 返回最近订单(实际返回最后一条)
return orders.get(orders.size() - 1);
#

19. API 文档的规范中 Javadoc/TSDoc 的标签与示例?

API 文档的规范:Javadoc/TSDoc 的标签与示例应如何组织?

  • Javadoc/TSDoc 的标签体系
  • 文档示例的组织
  • 公共 API 文档规范

(1)Javadoc 标签:@param@return@throws@since@deprecated@see@link@author@version。TSDoc 类似:@param@returns@throws@deprecated@example@link@default。 (2)规范:a) 公共符号必须有文档并满足覆盖率要求;b) 每个公共方法/参数/返回值/异常都有对应标签;c) 弃用用 @deprecated 并注明替代方案;d) 复杂 API 用 @example/@example 提供可运行示例。 (3)示例组织:@example 放调用示例,示例应可运行并纳入 CI(snippet/doctest);示例与真实 API 签名一致。 (4)一致性:用 javadoc 的 doclint、TSDoc 的 eslint 规则 + 覆盖率门禁强制标签完整、签名一致。

API 文档规范 = 标签体系 + 示例 + 覆盖率与一致性校验。Javadoc/TSDoc 的标签表达契约(参数/返回/异常/弃用),示例提供用法,工具保证标签完整、示例可运行、与签名一致。

/**
 * 计算订单折扣。
 * @param order 订单对象
 * @param coupon 优惠券(可选)
 * @returns 折扣后的金额
 * @throws Error 当订单金额非法时
 * @example
 * const total = calcDiscount(order, coupon);
 */
export function calcDiscount(order: Order, coupon?: Coupon): number { ... }
#

20. commit message 与行内注释的信息分工中二者不能互相替代,各自的职责边界是什么?

commit message 与行内注释信息分工为何不能互相替代?各自的职责边界是什么?

  • commit message 与行内注释的职责差异
  • 时间维度(变更历史 vs 当前状态)
  • 边界划分

(1)职责差异:commit message 记录"某次变更为什么发生"(时间维度,历史),行内注释解释"当前代码为什么这样"(空间维度,现状)。二者面向不同读者与场景。 (2)不可替代:commit message 描述"变更增量与动机",但 git blame 需要翻历史;行内注释描述"当前实现的状态与约束",但无法记录"为什么从旧方案迁来"。若只留其一,读者无法同时获得"现状 why"与"变更 why"。 (3)边界:a) commit message 负责"这次提交改了什么、为什么改、影响什么"(变更上下文);b) 行内注释负责"当前这行/这段为何如此、有什么约束"(现状上下文);c) 跨多个 commit 的演进理由→ADR;d) 在 commit message 中引用 issue 与 ADR,行内注释链接关键决策。 (4)实践:commit message 用 Conventional Commits 结构化;行内注释只写"现状 why";重命名/行为变更同时更新 commit message 描述与相关行内注释。

commit message 是"历史"(变更 why),行内注释是"现状"(状态 why),二者解决不同问题,不能互相替代。历史决策用 commit message/ADR,现状约束用行内注释,配合 git blame 可追溯。

# commit message:变更历史
feat(orders): cache discount result to avoid repeated computation
# 行内注释:现状约束
// 缓存折扣结果,避免每请求重复计算(决策见 ADR-007)