stdlib ドキュメントコメント整備方針

最終更新: 2026-03-15

1. 目的

  • stdlib reboot に伴う大規模な API 変更の中で、利用者が各関数・各型の責務と制約をコードから直接理解できるようにする。
  • nm 拡張 markdown と doctest を活用しつつ、コメントがソースコードの実体から乖離しない運用を固定する。
  • NEPLg2 の stdlib・examples・将来の selfhost compiler で、機械生成ではない説明責任のある文書化を徹底する。

2. 基本原則

  • ドキュメントコメントは手書きで整備する。
  • テンプレートをそのまま流し込まない。
  • 機械生成のような定型文を量産しない。
  • コメントはソースコードの現在の実装と一致していなければならない。
  • 変更したコードに対応するコメントが古くなっていないか、必ず同時に確認する。
  • 「何をするか」だけでなく、「なぜその API にしたか」「どのアルゴリズムか」「どこが危険か」を書く。
  • [目的/もくてき] のような表記は nm の ruby 記法を用いたものであり、読みが難しい語や誤読しやすい語には同様の記法を使うようにする。
  • ruby 記法は可読性を上げるために積極的に用いる。必要な箇所では省略しない。

3. nm と見出しの書き方

ドキュメントコメントは nm、すなわち NEPLg2 の拡張 markdown で書く。
したがって、通常の markdown と同じく見出しを使って構造を明示する。

3.1 見出しの役割

  • #

- ファイル全体、またはそのファイルで定義する大きな機能群の説明に使う。
- 例: # serialize

  • ##

- trait、struct、enum、関数など、個々の定義の説明に使う。
- 例: ## Serialize
- 例: ## serialize

  • ###

- その定義の中の節見出しに使う。
- 例: ### [目的/もくてき]
- 例: ### [注意/ちゅうい]
- 例: ### [計算量/けいさんりょう]

原則:

  • ファイル全体の説明は #
  • 各定義の説明は ##
  • その中の詳細説明は ###

この階層を揃えることで、ファイルを上から読んだときに

  • これは何のファイルか
  • どの定義があり
  • 各定義が何を意図しているか

を追いやすくする。

3.2 節の基本形

各定義では、少なくとも次の節を基本にする。

  • ### [目的/もくてき]
  • ### [注意/ちゅうい]
  • ### [計算量/けいさんりょう]

必要に応じて次を追加する。

  • ### [使用例/しようれい]
  • ### [実装/じっそう]
  • ### [入力形式/にゅうりょくけいしき]
  • ### [出力形式/しゅつりょくけいしき]
  • ### [安全性/あんぜんせい]
  • ### [target]
  • ### [移行/いこう]

[目的/もくてき] のような表記は nm の ruby 記法である。
読みが難しい語や誤読しやすい語には、必要な箇所で必ず ruby 記法を使う。

3.3 書き方の例

ファイル全体の説明:

//: # serialize
//: [機械/きかい][向/む]けの[安定/あんてい][文字列表現/もじれつひょうげん]を[与/あた]える trait [群/ぐん]
//:
//: ### [目的/もくてき]
//: - `Serialize` trait を[標準化/ひょうじゅんか]し、[保存/ほぞん]・[比較/ひかく]・[通信/つうしん]に[使/つか]う[文字列表現/もじれつひょうげん]を[共通/きょうつう] helper から[得/え]られるようにします。
//: - `Stringify` が[利用者向/りようしゃむ]け、`Debug` が[開発者向/かいはつしゃむ]けであるのに[対/たい]し、`Serialize` は[機械/きかい]に[渡/わた]しても[壊/こわ]れにくい[表現/ひょうげん]を[担当/たんとう]します。
//:
//: ### [注意/ちゅうい]
//: - [当面/とうめん]は `str` を[媒体/ばいたい]にした[最小/さいしょう]の trait として[導入/どうにゅう]します。
//: - [形式/けいしき]を[複数/ふくすう][持/も]つ `Serialize<T, F>` への[一般化/いっぱんか]は[後続/こうぞく]の[課題/かだい]です。
//:
//: ### [計算量/けいさんりょう]
//: - 各 impl に[依存/いそん]します。`str` の[再利用/さいりよう]は O(1)、[数値/すうち]の 10 [進/しん][変換/へんかん]は O(d) です。

trait の説明:

//: ## Serialize
//: [機械向/きかいむ]け[文字列表現/もじれつひょうげん]
//:
//: ### [目的/もくてき]
//: - [値/あたい]を `str` に[変換/へんかん]し、[保存/ほぞん]や[比較/ひかく]のために[安定/あんてい]した[表現/ひょうげん]を[得/え]られるようにします。
//:
//: ### [注意/ちゅうい]
//: - [現在/げんざい]の stdlib では `Serialize` の[返/かえ]り[値/あたい]は `str` に[固定/こてい]します。
//: - [人間/にんげん][向/む]けの[簡易/かんい][表示/ひょうじ]は `Stringify`、[調査用/ちょうさよう]の[詳細/しょうさい][表示/ひょうじ]は `Debug` を[使/つか]います。

helper 関数の説明:

//: ## serialize
//: trait を[通/とお]して[直列化/ちょくれつか][文字列/もじれつ]を[得/え]る
//:
//: ### [目的/もくてき]
//: - [呼/よ]び[出/だ]し[側/がわ]が[具体的/ぐたいてき] impl を[意識/いしき]せず、[共通/きょうつう]の[入口/いりぐち]から[機械向/きかいむ]け[表現/ひょうげん]を[取得/しゅとく]できるようにします。
//:
//: ### [使用例/しようれい]
//: neplg2:test
//: ```neplg2
//:| #entry
//:| #target std
//:| use std::test as *
//:| use core::traits::serialize as *
//:| let main \():
//:|     block:
//:|         ; assert_str_eq "42" serialize 42
//:|         assert_str_eq "false" serialize false
//: ```
//:
//: ### [実装/じっそう]
//: - `Serialize::serialize x` をそのまま[呼/よ]びます。
//:
//: ### [注意/ちゅうい]
//: - [型推論/かたすいろん]が[曖昧/あいまい]な[場合/ばあい]は[周囲/しゅうい]の[型注釈/かたちゅうしゃく]で[解決/かいけつ]します。
//:
//: ### [計算量/けいさんりょう]
//: - impl に[依存/いそん]します。

3.4 見出しを使う理由

見出しを明示する理由は次のとおり。

  • markdown として読んだときに、情報の段差が分かりやすい
  • nm を使った文書化・HTML 変換・将来の hover 表示でも節構造を保ちやすい
  • 長いコメントでも「目的」「注意」「計算量」を読み飛ばさずに探せる
  • boilerplate な平文の羅列になりにくい

4. 何を書くか

各ファイルの先頭には、次を簡潔に書く。

  • そのファイルがどの機能群を集めたライブラリか
  • どの層に属するか
  • 代表的な用途
  • 重要な制約

各関数・型・trait の直前には、必要に応じて次を書く。

  • [目的/もくてき]

- 何をする API か
- 呼び出し側がこの API を使う理由

  • [実装/じっそう]

- どのようなアルゴリズム・表現・レイアウトを使うか
- 返り値や副作用の意味

  • [注意/ちゅうい]

- 前提条件
- 失敗条件
- 使ってはいけない場面
- move/effect/memory の注意点

  • [計算量/けいさんりょう]

- 時間計算量
- 必要なら空間計算量
- 必要なら最悪計算量・平均計算量・償却計算量

原則:

  • 関数については、公開向けか内部向けかを問わず、[目的/もくてき][実装/じっそう][注意/ちゅうい][計算量/けいさんりょう] を整備する。
  • struct / enum / trait についても、少なくとも 目的注意 を整備する。
  • データ構造については、必要に応じて内部表現、所有権、利用上の制約、計算量の前提も書く。
  • trait については、どの能力・責務を表すか、実装側が守るべき前提、利用側が期待してよい性質を書く。
  • move / copy / clone に関する基本的な性質は、個々の関数だけでなく、そのデータ構造や trait の宣言側にも書く。

- その型は move 前提か
- 軽量コピー可能か
- Clone により独立な複製が得られるのか、共有を伴うのか
- 利用者がどの程度所有権移動を意識する必要があるのか

  • 関数側の [注意] には、その API 呼び出しに固有の move/clone 条件を書く。
  • 型宣言側・trait 宣言側のコメントには、その型・能力全体に通用する所有権規則を書く。
  • [注意/ちゅうい] では、特に所有権やメモリ管理に関わる条件がある場合、それを省略せず明記する。

- 呼び出し後に値が move されるのか
- Clone やコピーが必要なのか
- 呼び出し側が解放責任を持つのか
- RegionTokenMemPtr に寿命・所有権上の制約があるのか
- バッファ再確保により参照やポインタが無効化されるのか
- 失敗時に確保済み領域がどう扱われるのか
- その値が pure persistent value / unique mutable work state / linear capability のどれに分類されるか(doc/2.1spec/memory.md §2 の 3 分類を参照)

  • 計算量は一律に O(n) のような短い記法だけを書くのではなく、その関数の性質に応じて適切な観点を選ぶ。

- 配列走査なら時間計算量を中心に書く
- バッファ確保やコピーを伴うなら空間計算量も書く
- ハッシュ表や木構造では平均計算量と最悪計算量を分けて書く
- push/pop のように償却解析が本質な API では償却計算量を明記する
- 入力分布やハッシュ品質など前提条件がある場合は、その条件も併せて書く

必要な場合だけ追加する節:

  • [入力形式]
  • [出力形式]
  • [安全性]
  • [target]
  • [移行]

5. 書いてはいけないもの

次のようなコメントは禁止とする。

  • 実装を読めば分かるだけの逐語説明

- 例: 「i を 1 増やします」

  • どの関数にも貼れるテンプレート文

- 例: 「この関数は入力を受け取り、結果を返します」

  • 実際の挙動と一致しない説明

- 例: 失敗しない実装なのに Result 的説明を書く

  • ボイラープレート化した「使い方の最小例」の乱造

- 実用上意味のない sample だけの例を各関数に付けるのは避ける

  • 実装が変わったのに更新されていない古い計算量・古い注意書き

6. 良いコメントの条件

良いコメントは、次の条件を満たす。

  • その関数・型に固有の情報が入っている
  • 実装方式を説明している
  • 利用時の判断材料になる
  • 余計な一般論が少ない
  • ソースと一緒に保守できる長さに収まっている

7. 例から見る方針

6.1 良い方向の例

examples/rpn.nepl の冒頭コメントは次の点で良い。

  • REPL であること
  • Stack<i32> / str_split / to_i32 を使う実装例であること
  • 対応演算子が + - * に限定されること
  • 負数対応の前提
  • 全体計算量

つまり、単なる宣伝文ではなく、利用者が読むべき仕様情報が入っている。

stdlib/kp/kpgraph.nepl も次の点で良い。

  • 密行列であること
  • O(n^2) であること
  • 0-index/1-index の扱い
  • BFS を配列キューで実装していること

つまり、データ表現と計算量が API の性質に直結している。

6.2 改善すべき例

stdlib/std/test.nepl には、同じ構造の「最小例」が多数並んでいる。

問題点:

  • どの関数にもほぼ同型の説明が付いている
  • sample を渡すだけの例が多く、利用判断にあまり寄与しない
  • 実装や制約よりテンプレート感が先に立つ

この方向は避ける。

方針:

  • doctest は「その API の典型的な落とし穴」「境界条件」「使い方の要点」がある場合だけ書く
  • 何でもかんでも最小例を量産しない
  • 例を書くなら、その API にしかない重要性を示す

8. doctest の方針

  • ライブラリ .nepl ソースコード内の doctest の用途は、利用者に対して使い方を示し、そのサンプルコードが誤りでないことを保証することである。
  • ライブラリのドキュメントコメントに含める neplg2:test は、その API の典型的な使い方が正しく記述されていることを確認するために書く。
  • これは「実装が正しいこと」を網羅的に証明するためのテストではない。
  • ドキュメントコメント内の例は、利用者が最初に読む「使い方」の一部である。したがって doctest は、説明文とコード例が実際の API と一致していることを保証する役割を持つ。
  • ライブラリ実装の正しさ、境界条件、回帰ケース、内部アルゴリズムの妥当性を確認するテストは、原則として tests/ に配置する。
  • doctest では、特に次のような「利用者が迷いやすい表面仕様」を優先して示す。

- 引数をどの順に置くか
- pipe 記法でどの位置に値が流れ込むか
- 返り値をそのまま使うのか、let で受けるのか
- Resultmatch / unwrap / 補助関数でどう処理するか
- Option の不在をどう扱うか
- struct や enum の値をどう受け取り、どう取り出すか

  • 複数の用途がある API については、用途ごとに doctest を分けて用意する。
  • 重要度が高い API、誤用しやすい API、使い方が複数ある API については、同じ用途に対しても段階の異なる複数の例を置いてよい。

- 例: 最小の呼び出し例
- 例: Result を受けて分岐する例
- 例: pipe 記法で合成する例
- 例: struct や trait と組み合わせる例

  • ただし、同じ内容を言い換えただけの重複例は避ける。複数例を置くなら、それぞれが異なる利用判断や書き方を示していることが必要である。
  • 利用者が直接呼び出さない内部関数については、原則として doctest は必須ではない。
  • ただし内部関数であっても、説明コメントそのものは整備する。
  • 次のどれかに当てはまる場合に、ドキュメントコメントへ典型例として doctest を追加する。

- 典型的な使い方を文章だけでは伝えにくい
- 型注釈や import の書き方が重要
- 成功時の利用形が API の理解に直結する
- compile_fail として「この書き方は誤りである」と利用者向けに示す価値が高い

  • compile_fail は、利用者が踏みやすい誤用をドキュメントとして示すために使う。実装の網羅的検証は tests/ で行う。
  • 意味の薄い sample doctest は増やさない。

8.1 doctest と tests/ の責務分離

  • ドキュメントコメント内の doctest:

- 使い方の説明
- API 表面の理解補助
- import / 型注釈 / 呼び出し形の提示
- 引数配置、返り値処理、Result / Option / struct の受け方の提示

  • tests/ に置くテスト:

- 実装の正しさの確認
- エッジケース
- 回帰防止
- target 差分確認
- move/effect/memory 制約の厳密検証

この区別を崩すと、ドキュメントコメントが巨大化し、利用者向け文書として読みにくくなる。逆に doctest を一切置かないと、説明文と実際の API が乖離しやすい。両者を分離しつつ併用する。

9. reboot 中の優先度

stdlib reboot 中は、次の順でコメントを整備する。

  1. alloc/diag, core/mem, alloc/io, std/streamio
  2. alloc/collections, alloc/text
  3. std/stdio, std/fs, std/env
  4. features
  5. kp のうち昇格対象

理由:

  • 層の境界や安全性の前提が強いファイルほど、コメントの品質が設計理解に直結するため。

10. 実装変更時の運用

コードを変更したら、次を必ず確認する。

  • 関数名・型名・戻り値が変わっていないか
  • 計算量が変わっていないか
  • 安全性の前提が変わっていないか
  • Option / Result / Outcome の使い分け説明が古くなっていないか
  • doctest がまだ有効か

コメントが古い場合は、コードと同じ commit で更新する。

11. まとめ

  • ドキュメントコメントは API の仕様であり、装飾ではない。
  • 実装に追従しないコメントは、コメントが無いより悪い。
  • reboot 後の stdlib では、各関数ごとにその関数に固有の説明を手書きで与えることを標準運用とする。
  • テンプレート、ボイラープレート、機械生成のような反復説明は採用しない。
On this page