motoko-doc-strings
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotoko doc strings
Motoko文档字符串
Purpose
目的
Add triple-slash () doc comments to every public declaration in Motoko
source files so that renders meaningful documentation pages. This
skill captures the conventions used in (caffeinelabs/motoko-core)
and the lessons learned applying them across the library.
///mo-docmo:coremotoko-bitcoin在Motoko源文件中的每个公共声明上添加三斜杠()文档注释,以便渲染出有意义的文档页面。本指南涵盖了(caffeinelabs/motoko-core)中使用的约定,以及在库应用这些约定时总结的经验。
///mo-docmo:coremotoko-bitcoinWhen to Use
适用场景
- The user asks to "add doc strings", "document the public API", or "generate docs" for a Motoko package.
- Reviewing or polishing a library before release / publication on MOPS.
- After adding new public symbols and noticing missing entries in the
generated .
docs/
- 用户要求为Motoko包"添加文档字符串"、"记录公共API"或"生成文档"时。
- 在发布/在MOPS上发布库之前进行审阅或优化时。
- 添加新的公共符号后,发现生成的目录中缺少对应条目时。
docs/
What Counts as a "Public Object"
什么是"公共对象"
Document every declaration that ends up in the rendered docs. In a typical
file these are:
module { ... }public type ...public let ...public func ...public class ...- The module itself (or
module { ... })module Name { ... } - For a , every
public class,public let, andpublic varmember inside the class body.public func - For a nested , recurse and apply the same rules.
public module
Private declarations (, , without ) and helper
types should NOT receive comments — they don't appear in
output and the noise hurts readability.
funcletclasspublic///mo-doc为所有会出现在渲染后的文档中的声明添加文档。在典型的文件中,这些声明包括:
module { ... }public type ...public let ...public func ...public class ...- 模块本身(或
module { ... })module Name { ... } - 对于,类体内部的每个
public class、public let和public var成员。public func - 对于嵌套的,递归应用相同规则。
public module
私有声明(不带的、、)和辅助类型不应添加注释——它们不会出现在的输出中,多余的注释会影响可读性。
publicfuncletclass///mo-docComprehensiveness by Location
文档详细程度规范
How thorough a doc string needs to be depends on where the file lives:
- Inside — doc strings can be brief. These modules are implementation details that end users are not expected to call directly, so a short one-liner stating what a declaration does is usually enough. Trap/error notes can be omitted unless the behavior is surprising to another maintainer.
src/internal/ - Anywhere else under (i.e. outside
src/) — doc strings MUST be comprehensive. These are the public API surface that users will call, so they need every detail: argument units and formats, size/range constraints, return-value semantics, full failure behavior (src/internal//Trapsparagraphs), and runnable examples where helpful. Apply the full "User-Perspective Read-Through" checklist at the end of this skill to every declaration here.Errors
文档字符串的详细程度取决于文件所在位置:
- 目录内:文档字符串可以简洁一些。这些模块是实现细节,终端用户无需直接调用,因此用简短的单行语句说明声明的用途通常就足够了。除非行为对其他维护者来说出乎意料,否则可以省略陷阱/错误说明。
src/internal/ - 下的其他位置(即
src/之外):文档字符串必须详尽。这些是用户会调用的公共API表面,因此需要包含所有细节:参数的单位和格式、大小/范围限制、返回值语义、完整的失败行为(src/internal//Traps段落),以及在有帮助时提供可运行的示例。对这里的每个声明都要应用本指南末尾的"用户视角通读检查清单"。Errors
Doc String Format
文档字符串格式
mo-doc///motoko
/// Brief one-line description ending in a period.
///
/// Optional longer paragraph(s) describing semantics, edge cases,
/// and return values. Refer to parameters by `name` in backticks.
///
/// Example:
/// ```motoko include=import
/// let result = Module.func(arg);
/// ```
public func func(arg : T) : U { ... };Conventions used in :
mo:core- First line is a short imperative summary ("Returns ...", "Computes ...", "Decodes ...") ending with a period.
- One blank line separates paragraphs.
/// - Wrap parameter names, types, and short code in single backticks.
- Code blocks use fences. Use the
```motokoannotation when the snippet relies on the module's import header (defined elsewhere with```motoko include=import).```motoko name=import - For module-level docs, define a named import block once at the top so all
example snippets can reference it:
motoko
/// ```motoko name=import /// import Hash "mo:bitcoin/Hash"; /// ``` module { ... } - The /
name=importannotations are NOT consumed byinclude=importitself — they are directives for an external doctest runner (used bymo-doc's CI and by Docusaurus-based doc sites) that prepends the named snippet before compiling each example.mo:corepasses them through verbatim into the rendered code-fence info string. If the project does not run doctests, you can omit them and just use plainmo-docfences.```motoko
mo-doc///motoko
/// 简短的单行描述,以句号结尾。
///
/// 可选的较长段落,用于描述语义、边缘情况
/// 和返回值。用反引号包裹参数名称`name`来引用。
///
/// 示例:
/// ```motoko include=import
/// let result = Module.func(arg);
/// ```
public func func(arg : T) : U { ... };mo:core- 第一行是简短的祈使句摘要("返回..."、"计算..."、"解码..."),以句号结尾。
- 用一个空的行分隔段落。
/// - 将参数名称、类型和短代码用单个反引号包裹。
- 代码块使用围栏。当代码片段依赖模块的导入头(在其他地方用
```motoko定义)时,使用```motoko name=import注释。```motoko include=import - 对于模块级文档,在顶部定义一个命名的导入块,以便所有示例片段都可以引用它:
motoko
/// ```motoko name=import /// import Hash "mo:bitcoin/Hash"; /// ``` module { ... } - /
name=import注释不会被include=import本身处理——它们是外部文档测试运行器(mo-doc的CI和基于Docusaurus的文档站点使用)的指令,会在编译每个示例前预先添加命名的代码片段。mo:core会将它们原封不动地传递到渲染后的代码围栏信息字符串中。如果项目不运行文档测试,可以省略这些注释,只使用普通的mo-doc围栏。```motoko
Where to put the module-level doc string
模块级文档字符串的放置位置
The module-level block must sit at the very top of the file,
before the statements, with one blank line separating it from
the imports and one blank line between the imports and the
line. This matches the layout used in and is the
only placement that actually attaches to the module page —
a doc block placed between the imports and is silently
ignored and the rendered module page will have no description.
///importmodule { ... }dfinity/motoko-coremo-docmodule { ... }motoko
/// One-line module summary.
///
/// Longer description.
///
/// ```motoko name=import
/// import Foo "mo:pkg/Foo";
/// ```
import Bar "mo:core/Bar";
import Baz "mo:core/Baz";
module {
// ...
}For named modules (, e.g. )
the same rule applies: doc block at the top of the file, then a blank
line, then imports, then a blank line, then .
module Name { ... }src/bitcoin/Script.momodule Name { ... }模块级的块必须放在文件的最顶部,在语句之前,用一个空行将其与导入语句分隔开,导入语句和行之间也用一个空行分隔。这与中使用的布局一致,也是会将其附加到模块页面的唯一放置方式——放在导入语句和之间的文档块会被忽略,渲染后的模块页面将没有描述。
///importmodule { ... }dfinity/motoko-coremo-docmodule { ... }motoko
/// 单行模块摘要。
///
/// 较长的描述。
///
/// ```motoko name=import
/// import Foo "mo:pkg/Foo";
/// ```
import Bar "mo:core/Bar";
import Baz "mo:core/Baz";
module {
// ...
}对于命名模块(,例如),同样的规则适用:文档块放在文件顶部,然后是一个空行,接着是导入语句,再是一个空行,最后是。
module Name { ... }src/bitcoin/Script.momodule Name { ... }Where to put per-declaration comments
每个声明的注释放置位置
Place lines immediately above the declaration with no blank line
between them. If there is an existing legacy comment, put the
block above the legacy comment (the legacy comment can stay as
implementation notes).
///// ...///motoko
/// Public API description goes here.
// Legacy implementation notes can stay below the doc string.
public func encode(input : [Nat8]) : Text { ... };将行放在声明的紧上方,两者之间没有空行。如果已有遗留的注释,将块放在遗留注释上方(遗留注释可以保留作为实现说明)。
///// ...///motoko
/// 公共API描述放在这里。
// 遗留的实现说明可以保留在文档字符串下方。
public func encode(input : [Nat8]) : Text { ... };Common Pitfalls
常见陷阱
1. apply_patch
and literal \n
apply_patch\n1. apply_patch
与字面量\n
apply_patch\nWhen inserting multi-line content, write actual newlines in the patch body
(one per real line). NEVER use the escape sequence inside
inserted text — it will be written as the literal two characters and break
the file.
+\n插入多行内容时,在补丁正文中写入实际的换行符(每行一个)。绝对不要在插入的文本中使用转义序列——它会被写成两个字面字符并破坏文件。
+\n2. Don't accidentally insert into the middle of a function
2. 不要意外插入到函数内部
apply_patchmo-docmoc --checksyntax error [M0001], unexpected token 'import'loopfuncapply_patchmo-docmoc --checksyntax error [M0001], unexpected token 'import'loopfunc3. Class member documentation order
3. 类成员文档的顺序
mo-doc///public class Name(...)mo-doc///public class Name(...)4. Re-exported types
4. 重导出的类型
Re-exported types like
still need a one-line
description so they are not rendered as "(no description)".
public type Signature = Types.Signature;///像这样的重导出类型仍然需要一行描述,以免被渲染为"(无描述)"。
public type Signature = Types.Signature;///5. Skip private helpers and constants
5. 跳过私有辅助函数和常量
Don't add to , , or declarations that lack
. They never appear in the output and the comments add visual
noise.
///letfunctypepublic不要给不带的、或声明添加注释。它们永远不会出现在输出中,注释只会增加视觉噪音。
publicletfunctype///6. module Name { ... }
named modules
module Name { ... }6. module Name { ... }
命名模块
module Name { ... }Files like use the form
instead of the bare . The same top-of-file placement rule
applies — the module-level block goes at the very start of the
file (before the imports), not on the line directly preceding
.
src/bitcoin/Script.momodule Script { ... }module { ... }///module Script {像这样的文件使用形式,而不是裸。同样的顶部放置规则适用——模块级的块放在文件的最开头(在导入语句之前),而不是的紧上方。
src/bitcoin/Script.momodule Script { ... }module { ... }///module Script {7. Module doc must be at the top of the file
7. 模块文档必须放在文件顶部
mo-doc///importmodule { ... }module { ... }///module 只有当块出现在文件的最开头、在语句之前时,才会将其视为模块描述。放在导入语句和之间的文档块可以正常编译,但在渲染后的HTML中会产生空的模块描述。如果发现现有项目的模块文档紧邻,请将它们移到文件顶部(一个小型Python脚本可以批量迁移,它会找到之前的末尾块并将其前置到文件中)。
///importmo-docmodule { ... }module { ... }module ///Workflow
工作流
- Inventory public declarations:
bash
grep -RInE "public (type|func|class|let)" src - For each file:
.mo- Add a module-level block (with a
///example) at the beginning of the file, even before the block of import statements.name=import - Add blocks above every public declaration inside the top-level modules.
/// - Recurse into nested public declarations. For instance public members of public classes need doc strings. Public members of public modules need doc strings. And so on.
- Add a module-level
- Re-scan to catch anything missed:
Empty output = all public declarations are preceded by abash
awk ' FNR==1{prev=""} { if ($0 ~ /^[[:space:]]*public[[:space:]]+(type|func|class|let)\b/) { p=prev; gsub(/^[[:space:]]+|[[:space:]]+$/, "", p); if (p !~ /^\/{3}/) printf "%s:%d:%s\n", FILENAME, FNR, $0; } if ($0 !~ /^[[:space:]]*$/) prev=$0; } ' src/*.mo src/**/*.moline./// - Generate docs:
No output = success. Any "Skipping ..." line indicates a syntax error that must be fixed (often a straybash
mo-doc --source src --output docs --format htmlcorruption).apply_patch - Spot-check the rendered output:
- — every module should appear in the listing.
docs/index.html - Each — module description, types, functions, and class members should all show their text.
docs/<Module>.html
- 盘点公共声明:
bash
grep -RInE "public (type|func|class|let)" src - 对于每个文件:
.mo- 在文件开头(甚至在导入语句块之前)添加模块级的块(包含
///示例)。name=import - 在顶层模块内的每个公共声明上方添加块。
/// - 递归处理嵌套的公共声明。例如,公共类的公共成员需要文档字符串,公共模块的公共成员也需要文档字符串,依此类推。
- 在文件开头(甚至在导入语句块之前)添加模块级的
- 重新扫描以遗漏的内容:
输出为空表示所有公共声明前都有bash
awk ' FNR==1{prev=""} { if ($0 ~ /^[[:space:]]*public[[:space:]]+(type|func|class|let)\b/) { p=prev; gsub(/^[[:space:]]+|[[:space:]]+$/, "", p); if (p !~ /^\/{3}/) printf "%s:%d:%s\n", FILENAME, FNR, $0; } if ($0 !~ /^[[:space:]]*$/) prev=$0; } ' src/*.mo src/**/*.mo行。/// - 生成文档:
无输出表示成功。任何"Skipping ..."行表示存在必须修复的语法错误(通常是bash
mo-doc --source src --output docs --format html导致的损坏)。apply_patch - 抽查渲染后的输出:
- ——每个模块都应出现在列表中。
docs/index.html - 每个——模块描述、类型、函数和类成员都应显示对应的文本。
docs/<Module>.html
Tips for Writing Useful Descriptions
编写实用描述的技巧
- Describe what the function does and what it returns, not how it is implemented.
- For low-level helpers (,
readBE32, etc.) a one-liner stating the byte order, width, and offset semantics is sufficient.writeLE64 - For domain types (,
SighashType,WitnessProgram) name the spec or BIP that defines the format and link to it.OutPoint - Keep examples short and self-contained; prefer literal byte arrays over reading from external sources.
- 描述函数做什么以及返回什么,而不是它是如何实现的。
- 对于低级辅助函数(、
readBE32等),用单行语句说明字节顺序、宽度和偏移语义就足够了。writeLE64 - 对于领域类型(、
SighashType、WitnessProgram),注明定义格式的规范或BIP并提供链接。OutPoint - 示例要简短且自包含;优先使用字面字节数组,而不是从外部源读取。
Documenting Error and Trap Behavior (REQUIRED)
记录错误和陷阱行为(必填)
Every public function doc string MUST describe its full failure behavior.
Readers cannot tell from a type signature alone whether a function traps,
returns , returns , or simply produces a wrong-but-defined
result on bad input — the doc string is the only place this contract is
recorded.
null#err每个公共函数的文档字符串必须描述其完整的失败行为。读者无法仅从类型签名判断函数是否会触发陷阱、返回、返回,还是在输入错误时产生错误但已定义的结果——文档字符串是记录此约定的唯一位置。
null#errWhat to look for in the implementation
实现中需要注意的点
Scan the function body (and every helper it calls) for:
- /
Runtime.trap(...)/Debug.trap(...)calls.Prim.trap(...) - statements (a failed assert traps).
assert ...; - Pattern matches that are non-exhaustive in practice (e.g. a on
switchwhose?Tbranch traps, or anull).case (#err _) Runtime.trap ... - Implicit traps from the standard library: out-of-bounds array indexing
(when
a[i]),i >= a.size()subtraction underflow, division by zero,NatonOption.unwrap.null - Explicit error returns: returning
?T,nullreturningResult<T, E>, variant returns like#err.{ #ok; #err }
扫描函数体(以及它调用的每个辅助函数),查找:
- /
Runtime.trap(...)/Debug.trap(...)调用。Prim.trap(...) - 语句(断言失败会触发陷阱)。
assert ...; - 实际非穷尽的模式匹配(例如对的
?T,其switch分支触发陷阱,或null)。case (#err _) Runtime.trap ... - 标准库的隐式陷阱:数组索引越界(时的
i >= a.size())、a[i]减法下溢、除以零、Nat处理Option.unwrap。null - 显式错误返回:返回
?T、null返回Result<T, E>、变体返回如#err。{ #ok; #err }
What to write
需要编写的内容
For each failure mode, state in the doc:
- The condition — what input or state triggers it, in user-facing
terms ("when contains a character outside the Base58 alphabet", not "when
input").mapBase58[c] == 255 - The outcome — ,
traps,returns null, etc.returns #err(...) - For /option returns, list every distinct error case separately when the variants carry meaning.
Result
Use a dedicated and/or paragraph (or both) at the end of
the doc, after the example and before the runtime/space notes:
TrapsErrorsmotoko
/// Decodes a Base58-encoded string into the original byte array.
///
/// ```motoko include=import
/// let bytes = Base58.decode("StV1DL6CwTryKyV");
/// ```
///
/// Traps if `encoded` contains any character that is not in the Base58
/// alphabet (i.e. not in
/// `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`).
public func decode(encoded : Text) : [Nat8] { ... };For graceful errors:
motoko
/// Parses a WIF-encoded private key.
///
/// Returns `#err(msg)` when:
/// - the input is not valid Base58Check (bad checksum or alphabet),
/// - the decoded payload has an unexpected length (must be 33 or 34 bytes),
/// - the version byte does not match `network`.
public func decode(wif : Text, network : Network) : Result<PrivateKey, Text> { ... };Use the wording "Traps" (capitalised, present tense) for unrecoverable
failures so it stands out and is greppable across the codebase.
对于每种失败模式,在文档中说明:
- 触发条件——什么输入或状态会触发它,用用户易懂的术语描述(例如"当包含Base58字母表之外的字符时",而不是"当
input时")。mapBase58[c] == 255 - 结果——、
traps、returns null等。returns #err(...) - 对于/可选返回,当变体带有特定含义时,分别列出每个不同的错误情况。
Result
在文档末尾使用专门的和/或段落(或两者都用),放在示例之后、运行时/空间说明之前:
TrapsErrorsmotoko
/// 将Base58编码的字符串解码为原始字节数组。
///
/// ```motoko include=import
/// let bytes = Base58.decode("StV1DL6CwTryKyV");
/// ```
///
/// 当`encoded`包含Base58字母表(即不在
/// `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`中)之外的任何字符时,会触发陷阱。
public func decode(encoded : Text) : [Nat8] { ... };对于优雅错误处理:
motoko
/// 解析WIF编码的私钥。
///
/// 当以下情况发生时返回`#err(msg)`:
/// - 输入不是有效的Base58Check(校验和错误或字母表错误),
/// - 解码后的负载长度不符合预期(必须为33或34字节),
/// - 版本字节与`network`不匹配。
public func decode(wif : Text, network : Network) : Result<PrivateKey, Text> { ... };对于不可恢复的失败,使用**"Traps"**(大写,一般现在时)的措辞,使其突出显示并可在整个代码库中通过grep查找。
Pure / total functions
纯函数/全函数
If a function genuinely cannot fail (e.g. , a pure arithmetic
helper that uses fixed-width types and total operators), say so explicitly
with a one-liner like "Never traps." or omit the failure section
entirely — but only after auditing the body to confirm.
Array.size如果函数确实不会失败(例如、使用固定宽度类型和全运算符的纯算术辅助函数),可以明确用单行语句说明"Never traps.",或者完全省略失败部分——但只有在审核函数体确认后才能这样做。
Array.sizeWorked examples from motoko-bitcoin
motoko-bitcoinmotoko-bitcoin
中的示例
motoko-bitcoin- — traps on any character outside the alphabet (not a
Base58.decode, no graceful fallback).Result - — returns
Base58Check.decode;?[Nat8]on bad alphabet, bad length, or checksum mismatch. Document each.null - — returns
Bech32.decodewith distinct messages for invalid characters, mixed case, bad checksum, length out of range, invalid HRP. Mention each error category, not just "returnsResult<_, Text>".#err - — traps on hardened derivation from a public key.
Bip32.derivePath - (and the
Fp.inverseoperator) — traps when the value is zero./ - /
Affine.add— document behaviour at the point at infinity and for equal/opposite inputs.Jacobi.add - Transaction serialization (,
Transaction.toBytes, etc.) — note any size limits that would causeTxInput.toBytes/Nat32conversion traps.Nat64
- ——当输入包含字母表之外的任何字符时触发陷阱(不是
Base58.decode,没有优雅回退)。Result - ——返回
Base58Check.decode;当字母表错误、长度错误或校验和不匹配时返回?[Nat8]。需分别记录每种情况。null - ——返回
Bech32.decode,针对无效字符、大小写混合、校验和错误、长度超出范围、无效HRP等情况返回不同的消息。需提及每个错误类别,而不仅仅是"返回Result<_, Text>"。#err - ——从公钥进行硬派生时触发陷阱。
Bip32.derivePath - (以及
Fp.inverse运算符)——当值为零时触发陷阱。/ - /
Affine.add——记录无穷远点以及相等/相反输入时的行为。Jacobi.add - 交易序列化(、
Transaction.toBytes等)——记录可能导致TxInput.toBytes/Nat32转换陷阱的任何大小限制。Nat64
Audit workflow for an existing file
现有文件的审核工作流
- List every and
public funcmember.public class - For each, read the body and follow the call graph one level into private helpers.
- Note every ,
trap,assert/Resultreturn, and any implicit trap source (subtraction, indexing, division).Option - Update the doc string to enumerate the conditions.
- Re-run and visually scan the rendered HTML for sections that still lack a "Traps" / "Errors" paragraph on a non-trivial function.
mo-doc
- 列出每个和
public func成员。public class - 对于每个成员,阅读函数体并跟踪调用图到私有辅助函数的第一层。
- 记录每个、
trap、assert/Result返回,以及任何隐式陷阱源(减法、索引、除法)。Option - 更新文档字符串以枚举这些条件。
- 重新运行并目视扫描渲染后的HTML,查找非平凡函数仍缺少"Traps"/"Errors"段落的部分。
mo-doc
Final Step: User-Perspective Read-Through (REQUIRED)
最后一步:用户视角通读检查(必填)
After every public declaration has a doc string, do one more pass. Read
each doc string from the perspective of a first-time user of the API who
has not seen the implementation. For every doc, ask:
- What is the unit / format of each argument and the return value? (bytes vs. bits, big- vs. little-endian, satoshis vs. BTC, raw vs. DER-encoded, compressed vs. uncompressed, 0-based vs. 1-based, …)
- What are the size or range constraints on each input?
- Which BIP / RFC / spec defines the format, and is it linked?
- For mutating methods, what state changes? Is the receiver still usable afterward?
- For functions that take a callback or proxy (e.g. an ECDSA signer), what is the expected input/output shape of the callback?
- For variant returns, what does each variant mean semantically (not just what tag it carries)?
- Are domain-specific terms ("witness program", "tap leaf", "sighash", "scriptPubKey") used without a one-line explanation or link?
- For constants (,
EMPTY_WITNESS), what is the value and why does it have the value it has?dustThreshold - For re-exported types, where is the actual definition (and is the link there)?
- If you removed the function name, would the description still be
unambiguous? If two near-identical functions exist (e.g. vs.
toBytes,toBytesIgnoringWitnessvs.encode), is the difference between them spelled out?encodeUncompressed
If any question remains unanswered, extend the doc string to address it.
Prefer one extra sentence in the doc over forcing the user to read the
source. Do this pass file by file; it usually surfaces 2–5 missing facts
per non-trivial module.
在每个公共声明都有文档字符串后,再做一次检查。从首次使用API且未见过实现的用户视角阅读每个文档字符串。对于每个文档,问自己:
- 每个参数和返回值的单位/格式是什么?(字节vs位、大端vs小端、聪vsBTC、原始vsDER编码、压缩vs未压缩、0基vs1基等)
- 每个输入的大小或范围限制是什么?
- 定义格式的BIP/RFC/规范是什么,是否提供了链接?
- 对于可变方法,状态会发生什么变化?接收器之后是否仍可使用?
- 对于接受回调或代理(例如ECDSA签名器)的函数,回调的预期输入/输出形状是什么?
- 对于变体返回,每个变体在语义上是什么意思(不仅仅是它携带的标签)?
- 是否使用了领域特定术语("见证程序"、"tap leaf"、"签名哈希"、"scriptPubKey")但没有单行解释或链接?
- 对于常量(、
EMPTY_WITNESS),其值是什么,为什么是这个值?dustThreshold - 对于重导出的类型,实际定义在哪里(是否有链接)?
- 如果去掉函数名,描述是否仍然明确?如果存在两个几乎相同的函数(例如vs
toBytes、toBytesIgnoringWitnessvsencode),是否明确说明了它们之间的区别?encodeUncompressed
如果有任何问题未得到解答,请扩展文档字符串以解决它。宁愿在文档中多写一句话,也不要强迫用户阅读源代码。逐个文件进行此检查;对于非平凡模块,通常会发现2-5个缺失的信息点。