- 状態: 採用
- 日付: 2026-08-29
本ライブラリの差別化要因は「1人の開発者が全言語へ同一の API を提供すること」に尽きる
(0001)。これを人手で維持するのは必ず破綻する。
C# に WriteBytes を足して Rust に write_bytes を足し忘れれば、
その時点でライブラリの価値そのものが失われる。
したがって機械で守る必要がある。守るべきものは2つ。
- 全言語の公開API集合が一致していること
docs/api/の対応表が実装と一致していること
| 方法 | 長所 | 短所 |
|---|---|---|
各言語の公式ツール(リフレクション / javap / cargo public-api / .d.ts 解析 / inspect) |
正確。マクロ展開後も見える | CI に5言語ぶんのツールチェーンとビルド成果物が要る。ツール自身のバージョン差で壊れる。5つの別々のツールが5通りの形式で出力するので、それを揃える層が結局要る |
| ソースの静的解析(正規表現) | 依存は Python 3 だけ。ビルド不要で数秒。壊れても原因が1か所 | マクロ生成やメタプログラミングは見えない。書き方の揺れに弱い |
| 方法 | 長所 | 短所 |
|---|---|---|
| 人手で書く | 説明を自由に書ける | 必ず実装から遅れる。5言語 × 数百メンバを手で保守するのは非現実的 |
| 実装から生成する | 原理的にずれない | 説明文をどこから取るか決める必要がある |
- 抽出はソースの静的解析で行う。 実装は
spec/tools/extract_api.py docs/api/*.mdの対応表は生成する。 生成部分は<!-- generated:start -->/<!-- generated:end -->で囲み、check_docs_sync.pyが書き出す。 導入の説明文はその外側に手書きで置く- 説明文は基準実装である C# のドキュメントコメントから取る(0002)。 説明を直すときは C# のソースを直す
- CI は生成結果と現在のファイルを突き合わせ、違えば落とす。
開発者は
--writeで書き直す
型は完全一致を求め、メンバは求めない。
型が欠けているのは機能そのものの欠落を意味するので、
差異は extract_api.EXPECTED_TYPE_GAPS に理由つきで登録しなければ CI が落ちる。
一方メンバは、言語の作法による差異が正当に生じる。
- Rust の
section_mut()— 借用規則のため可変参照用の別メソッドが要る - Rust の
as_longs()/into_longs()— 所有権を渡すかどうかで2つに分かれる - Java の
setFoo()— 他言語はプロパティへ直接代入する iter/items/entries/keys— コレクションの走査は言語ごとに作法が違う
これらを無理に揃えると、どの言語でも不自然な API になる。 メンバの差異は対応表に「—」としてそのまま表示し、 利用者が事実を見て判断できるようにする。
静的解析の弱点は、この設計では実際には効かない。
マクロ生成が見えない問題は Rust の scalar_accessors! だけで、
マクロ呼び出し行から名前を拾えば足りる。
書き方の揺れに弱い点は、全言語を同じ人間が同じ方針で書いているので起きにくい。
むしろ「揺れたら検出される」ほうが望ましい。
CI の軽さは、この検査が実際に回り続けるかを左右する。 5言語のビルドを揃えないと動かない検査は、 壊れたときに直されず放置されて形骸化する。 Python 3 だけで数秒で終わるなら、そうならない。
対応表を生成にしたことで、ドキュメントが実装より価値の高い成果物になった。 手書きの表は「書いた時点の実装のスナップショット」でしかないが、 生成した表は常に現在の実装そのものである。
- 新しい公開型を足したら、
check_docs_sync.API_PAGESにも足さないと CI が落ちる。 これは意図した動作で、ドキュメントに載らない API を作らせないための仕掛けである - 静的解析が読めない書き方(複雑なメタプログラミング)を新たに導入する場合は、 抽出器も同時に直す必要がある。実装より抽出器のほうが先に壊れるので、気づける
- 検証ツール(
conformance)や内部ヘルパ(_始まりのファイル)はextract_api.EXCLUDED_FILESで対象外にしている