ousterhout-quality-program
できること
コードの作成やレビュー時に、境界を作成または変更する場合に使用します。新しいモジュール、クラス、コンポーネント、ヘルパー、フック、サービス、ラッパー、共有コードの抽出や集約、再利用可能にしようとする瞬間、またはモジュールの明示的なレビュー、リファクタリング、設計時に使用します。抽象化がその価値を持つかどうかを判断します:モジュールの深さ、設計決定を隠すべきかどうか、重複コードが共有不変条件を守っているか単なる類似か、インターフェースが安定しているかどうか。多くの浅いクラスを生み出す機械的なSOLID/Clean Codeを防ぎます。また、リーダーコストテスト(人間やエージェントが読み書きしやすいコード)と、この基準に既存コードベースをリファクタリングする手順を定義します。
インストールすると、このページがデスクトップ版 AgentsRoom で開きます。アプリが未インストールの場合はダウンロードページに移動します。
SKILL.md
--- name: ousterhout-quality-program description: コードの作成やレビュー時に、境界を作成または変更する場合に使用します。新しいモジュール、クラス、コンポーネント、ヘルパー、フック、サービス、ラッパー、共有コードの抽出や集約、再利用可能にしようとする瞬間、またはモジュールの明示的なレビュー、リファクタリング、設計時に使用します。抽象化がその価値を持つかどうかを判断します:モジュールの深さ、設計決定を隠すべきかどうか、重複コードが共有不変条件を守っているか単なる類似か、インターフェースが安定しているかどうか。多くの浅いクラスを生み出す機械的なSOLID/Clean Codeを防ぎます。また、リーダーコストテスト(人間やエージェントが読み書きしやすいコード)と、この基準に既存コードベースをリファクタリングする手順を定義します。 --- # Ousterhout Quality Program ## 概要 モジュールの役割は、小さなインターフェースの背後に複雑さを隠すことです。核心となる指標は**深さ**です。深いモジュールは大きな機能をシンプルなインターフェースで提供しますが、浅いモジュールのインターフェースは実装とほぼ同じくらい複雑であり、何の得にもなりません。複雑さとは、変更が予期しなかったコードを理解したり触れたりすることを強いるときに感じるものです。Ousterhoutはその複雑さの原因を2つ挙げています:**依存関係**(Aを変えるとBも変えなければならない)と**不明瞭さ**(重要な情報が明白でないこと)です。 Ousterhoutは良いモジュールが*どのように感じられるか*を教えてくれます。これは、境界がどこにあるべきか、そして安全にそこへ向かう方法を示す他のいくつかの視点と組み合わせると最も効果的です。このスキルはその組み合わせた視点です。 ## レビューが実際に間違うところ このスキルが修正するために存在する2つの失敗は、エージェントが書いたコードで繰り返し観察されるもので、**判断(分割すべきか否か)自体ではなく、修正方法にあります**: 1. **浅い修正。** 6つの`as unknown as`キャストがある場合、支援なしのレビュアーはそれらを1つの汎用的な`castRows<T>()`ヘルパーにまとめます—見た目はすっきりしますが、不明瞭さは残ります。深い修正は、型付きの行→ドメインマッパーをテストとともに最初に固定することです(Parnasの教え:キャストは境界が欠けている臭いであり、Beckの教え:移動する前にマッピングを証明する)。臭いを整えることは、それを取り除くことではありません。 2. **反射的な抽出。** 同じ更新ロジックが3つの兄弟コンポーネントで繰り返されている場合、支援なしのレビュアーは「共有ヘルパーを抽出せよ」と言います—DRYの反射的反応です。このプログラムのルールはMetzを拡張したもので、「3回目の類似が出るまで待て」というものです—コードが共有ルールを守っているときに中央集約し、単に似ているだけのときはしないでください。 修正を推奨するときは、両方の視点で検証してください:それは不明瞭さを取り除いているか、それとも単に移動させているだけか?抽出は不変条件を守っているか、それとも単に形を重複排除しているだけか? ## 比例性ゲート 変更が新しいエクスポート名やインポート可能な名前を追加せず、新しいモジュール/クラス/コンポーネント/ヘルパー/フック/サービス/ラッパーを作成せず、中央集約もしない場合はこの視点をスキップしてください。純粋な名前変更、機械的なコードモッド、設定やデータの編集、1行の修正は免除されます。迷ったら、2つのコアテスト(深さ、不変条件)のみを実行してそこで止めてください。 ## ルール全文 生成またはレビューされたすべてのコードは、タスクが完了と呼ばれる前にOusterhoutの視点を通過しなければなりません—明示的な設計レビューだけでなく—比例性ゲート以下の変更(新しい境界なし、中央集約なし:名前変更、コードモッド、設定編集)は除きます。2つのテスト:(1)**深さ**—新しいインターフェースは公開する以上に大幅に隠す必要があります。包むものと同じくらい複雑なインターフェースは何の価値もありません。(2)**不変条件**—共有コードは共有ルールを守る場合にのみ抽出し、3箇所が似ているからという理由で抽出してはいけません;修正は不明瞭さを取り除き、単に移動させてはいけません(6つのキャストを1つのヘルパーにまとめても6つのキャストのままです)。変更が境界を作成または再形成するときは、まず確立された製品がこの形状と規模の問題をどのように解決しているかを見つけ、明確な理由がない限りその慣習を採用し(トレーニングで思い出したパターンは主張であって情報源ではありません)、その後以下のチェックを実行してください。 ## 使うべき時 - 新しいクラス/関数/フックがそのインターフェースに見合う価値があるか、単なる浅いパススルーかを判断するとき。 - ファイルがサイズの閾値を超え、単に分割すべきかではなく*どのように*分割すべきかを決めるとき。 - 繰り返されるコードが共有ヘルパーの抽出を誘惑するとき。 - ビジネスルールの境界(認可スコープチェック、金銭/丸めルール、状態遷移ガード、データ保持ルール)を設計またはレビューするとき。 - インターフェースがパラメータや特別なケースを増やそうとしているとき。 - 既存のコードベースをこの基準に合わせるとき—以下の「既存コードベースをこの基準にリファクタリングする」を参照。 **対象外:** 些細な機械的編集や、プロジェクトの規約がすでに構造を決めている場合—上記の比例性ゲートを参照。外科的変更の規律には`karpathy-guidelines`に従い、リファクタの安全網としてテスト駆動開発スキルを利用してください。 ## 視点 各視点は正確に1つの質問を追加します。Ousterhoutが軸であり、他はその盲点を補正します。 | レンズ | それが追加する問い | いつそれが優先されるか | |---|---|---| | **Ousterhout** — 深いモジュール | このインターフェースは公開する以上に隠しているか? | デフォルトのスパイン。 | | **Parnas** — 情報隠蔽 | このモジュールはどの設計決定(変わる可能性が高い)を隠しているか? | モジュールが深くあるべき*理由*。変わるものを隠していなければ、深さは見せかけに過ぎない。 | | **Brooks** — 本質的複雑さ vs 偶発的複雑さ | これは偶発的複雑さを取り除いているか、それとも本質的なドメインの複雑さを単に移動させているだけか? | 混乱を縮小せずに移動させる「リファクタ」を排除する。 | | **Evans** — ドメイン駆動設計 | この境界はドメイン言語で名付けられているか、一般的なユーティリティ言語か? | `utils`/`helpers`をリネームし、このリポジトリが実際に持つ不変条件に基づいて境界に名前を付ける。 | | **Fowler** — リファクタリング / 悪臭 | より深い設計に向けた最小の安全な一歩は何か? | 「もっと深くあるべき」を、テストが通る具体的なステップに変える。 | | **Beck** — シンプル設計、テストファースト | 深い継ぎ目にする前に現在の振る舞いを証明したか? | 早すぎるアーキテクチャ設計へのブレーキ。まず動作させテストし、それから適切な継ぎ目を深くする。 | | **Hickey** — シンプル vs イージー | 関連のない概念を混ぜているか、それとも本当に一つの概念か? | 浅いヘルパーは通常*イージー*(近くて速い)であって、*シンプル*(少ない概念の混在)ではない。シンプルを優先する。 | | **Metz** — 間違った抽象より重複を選ぶ | この繰り返しコードは共有不変条件を守っているか、それとも単に似ているだけか(このプログラムのルール、Metzを拡張)? | Metz: 間違った抽象より重複の方が安い — 間違った抽象は元に戻すべきで曲げてはいけない。このプログラムは彼女を拡張し、繰り返しだから中央集権化するな;本当に不変条件を守る時だけ中央集権化せよ。不変条件が明らかになるまで重複を許容せよ。 | | **Hyrumの法則** — 観測可能な振る舞い | 呼び出し元はこのインターフェースの契約を超えた振る舞いに依存するか? | 小さく安定したインターフェースを推奨:観測可能な振る舞いは最終的に負荷を支えるものになる。 | ## 組み合わせのレシピ この順で適用する — 後のレンズは前のレンズが通った場合にのみ意味を持つ: 1. **Metz — 入場ゲート。** この境界/抽象は存在価値があるか?このプログラムのルール、Metzを拡張:コードが共有ルールを守る場合にのみ抽出する — 3つの似たものは不変条件の証明ではない。違うならここで止める。 2. **Parnas / Ousterhout** — 変わりやすい決定(認可スコープ、丸めルール、遷移ガード、保持ルール)を深いモジュールの背後に隠す。 3. **Evans** — そのモジュールをドメイン言語で名付け、`utils`ではない名前にする。 4. **Beck / Fowler** — 既存コードはテストで現在の振る舞いを固定し、小さく安全なステップでリファクタリングする。新規生成コードは現在の振る舞いがないので、意図した振る舞いを定義するテストを書く。 5. **Hickey** — ワークフローが似ているだけで関連のない概念を混ぜるインターフェースは拒否する。 ## 構造的アンチパターン **機械的なSOLID / Clean Codeは浅いモジュールを生む。** 教条的な読み方 — 責任ごとにクラスを分け、すべての関数を抽出し、すべてを小さく保つ — は、インターフェースが本体と同じくらい複雑なクラスの群れを生む。ルールが「分割せよ」と言う時は、どの*決定*を隠すのか(Parnas)、公開する以上に隠しているか(Ousterhout)を問え。変わるものを隠していなければ分割するな。このガードはリファクタ圧力(「これをきれいに」「このファイルは大きすぎる」)の下で最も重要 — 冷静な分析ではレビュアーは既に抵抗し、リファクタ中間で目に見える変化を求められる時に浅いファイルの群れが書かれる。 ## よくある間違い - **サイズだけで分割する。** 400行のクエリモジュールが一つの一貫した決定を隠しているなら、同じ結合を漏らす100行のモジュール4つより深いかもしれない。 - **分割を`helpers`/`utils`と名付ける。** ドメイン言語で名付けられなければ(Evans)、境界はおそらく間違っている。 - **2回目の出現で抽出する。** このプログラムのルール、Metzを拡張:不変条件を待て、3回目の類似まで待て。 - **振る舞いを固定する前に深くする。** Beck: 現在の振る舞いを証明するテストなしに「深くする」リファクタは書き換えに過ぎない。 - **単なる透過をモジュールと数える。** 引数を転送するラッパーはインターフェースを追加し何も隠さない — 定義上浅い。 - **韻を不変条件と誤解する。** 共有不変条件の最良の証拠は共変化:コピーが歴史的に一緒に修正・変更されている(同じバグが2箇所で修正された)。独立して変わる類似は韻であり、重複のままにせよ。 - **悪臭を取り除くのではなく整える。** 6つのキャストを1つの汎用キャストヘルパーにまとめるのは同じ不明瞭さの整理版。深い修正はキャストが覆い隠していた境界に名前を付けること。 ## 読者コスト:第3のテスト 深さと不変条件が境界の存在を決める。読者コストはその周囲のコードが変更しやすいかを決める。次の読者(人間でもエージェントでも)は安全に変更するために読み込む行数に対してコストを払う。エージェントはトークンで支払い、テキスト検索や部分読み、型チェック/テストループでナビゲートするため、同じ欠陥はより高コストになる。問え: - **見つけやすいか?** 概念ごとに一つの名前があり、どこでも同じ綴りで、プレーンテキスト検索で到達可能。欠陥:文字列から組み立てられた名前、インポートの副作用による配線、定義を隠す再エクスポートの連鎖、1つの概念に2つの名前がある。 - **読者は途中で止められるか?** 契約はファイルの先頭かエクスポートの上にあり、何を約束し、何を隠し、何を決してしないかが示されている。欠陥:契約は本文を読まなければ導き出せない。 - **機械的に検証可能か?** 境界の入出力に正確な型があり、型チェックが呼び出し元の読み取りに代わる。欠陥:`any`、素の辞書、意味が本文にあるブールフラグ。 - **結合が見えるか?** 一緒に変更しなければならない箇所は(共有型、テスト、単一のソース)で強制されているか、そうでなければ両方の場所にマークされている。隠れた結合の証拠は、コードのどこにも言及がないのに履歴で共変化していること。 - **ノイズがないか?** コードを言い換えたコメント、コメントアウトされたコード、死んだ分岐、変更履歴コメント、置き換えられた古いパスが隣に残っていることはない。 - **予測可能か?** レイアウトはリポジトリの既存パターンに従い、テストは読者が探す場所にあり、自動で実行される。 ファイルサイズは意図的に含めていない。非常に大きなファイルは隠れた別の決定を探す理由であり、切り捨てる理由ではない:読者は範囲を検索・読取でき、何も隠さない分割は負荷を減らすことなくインターフェースを追加する。 コード内マーカーとリポジトリのコードマップには、利用可能なら`context-audit`を使う:その`AIDEV-NOTE:`アンカー(一つの回復不能な事実と出所参照、最大2行、現場にある)は強制できない結合の慣例である。 ## 既存コードベースをこの基準にリファクタリングする レトロフィットは新規コードと同じ基準で評価される;違いは順序と節度である。コードベースの大部分はそのままにすべき。 1. **調査、読み取り専用。** 境界(モジュール、サービス、共有ヘルパー)をリストアップ。各レコードについて:隠された決定、または「なし」;インターフェースサイズと本文の比;履歴からの共変化パートナー;読者コストの欠陥。まだ何も変更しない。 2. **醜さではなく変動率で順位付け。** 優先度はコードの変更頻度×読み取りコスト。動いている冷たいコードは浅くてもそのまま。重要なドメインの複雑さはそのまま(Brooks)。 3. **発見ごとに一つの対処を割り当てる:** - 何も隠さないパススルーレイヤーやラッパー:削除し、呼び出し元は元のものを使う; - フラグや特例で曲げられた誤った抽象:元に戻して(Metz)、本当の不変条件を探す; - 一つの決定を共有する浅い兄弟:一つのインターフェースの背後に統合; - 漏れた決定(呼び出し元がフォーマット、ルール、スキーマを知っている):所有モジュールに引き下げる; - 汎用名(`utils`、`helpers`、`manager`):隠された決定に合わせて名前を変えるか、呼び出し元に溶かす; - 型なし境界:型を付け、キャストをそれが隠していたマッパーに置き換える; - 隠れた結合:強制するか両方の場所にマーク; - ノイズ:削除。 独立して変わる韻は対処しない。 4. **まず振る舞いを固定。** 対処は触るコードの現在の振る舞いをテストが証明するまで始めない(Beck)。リファクタは振る舞いを保持し、振る舞い変更は別コミット。 5. **作業を一人のエージェントが単独で終えられる単位に分割。** 単位ごとに一つの境界。各単位は所有ファイル、守るべき契約、単独で証明するコマンドを名付ける。二つの同時単位が同じファイルを書かない;共有ファイル(バレル、レジストリ、ルートテーブル)は単一所有者か統合待ち。複数単位が依存するインターフェース変更は最初に単独単位として着地。 6. **結果を測定。** 代表的な変更を選び、開始前後で読者が読み込むファイル数と行数を数える。エクスポート名と総行数は減るか維持。インターフェースを追加するリファクタは理由を明示。 7. **停止** 残りが冷たく、重要で、韻であるとき。 関連スキル(利用可能なら):`repo-review`(設計タイプ)は調査を助言用成果物として出す;`design-cleanup`は偶発的複雑さの修正と再調査ループを実行;`context-audit`はアンカーとコードマップを追加;`ousterhout-build-deep`は単位を行うエージェントの著者時チェックリスト。 ## 位置づけ このスキルはレビューと判断の層:抽象が深く、正しい決定に名前が付けられ、抽出に値するかを決めるために使う。`find-shared-code`は共有に値するコードを最近の履歴から掃く際の入場テストとして使う。以下の付録は各著者の理由付けを示す。 --- ## 付録:レンズの詳細 各著者が捉える失敗モードと与える一手。上の表はクイックリファレンス;こちらはその背後の理由付け。 ### Ousterhout — 深いモジュール(背骨) *ソフトウェア設計の哲学.* - **深さ** = 利益(隠された機能)÷ コスト(インターフェースの複雑さ)。深いモジュールは少ないもので多くを隠す。浅いモジュールのインターフェースは本文とほぼ同じ複雑さで、利益はない。 - **複雑さ** は理解や修正を難しくするシステムの何か。二つの源泉: - **依存関係** — 一部を変えると他も触らなければならない。 - **不明瞭さ** — 重要な情報がコードから明らかでない。 - **症状:** 変更の増幅(一つの決定で多くの編集)、認知負荷(頭に保持すべき量)、未知の未知(どのコードに影響があるか分からない)。 - **重要な手:** 複雑さを*下方*に引き下げる — モジュールが難しいケースを吸収し、呼び出し元が負わないようにする。設定パラメータやパススルーは複雑さを*上方*に押し上げる;それが浅さ。 捉えるもの:実装を漏らすインターフェース;役に立たないヘルパー。 ### Parnas — 情報隠蔽(深さが重要な理由) *システムをモジュールに分解するための基準について(1972年)。* - **変わりやすい設計上の決定**を中心に分解し、計算のステップごとに分解しない。各モジュールはそのような決定を一つ隠す。 - これは deep module の直接の祖先である。モジュールが deep であるのは、呼び出し元に波及する決定を隠しているからである。 注意点:「何も変わらない」モジュールは深さが見せかけに過ぎない。このインターフェースの背後で呼び出し元が決して見ない変化は何か?答えが「何もない」なら、その境界は飾りである。 ### Brooks — 本質的複雑さと偶発的複雑さ *No Silver Bullet.* - **本質的**複雑さはドメインに固有のもの(評価は本当にこれほど複雑である)。**偶発的**複雑さはツールや構造が課すものである。 - 除去できるのは偶発的複雑さだけである。ドメインの本質的複雑さをあるファイルから別のファイルに移動して「整理」しただけのリファクタは何もしていない。 注意点:単なる入れ替えを簡略化と偽る。全体の複雑さが減ったのか、それとも単に移動しただけかを問う。 ### Evans — ドメイン駆動設計 *Domain-Driven Design.* - 境界はドメインの**ユビキタス言語**で命名すべきであり、一般的なユーティリティ用語ではない。`helpers`というモジュールは何も命名していないが、`AccessScope`や`PricingPolicy`というモジュールは不変条件を命名している。 - 境界づけられたコンテキストはビジネスの不変条件が境界を越えて漏れるのを防ぐ。 注意点:正しい分解でも意味のない名前。ドメイン言語でモジュールに名前を付けられなければ、境界の切り方が間違っている可能性が高い。 ### Fowler — リファクタリングとコードスメル *Refactoring.* - 現行設計からより深い設計へ移行するための具体的で安全な名前付き操作(関数抽出、フィールド移動、条件分岐をポリモーフィズムに置換など)を提供する。 - すべての操作は振る舞いを保持し、小さくて可逆的である。 注意点:「もっと深くあるべき」と「次のコミットが何かを知る」間のギャップ。Ousterhout が目標を設定し、Fowler が道筋を示す。 ### Beck — シンプル設計、テストファースト *Test-Driven Development; XP.* - Beck の公表順に従うシンプル設計の4つのルール:テストに合格、重複なし、意図を明示、最小要素数。このプログラムは後の Fowler/Haines の順序変更(意図を重複より先に)に従う。これはこのプログラムの Metz 拡張不変条件ルール(下記 Metz 参照)に合致する:重複に対しては、それが守る意図を名前で表せるまで手を付けない。 - テストファーストは早すぎるアーキテクチャ構築にブレーキをかける。まず動作させて振る舞いを証明し、その後テストが守る境界を深める。 注意点:振る舞いが確定する前にアーキテクチャを構築すること。現在の振る舞いを証明するテストなしに「深める」リファクタは未検証の書き換えである。 ### Hickey — シンプルとイージーの違い *Simple Made Easy.* - **シンプル**=絡み合っていない:一つの概念で、他と混ざっていない(客観的)。 - **イージー**=手近で、馴染みがあり、すぐに使える(あなたにとって相対的)。 - 両者は独立している。浅いヘルパーは通常*イージー*(書きやすく近い)が多いが、無関係な関心事が絡んでいれば*シンプル*ではない。 注意点:利便性を設計と偽る。タイプが速くても絡み合いのない概念を保つ構造を優先する。 ### Metz — 間違った抽象より重複を優先 *"The Wrong Abstraction" (2016).* - 重複は間違った抽象よりはるかに安価である。早すぎる抽象化は将来のすべての呼び出し元に合わない前提を強いる。 - 抽象が間違っていた場合、Metz の対処法はそれをインラインに戻し重複を許容することであり、合わないケースに無理に合わせようとしない。 - **このプログラムのルール(Metz 拡張):コードが繰り返されるからといって中央集権化しない。真の共有不変条件を守るために中央集権化する。** 不変条件が明らかになるまでは重複を許容する。 注意点:過剰な中央集権化—みんなが回避しなければならない浅い共有ヘルパー。これは機械的な「DRYを無条件に守る」ことへのカウンターウェイトである。 ### Hyrumの法則 — 観測可能な振る舞いは契約になる *"With a sufficient number of users, every observable behavior of your system will be depended on by somebody."* - インターフェースが*たまたま*行うこと(順序、タイミング、エラー文言など)は誰かに依存される。したがって公開する表面は文書化した表面より大きい。 - これは Ousterhout の**小さく安定したインターフェース**を好む理由を支持する:公開を減らせば偶発的に負荷を負う部分も減る。 注意点:広いインターフェースは硬直化する。余計な観測可能なものは将来の制約になる。 ### それらの関係性 - **Parnas → Ousterhout:** 変わりやすい決定を隠す → モジュールは deep になる。 - **Brooks:** 深さが複雑さを減らしたことを確認する。 - **Evans:** 境界をドメイン言語で命名する。 - **Beck → Fowler:** 振る舞いを確定し、小さく安全な操作でリファクタリングする。 - **Metz:** 不変条件が真実になるまで中央集権化を控える。 - **Hickey:** インターフェースは一つの概念に保つ。 - **Hyrum:** インターフェースを小さく保ち安定させる。 危険なのは Ousterhout と SOLID や Clean Code の機械的な読みを混ぜること。これにより浅いインターフェースの小さなクラスや関数が多数生まれ、deep module の正反対になる。Ousterhout は Metz をカウンターウェイトとして、これに対する解毒剤である。
タグ
さらに詳しく
Claude Ads:広告アカウントを監査する Claude Code スキル
Claude Ads は Claude Code 向けのオープンソーススキル。Google、Meta、LinkedIn、TikTok、Amazon 広告など250以上の項目をチェックし、100点満点のスコアと優先順位付きのアクションプランを、わずか10分ほどで出力します。インストール方法、コマンド、限界、そして AgentsRoom での使いこなし方まで解説。
AGENTS.md: すべてのコーディングエージェントに効く唯一のコンテキストファイル(Codex、Antigravity、Claude)
AGENTS.md は、AI コーディングエージェントがコードに触れる前に読み込む、ポータブルな指示ファイルです。何を書くべきか、CLAUDE.md との違い、そして Codex、Antigravity、Claude をまたいで単一のコンテキストを保つ方法を解説します。
AgentsRoomをダウンロード
すべてのAIエージェントを、すべてのプロジェクトで、ひとつのウィンドウから実行。
コンパニオンアプリ:外出先でもエージェントを確認
Claude、Codex、Antigravity CLI、またはその他の AI プロバイダーを使用します。
バグや要望を公開バックログに直接送信できます。