検索/大きなJavaバックエンドで、AIにコードスタイルとモジュールの決まりを守らせる

大きなJavaバックエンドで、AIにコードスタイルとモジュールの決まりを守らせる

モジュールとプロバイダで組み立てた大きなJavaサーバーで、AIに作業させる時に使う。先に入口のコードを読み、自動でチェックされるコードスタイルと、サポートする一番古いJavaのバージョンを守り、使うモジュールを全部宣言し、ソースで確かめてから答え、プッシュの前にローカルでテストする。

このAIルールを使うには
## 編集の前に、モジュールとプロバイダのつながりを読む この種の大規模サーバーは、ひとつのアプリケーションとして書かれるのではなく、複数のモジュールを組み立てて構築される。モジュール宣言はそのモジュールの名前と、公開するサービスインターフェースを示す。プロバイダはそれらのサービスを特定の技術ひとつで実装し、他のモジュールは宣言されたインターフェースだけを利用する。プロバイダはプラットフォームのサービスローダー機構によって発見され、決まったライフサイクルに従う。すなわち、オブジェクトを組み立てて設定を読み込む準備フェーズ、その後にサービスを有効化する開始フェーズである。 ### ステージに手を入れる前にパイプラインを追う コレクターはトランスポート層を介してテレメトリを送り、レシーバープラグインがそれを解析・検証し、解析エンジンがプロジェクト独自のドメイン言語を通じて集計し、ストレージプラグインが結果を永続化し、クエリプラグインがそれをインターフェースへ提供する。あるステージの変更は、たいてい別のステージでの対応を必要とする。 ### まずエントリーポイントを読む 編集の前に、コアモジュールの定義、モジュールシステムの基底クラス、ドメイン固有言語の文法ファイル、アプリケーションのエントリーポイント、そしてアーキテクチャのドキュメントを読む。名前を見ただけでは、その機能がどう動くかは分からない。 ## ビルドで自動チェックされるコードスタイルを守る ここでのスタイルは機械的にチェックされるため、違反は議論の始まりではなくビルドの失敗になる。標準出力へ出力してはならない。プロジェクトのロギングファサードを使うこと。作者アノテーションを付けてはならない。タブ文字、スター(ワイルドカード)インポート、未使用や冗長なインポート、空文、連続する複数の空行を使ってはならない。1行につき1文とし、カンマ・セミコロン・キャストの後には空白を入れ、演算子の前後にも空白を入れる。 ### 必須のパターン オーバーライドしたメソッドにはアノテーションを付ける。等価判定とハッシュは一緒にオーバーライドする。ドキュメントのパラメータ・戻り値・例外の各タグには、必ず実質的な説明を書く。long型の定数は大文字のサフィックス文字を付けて書く。switch のデフォルト分岐は最後に置く。ファイルの最大長を守り、それに近づいたものは分割する。 ### 命名 定数と静的変数はアンダースコア区切りの大文字、型パラメータは大文字、型はパスカルケース、ローカル変数・パラメータ・メンバーはキャメルケースを使う。手書きの定型コードよりも、イミュータブルコレクションのファクトリとプロジェクトのアノテーションプロセッサを優先し、ローカル変数とパラメータは final を優先する。 ### 完全修飾クラス名をその場に書かない インポートを追加し、フィールド宣言・メソッドシグネチャ・ローカル変数・ジェネリクスの引数では短い名前を使う。許容される例外は、インポートした2つのクラス間で単純名が実際に衝突している場合と、短い名前では読み手にとって曖昧になるドキュメントリンク1か所だけである。 ### 1行の委譲メソッドを書かない 本体が別のクラスへの転送だけのラッパーは、価値のない中継を1段増やすだけである。呼び出し箇所にインライン展開するか、メソッド参照を含めて対象のオブジェクトを直接呼ぶこと。 ### ライセンスヘッダー ソース・マークアップ・設定の各ファイルは、プロジェクトのライセンスヘッダーを持つ。コメントを書けない形式はライセンス設定によって対象外とされている。ヘッダーが本来不要な場所へ手で付け足すのではなく、その設定に従うこと。 ## クラスとメソッドに、たとえではなく役目の名前を付ける クラスとメソッドは、抽象的な比喩ではなく、その機能に基づいて名付ける。seed(種をまく)、hydrate(水を与える)、bootstrap(ブートストラップ)、prime(呼び水を差す)のような比喩的な語よりも、load・apply・unregister・compile・verify・commit・roll back のような具体的な動詞を優先する。 ### 名前に仕事の内容を語らせる 静的なルールを読み込むクラスは、印象的なイメージではなく、静的なルールを読み込むことに基づいて名付ける。データベースの状態をスケジュールに従って同期するタイマーは、tick ではなくその同期に基づいて名付ける。 ### 比喩は分割のサイン 比喩に頼らずにメソッド名を付けられないなら、そのメソッドはおそらくやりすぎている。それぞれが平易な機能名を持つようになるまで分割すること。 ## コードだけでは分からない事だけをコメントに書く コードがすでに述べていることを説明してはならない。コメントを書くのは、コードを読むだけでは足りないところだけである。すなわち、そのメソッドからは見えない不変条件、複数箇所にまたがる順序の制約、自明でない分岐が存在する理由、一見冗長に見える呼び出しが必要な理由である。コードが明快なら、コメントは書かない。 ### 離れた場所にあるロジックを結び付ける 別の場所で保証されているからこそ存在するチェックには、その保証を指す1行のポインタを添える価値がある。他所の既知の不具合に対する回避策には、その不具合へのリンクを添える価値がある。 ### 言い換えただけのドキュメントは刈り込む メソッドのドキュメントは本体ではなく契約を記述する。複数段落にわたる根拠の説明は、ソースファイルではなく設計ドキュメントに属する。 ## フレームワークのアサーションを使い、テストを変える前に聞く 必ずテストフレームワークのアサーションメソッドを使う。テストの中で言語組み込みのアサーション文を使ってはならない。テストランナーがたまたまそれらの文を有効にしている場合でも、フレームワークのアサーションはコードベースの他の箇所での慣習であり、ランナーの設定に左右されず、文脈のないエラーではなく実際の値を示す構造化された失敗を生む。 ### 失敗するテストは直す。こっそり書き換えない 既存のユニット・統合・エンドツーエンドのテストが失敗したら、まず直すよう試みる。テストケースを変更する必要がある場合、あるいはテストコードを対象から外す必要がある場合、その変更は適用の前に人間による二重チェックを受けなければならない。提案する変更や対象外とする案とその理由を提示し、明示的な承認を待つこと。自分の判断でテストを書き換えたり対象から外したりしてはならない。 ### テストの種類をスコープに合わせる ユニットのスコープと統合のスコープでは、パターンもハーネスも異なる。同じアサーションをあらゆる階層で重複させるのではなく、その振る舞いが実際に存在するスコープで証明すること。 ## サポートする一番古いJavaのバージョンで動くコードを書く 複数の長期サポートリリースをサポートするプロジェクトは、ビルドマシンにより新しいコンパイラが入っていても、最も古いリリースでコンパイルでき、動作しなければならない。エディタが受け付けるものではなく、最小バージョンを前提とすること。 ### 最小バージョンが古いときに避けるべき構文 アロー形式の switch 式、リストを直接返すストリームの終端操作、テキストブロック、レコード型、型チェック内のパターンマッチング、シール型は、いずれも古いベースラインより新しい。代わりに、明示的な case と break を持つ従来型の switch、リストを組み立てるコレクター、文字列連結、アクセサを生成させた通常のクラス、型チェック後の明示的なキャスト、そして通常のクラスまたはインターフェースを使うこと。 ### 安全に使える構文 イミュータブルコレクションのファクトリ、新しめの Optional のメソッド、ラムダ式、メソッド参照、そしてストリームのインターフェース自体は、古いベースラインでも利用でき、回避策を必要としない。 ### ビルドを信じず、検索して確認する 互換性があると主張する前に、禁止された構文ひとつひとつについてソースツリーを検索し、一致がないことを確認する。新しいコンパイラでのビルドは、これらを代わりに検出してはくれない。 ## サブモジュールと生成コードを、ビルドとそろえておく プロトコル定義とウェブインターフェースはサブモジュールに置かれていることがある。常にサブモジュールを含めてクローンし、プロトコルの変更が必要とするときは明示的にサブモジュールを更新すること。サブモジュールのポインタに反映されていないプロトコル変更は、他の誰の環境でもビルドできない。 ### 解析の前に生成する 生成コードを解析する前にコンパイル手順を実行し、テストを走らせる前にプロジェクトをインストールして、プリコンパイラと生成クラスがエクスポートされた状態にする。生成されたクラスを手で編集してはならない。それらを生み出す定義のほうを変更して、再ビルドすること。 ### モジュールをまたぐ変更の後はプロジェクト全体を再ビルドする 変更したモジュールが2つを超える場合、あるいはリモートから pull や rebase をした場合は、個々のモジュールを選ぶのではなくプロジェクト全体をビルドする。単一モジュールの増分ビルドでは、アーカイブのサイズが変わらないまま内容だけが変わったときに、ローカルリポジトリやライブラリディレクトリに古いアーカイブが残ることがあり、追跡が非常に困難な実行時の失敗を引き起こす。 ## ドキュメントと変更履歴を正しく書き、正しく表示させる ドキュメントはマークダウンエンジンによって描画されるため、ソースの読みやすさだけでなく、表示された姿も考慮すること。相対ファイルパスは有効で、リポジトリ内でもドキュメントサイト上でも機能する。絶対リンクではなく相対パスを使い続けること。振る舞いが変わったときは必ず変更履歴を更新する。 ### 変更履歴の各項目は1行に収める 1つの文を項目の中でソース行にまたがって折り返してはならない。入れ子の子項目は本物のリスト項目なので問題ない。変更履歴はリリース本文になり、リリースの描画側は単一の改行をそのまま改行として扱う。そのため折り返された項目は、ドキュメントサイトでは文が流し込まれて問題が隠れていても、リリースページでは文の途中でギザギザに折れて表示される。 ### 設計提案は出荷されたら凍結する 設計提案の実装が着地したら、その提案を実際に作られたものへ一度だけ同期させる。提案されたすべての変更を過去形で書き、そのスコープに含まれるものについては、保留中の作業・今後の作業・未解決の依存・「〜まで空欄」といった記述を残さない。この同期の後、その提案は凍結される。それ以降の指標・ダッシュボード・段階的な機能強化は、運用者向けの生きたドキュメントのほうへ書く。今後の作業として残してよいのは、本当に小さな任意のフォローアップだけであり、大きな変更を残してはならない。 ### アンカーリンクで相互参照する 別のセクションを参照するときは、マークダウンのアンカーリンクを使い、セクション記号やセクション番号だけを書いてはならない。サイトのスラッグ規則のもとで、すべてのアンカーが実在する見出しに解決することを検証すること。壊れたアンカーは何の警告もなくただのテキストとして表示されるからである。 ## コードが探すモジュールを、全部宣言する 別モジュールのサービスを探す処理は、必ず宣言に裏付けられていなければならない。コードがモジュールマネージャに対して、直接であれヘルパー経由であれモジュールを探させるなら、そのモジュール名はプロバイダの必須モジュール一覧に載っていなければならない。宣言の欠落は起動時には失敗せず、そのコードパスが初めて実行された瞬間に例外を投げる。呼び出しを広い catch で包むのは代替にならない。モジュールを宣言し、防御的な処理は一時的なプロバイダの停止に対してだけ残すこと。ブランチを監査するときは、触れたすべてのモジュールでモジュールマネージャの検索を探し、対象それぞれが宣言されていることを確認する。 ### メインの実装だけでなく、すべての実装を更新する コアモジュールのサービス一覧にサービスを追加したら、そのモジュールのすべてのプロバイダ実装を更新する。単独ツールやテストハーネスが使うモックプロバイダも含める。実装はひとつだと決めつけず、ツリーからサブクラスを検索すること。メインのサーバーだけがパッケージングではない。単独ツールはモックプロバイダとモジュールの部分集合で起動するため、コアモジュールの契約への変更はそれらにも届かなければならず、届かなければそのツールは起動時に失敗する。 ### 調整モジュールより、すでに値が入っているサービスを優先する レシーバーモジュールとアナライザーモジュールは、通常クラスタ調整モジュールを宣言していないため、それを直接探すと実行時に例外になる。コアモジュールのピア管理サービスを経由すること。これは調整レイヤーがすでに値を入れており、多くのコンポーネントが必要とするピア一覧を公開している。もし本当に調整のプリミティブが必要なモジュールがあるなら、そのモジュールを明示的に宣言する。 ### スレッドローカルの裏経路で意図を渡さない 呼び出し側の意図をスレッドローカル経由で下流のコードに読ませると、離れたコードパスの間に見えない結合が生まれ、エグゼキュータやイベントループのような非同期の受け渡しをまたいで漏れ出し、メソッドシグネチャからは振る舞いを理解できなくなる。代わりにインターフェースを拡張すること。行数が増えても、シグネチャに現れるパラメータ・メソッド・モード値を追加する。数少ない例外は、業界全体がスレッドローカルで標準化しているコンテキスト伝播と、フレームワークが強制するセキュリティプリンシパルである。 ## スキーマの変更が全ノードに届くのを待ってから書き込む スキーマやメタデータの変更がコーディネーターノードへブロードキャストされ、データノードへ非同期に伝播する構成では、定義呼び出しはコーディネーターが受理した時点でリビジョン識別子を持って返り、他のノードを待たない。定義がまだ伝播していないデータ書き込みは、実行側のノードで破棄され、ログに残されて読み飛ばされる。そのためフェンスが必要になる。 ### リビジョンのウォーターマークを待つ 定義変更を発行した後は、生存するすべてのノードが返されたリビジョン以上を適用したと報告するまで、クライアントのスキーマウォッチャーでブロックし、タイムアウトで上限を設けたうえで、依存する書き込みを解放する。レジストリに対する書き込み後の読み取りチェックは、伝播の証明にはならない。 ### データを消す操作と同期的なストアを扱う 消す操作が使えるリビジョン識別子を返さない場合は、専用の消去完了待ち呼び出しにフォールバックする。定義文がコーディネーター上で同期的に完了するストアには、フェンスはまったく必要ない。自分の書き込みが読めるようになるまで検索呼び出しをポーリングする、という古いやり方を再び持ち込んではならない。それはバリアインターフェース以前のもので、すでに置き換えられている。 ### どのクライアントライブラリを読んでいるか確認する バリアやウォッチャーの型は、公開されている単独のクライアントではなく、ツリー内のクライアントのフォークに存在することがある。公開版のクライアントに見当たらないことは、そのインターフェースが無くなった証明にはならない。 ## 推測せず、ソースとドキュメントを読んで確かめる 決して当て推量や憶測をしてはならない。すべての分析は、ソースコード・ドキュメント・観測された振る舞いに基づかなければならない。ある機能の動きを名前や慣習から想像するのではなく、実装を読むこと。設計を提案する前に、プロジェクトのドキュメント、サブモジュール内のガイダンスファイル、そして readme ファイルを読む。 ### 設定とフラグを検証する どのフラグと環境変数が実際に存在するか、既定値は何か、どう解析されるかを確認する。フラグを環境変数に自動的に結び付ける接頭辞の規約があれば、それも含めて確認する。 ### 依存しているプロジェクトを読む ある機能があると決めつける前に、関係する依存先や関連プロジェクトのソースとドキュメントを読むこと。サポートされるフィールドはデプロイ用のチャートで、サポートされるオプションはテスト基盤で、利用できる呼び出しはクライアントライブラリで確認する。関連プロジェクトがローカルにチェックアウトされていそうなら、そのローカルパスを尋ねること。ローカルのクローンを検索するほうが、リモートのインターフェースからファイルを1つずつ取得するよりはるかに速いからである。 ## CIに頼る前に、コードとテストをローカルで動かす プッシュする前に、コードを実行し、コンテナを起動し、テストをローカルで走らせる。継続的インテグレーションは試行錯誤の場ではない。 ### パスとディレクトリ構造を検証する フラグの名前からディレクトリ名を推測するのではなく、データが実際にどこへ書き出されるかを確認する。ルートパスのオプションは、その下に別の名前のサブディレクトリを作ることが多い。 ### 生成した設定・編集した設定を検証する 設定ファイルを編集した後は、特にストリームエディタやスクリプトで編集した場合、コミットの前に本物のパーサーで検証すること。壊れた設定は、自動化のなかで静かな失敗を引き起こす。 ### イメージとバイナリの存在を確認する コンテナイメージに依存するパイプライン設定やエンドツーエンド設定を書く前に、そのイメージが実際に公開済みであることを確かめ、さらにイメージの中にどのバイナリ・シェル・ディレクトリが存在するかを、それらを使うコマンドを書く前に確認する。イメージが利用できるかどうかは、公開用のワークフローが完了しているかに依存する。イメージの置き場所にも注意すること。自社プロジェクトのイメージはコードホストのレジストリに、完全なコミット識別子をタグとして置かれていることがある一方、公式イメージやサードパーティのイメージは慣例的なタグでパブリックレジストリに置かれている。 ## 決まった提出ワークフローと、許可された自動化ステップでプッシュする レビュー用ブランチへのコミットとプッシュには、プロジェクト専用の提出ワークフローを使う。これは毎回のプッシュの前に、コンパイル・スタイルチェック・ライセンスヘッダーの検証といった事前確認を実行し、レビュー依頼がまだ無ければそれを作成する。 ### 自動化の許可リストを尊重する 組織はサードパーティの自動化ステップに対して許可リストを課していることがあり、その場合は各ステップをバージョンタグではなく承認済みのリビジョン識別子に固定する必要がある。ステップが許可されていないためレビューがブロックされたら、承認済みの一覧を確認し、ワークフローファイルを承認済みの固定値に更新する。プラットフォームのベンダーや組織自身が所有するステップは、通常は固定なしで許可されている。 ### 推測せず、足りないものを言う ソースが見つからない、何かの実行方法が分からない、あるいはコードを読んでも答えがはっきりしない場合は、憶測せずに尋ねること。実行時の振る舞いを調べるときは、実データを使ったローカルのテストを用意し、結果を予測するのではなく実際の出力を観測する。