検索/複数のプラットフォーム向けUIライブラリを、パッケージの層を守って保守する

複数のプラットフォーム向けUIライブラリを、パッケージの層を守って保守する

複数のプラットフォーム向けに、層ごとのパッケージに分かれたUIライブラリで作業する時に使う。パッケージの層と公開済みのエクスポートを崩さず、外部サービスのアダプターを決まったファイル構成で作り、リリース・テスト・レビューの決まりを守る。

このAIルールを使うには
## 各コードを、コアと配布パッケージのどの層に置くか決める コードベースは、それぞれが単一の責務を持つ小さな階層のスタックとして構成する。すなわち、フレームワークのフックをヘッドレスなリソースとして実行するリアクティブプリミティブ。それらのプリミティブをフレームワークの状態システムへ橋渡しするバインディング層。ドメインロジックを所有するフレームワーク非依存のコアランタイム。そして、コアを再エクスポートしプラットフォーム固有のUIプリミティブを追加する、ターゲットプラットフォーム(Web、ネイティブ、ターミナル)ごとに1つの配布パッケージである。プライベートなコンポーネントキットは、ランタイム依存として公開するのではなく利用側プロジェクトへコピーされる、UIの正典ソースを保持する。共有のビルドツールは独自の内部パッケージに置く。 配置の規則: - フレームワークに依存しないロジックはコアパッケージに属する。プラットフォームと結合したランタイムは配布パッケージに属する。 - フレームワークと結合したランタイムは、すべての配布パッケージが再エクスポートするコアパッケージのサブパスに置く。これにより、プラットフォームごとに複製を持つのではなく実装を1つに保てる。 - 利用者がコアパッケージを直接インストールすることはない。インストールするのは配布パッケージちょうど1つである。 - 外部プロバイダーをコアランタイムへ対応付ける統合パッケージは、それ自体が1つの階層であり、コアロジックをフォークしてはならない。 ## 公開したエクスポートは足すだけにし、外さない 公開されるすべてのパッケージの公開サーフェスは追加のみ可能とする。シンボルを移動する際は、そのエクスポートを新しいファイルへ向け直す。すでに出荷済みのエクスポートを削除してはならない。リポジトリ内の監査では下流の利用者を把握できないため、一見未使用に見える型であっても削除は破壊的変更となる。実際の挙動変更は、移動やリネームに紛れ込ませることなく、意図された独立した変更として出荷する。 長期にわたる移行でレガシー実装を新しいアーキテクチャへ置き換える場合、パッケージのエントリポイントはその期間、両方の経路を再エクスポートしつつ、なお追加のみ可能な状態を保つ。そうした移行期間中は次を守る。 - 新しい挙動は正典のサーフェスに追加し、非推奨の経路には決して追加しない。 - 統合ごとに複製を書くのではなく、共有のコアプリミティブを再利用する。 - さらに別のオプトアウトフラグを導入するよりも、再利用を優先する。 ## 外部サービスのアダプターを、決まったファイル構成で作る 統合アダプターは、1つのエントリフック、一組のアクセサーフック、および純粋なコンバーターを通じて、外部プロバイダーをコアランタイムへ対応付ける。指針となる原則はこうである。ランタイムファイルが調整し、純粋なモジュールが変換し、コントローラー(存在する場合)が集約し、フックは読み取りのみを行う。 - コアランタイムの選択。メッセージがプロバイダーの所有するソースから導出される場合は外部ストアランタイムの上に構築し、プロバイダーがスレッド状態を保持しない場合はローカルランタイムとチャットモデルアダプターの上に構築する。スレッド一覧が必要なときは、いずれの場合も共有のマルチスレッドランタイムでラップする。出荷済みのランタイムコアを再利用すること。並行するスレッドランタイムの状態保持機構を自作してはならない。 - 状態の公開。ランタイム状態をアクセサーフックへ公開する際は、独自に組み立てたユニークシンボルによるブランドとガードではなく、共有のランタイムエクストラのファクトリーを用いる。 - 標準のファイル構成。調整のみを担うランタイムのエントリファイル1つ、ランタイムエクストラのインスタンス1つ、アクセサーとアクションのフックのファイル1つ、双方向のメッセージ変換を行う純粋なファイル1つ、そして型定義ファイル1つ。アダプターがスレッド状態を所有する場合はスレッドコントローラーと純粋な状態リデューサーを追加し、プロトコルがワイヤーフォーマットを所有する場合はサーバーまたはノードのサブパスのエントリを別途追加する。 - サーバー専用コード。サーバー専用のコードとプロバイダーSDKのコードは、サーバーまたはノードのサブパス内に留め、既定のエントリとネイティブのエントリからは除外する。 - テスト。各モジュールの隣に併置し、コンバーターを双方向で、リデューサーまたはコントローラーを、そしてすべてのアクセサーフックを対象とする。 プロバイダーによって決まる選択は柔軟なままとする。どのコアプリミティブの上に構築するか、薄いラッパーかアキュムレーターかコントローラーか、独自のトランスポート、人間参加型サポートをどこまで充実させるか、スレッド一覧のサポートをどこまで深く行うか、といった点である。 ## アダプター同士の機能をそろえ、変換で例外を出さない ランタイム横断の機能同等性。アダプター間で機能を同等に保つ。ある機能が1つのランタイムに追加されたら、その概念が当てはまるすべてのランタイムで公開するか、なぜ1つだけ異なるのかを文書に残す。黙って生じた同等性の欠落は、アダプターを切り替える人にとって破壊的な期待外れとなる。 防御的なコンバーター。コンバーターとコンテンツブロックのレンダラーは、未定義のフィールド、欠落したフィールド、利用できないプラットフォーム機能に対して決して例外を投げてはならない。仕様外のプロバイダーのペイロードに備え、ブラウザー専用のプラットフォームAPIを、サーバー上、ターミナルのレンダラー、あるいはネイティブのターゲットでも実行されるコード経路から排除する。 決して導入してはならないパターン: - 出荷済みのランタイムコアを複製する、並行したスレッドランタイムの状態保持機構。 - 非リアクティブな状態をコンポーネントツリーへ橋渡しするための、変更通知とバージョンカウンターによる再レンダリングの小細工。 - プロトタイプ生成を通じてオブジェクトにメソッドを継ぎ足すこと。 - 呼び出し側が所有するオブジェクトへのモンキーパッチ。 ## 共有のビルドツールでビルドし、変更ごとにリリースノートを書く 公開対象のすべてのパッケージは、共有のビルドツールを通じてビルドする。パッケージごとのビルド設定を追加したり、別のバンドラーやコンパイラーのCLIを直接持ち出したりしてはならない。エクスポートマップはモジュール限定かつ型優先とし、型宣言を既定の条件よりも前に記載する。また、各パッケージは副作用がないことを宣言する。 - UIの正典ソースディレクトリは1つとする。テンプレートと例はコンパイラーのパスマッピングを通じてそれをエイリアスし、バイト単位で同一の複製を持たない。ただし、最小構成のスターターが意図的に自前の複製を出荷する場合は例外とする。同期スクリプトがその複製をバイト単位で同一に保ち、意図的な差異はすべてスクリプトのオーバーライド一覧に宣言する。 - ディレクトリ構成の慣習は意図によって異なる。すべてのプリミティブが両方の形態で存在する場合は並列のディレクトリを用い、一部のファイルだけが第2の形態を持つ場合は疎なバリアント接尾辞を用い、接尾辞のないファイルを既定のソースとする。 - コンパイラーのツールチェーンやそのプラグインを更新する際は、コンパイラープラグインの検証タスクを再実行する。ビルドが通ることだけでは、ツールチェーンが依然として健全である証明にはならない。 - 公開されるバンドルが実行時にインポートするパッケージは、開発依存ではなくランタイム依存に属する。そうすることでバンドラーがそれを外部として扱う。配置を誤ると、そのパッケージがインライン化され、解決不能な推移的インポートを利用側のビルドへ引きずり込む。 - コピーしてインストールできるコンポーネントのエントリは自己完結していなければならない。内部コンポーネントのインポートとスタイルシートのインポートをすべてレジストリ依存として列挙し、インストーラーが解決不能なインポートを含むファイルを配置しないようにする。 - 公開パッケージへの変更にはすべてリリースノートの項目を添える。常にパッチ更新を用いること。マイナーおよびメジャーの更新にはメンテナーの承認が必要である。プライベートパッケージは対象外とする。 ## 自動生成のAPIリファレンスを、手で書き換えない 生成されるAPIリファレンスのツリー全体は、手作業ではなく、その生成スクリプトが所有する。 - ページ全体が生成対象であることを示すマーカーを持つページは、全体が生成側の所有物である。生成ブロック内の手編集は次回の実行で失われる。恒久的な文章は、代わりにソースのドキュメントコメントに置く。 - 再生成後も残すべき手書きの内容は、指定された手動用または例示用のスロットにのみ記載する。 - 完全に手作業で保守するページは、フロントマターの直後に自動生成をスキップするマーカーを付けなければならない。このマーカーによってページはセクションのナビゲーションに掲載され続け、削除対象からも除外される。マーカーがないと、想定外の非生成ページとして未管理かつ陳腐化していると報告され、厳格モードでは失敗する。 - そのツリー配下のセクションおよびルートのナビゲーションメタデータファイルも生成される。決して手で編集しないこと。 - 継続的インテグレーションは厳格モードで再生成し、差分があれば失敗する。したがって、提出前にローカルで再生成しておく。 ## 書式はリンターとフォーマッターに任せ、コメントは最小限にする リポジトリのリンターを実行し、機械的な問題にはその自動修正モードを用いる。フォーマットはフォーマッターが所有する。手でフォーマットを整えてはならず、レビューで空白やフォーマットの些末な指摘を持ち出してもならない。 リアクティブリソースはフレームワークのフックの上に構築されるため、依存配列とフックの順序に関する規則はリンター標準のフック向けルールによって強制される。それらのルールが関数本体を解析できるようにするには、フレームワークのツールがフックとして認識できる名前を関数に付ける必要がある。すなわち、リソースをフック接頭辞付きの関数へ切り出してそれをリソースのファクトリーに渡し、ルート生成のヘルパーにはアロー関数ではなく名前付き関数式を与える。 コードコメントは既定でゼロとする。コードを言い換えただけのコメント、経緯を記録しただけのコメント、現在の変更要求を指し示すコメントは削除する。隠れた不変条件、自明でない制約、上流の回避策など、将来の読み手がコードそのものからは復元できない理由を記録する場合にのみコメントを残し、中立的な平叙文で書く。 ## テストはモジュールの隣に置き、スナップショットを使わない テストは対象モジュールの隣に置き、パッケージ名経由ではなく相対パスでインポートする。これにより、テストは常に解決済みのビルド成果物ではなくローカルのソースを検証する。 - コンバーターを双方向で、リデューサーまたはコントローラーを、そして各アクセサーフックを、それぞれ独立したテストファイルで網羅する。 - モックはテストランナーの巻き上げ対応モックヘルパーで構築し、常に元のモジュールをモックへ展開して、手を加えていないエクスポートが本来の挙動を保つようにする。 - スナップショットによるアサーションは用いない。重要な具体的な値に対してアサートし、失敗時に実際の退行が特定できるようにする。 ## 変更は1つの目的に絞り、説明と再現手順を付けて出す - 1つの変更要求につき論点は1つ。大きな変更や複数の目的を持つ変更は、それぞれがレビュー可能な小ささで、単独の是非で承認でき、退行したときにきれいに差し戻せるよう、別々の提出へ分割する。 - 定型の穴埋めではなく、実質的な説明を書く。変更が何であるか、なぜ必要か、どのように行ったかを述べ、レビュアーが差分を逆解析せずとも十分な文脈を得られるようにする。タイトルは変更を1行で示し、トレードオフや逸脱は本文に記す。 - 既存のリポジトリの慣行から逸脱する場合は理由を示す。コードベースがすでにある方法で問題を解決しているなら、それに従う。誰かが意図してその方法を選んだのだから、異なる方法には個人的な好みではなく文書化された理由が必要である。 - 無関係な変更を差分に含めない。ついでの削除、リファクタリング、再フォーマット、その場の思いつきによる追加は、本来の変更を埋もれさせ、レビューを遅らせる。別途提出するか、指摘するだけが目的ならイシューを立てる。 - 些細でない機能変更の前にはイシューを立てる。一段落あれば、多くの時間を費やす前に、その方向性が望まれているかをメンテナーが確認できる。誤字修正や小さなドキュメント修正といった些細な修正にイシューは不要である。 - 最小限の再現手順を添える。すべての不具合報告および修正には、当該バージョンちょうどで実行可能な再現が必要である。メンテナーが報告された挙動を再現できない場合、その報告はクローズされる。 - 修正が根本原因に対処していることを確認する。変更を適用すると報告された不具合が消え、外すと再発することを検証し、その仕組みを説明する。値の差し替え、機能の削除、型の緩和によって症状を覆い隠すだけの変更は却下される。 - ドキュメントと生成リファレンスを、同じ提出の中でコードと歩調を合わせて更新する。リファレンスを再生成し、その変更によって記述が成り立たなくなるページをすべて更新する。自動化は、APIが黙って消えたり後続の変更へ先送りされたりすることを許すのではなく、ドキュメントの乖離があれば失敗させるべきである。 ## フレームワークの機能の作り直しなど、レビューで断られる変更をしない - フレームワークがすでに提供しているものの再発明。ランタイム状態、リアクティビティ、その他の下回りを再実装するのではなく、共有のプリミティブの上に構築する。並行する仕組みはコードベースの他の部分から乖離し、全員に保守コストを負わせる。 - 重量級またはオープンソースでない依存の安易な追加。商用ライセンスのライブラリはオープンソースプロジェクトでは使用できない。また、重量級の依存や新しいビルド手順はすべての貢献者に影響するため、正当化とメンテナーの承認が必要である。 - 既存のものと重複する新しい例示アプリケーションの出荷。まず既存の例を確認する。すでにその統合を扱う例があるなら、ほぼ同じものを追加しても得るものはなく、メンテナーから依頼があった場合を除き、個人の別リポジトリに置くべきである。 - ユーザー体験の退行を持ち込んで不具合を修正すること。描画やちらつきの不具合を覆い隠すために機能を無効化したり、アニメーションを削ったり、インターフェースを緩めたりするのは、元の報告が正当であっても受け入れられる修正ではない。 - ツールチェーンがすでに強制している防御的チェックの追加。最も厳格なコンパイラー設定のもとでは、オプショナルプロパティの厳密なチェックを含め、null許容性やオプショナルプロパティのガードは冗長である。 - マージのために必須チェックを上書きすること。チェック一覧のすべての行が成功または明示的にスキップと表示されるまで、強制的にマージしてはならない。成功およびスキップの行を除外して何も残らないことを確認すること。過去に一覧が途中で切れて失敗したチェックが隠れたことがあるためである。リポジトリ固有のチェックが失敗中または保留中の場合は、上書きせずに解消する。 ## 同じ作業ツリーで、ほかのエージェントの変更を上書きしない 他の自動化エージェントや人間が、同じ作業ツリーを同時に編集していると想定する。編集前に現在の作業ツリーの状態を確認し、自分が加えたのではない変更を上書きしないようにする。同時進行の作業をきれいに切り分けられるよう、各変更の範囲は厳密に絞る。 生のバージョン管理コマンドが適切だと決めつけず、プロジェクトのバージョン管理ワークフローを尊重する。作業ブランチから、そのプロジェクトがバージョン管理の上に重ねたワークフローツールで管理されていると分かる場合は、明示的に依頼されない限り、直接のコマンドでブランチを作成したり、ファイルをステージしたり、コミットしたり、履歴を書き換えたりしてはならない。