- 状態: 採用
- 日付: 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で対象外にしている