mcpField
W
write-discoverable-code

write-discoverable-code

github.com/modem-dev開発59222 回インストール14 週間前に更新
リポジトリ

write-discoverable-code について

write-discoverable-codeは、検索でコードを見つけて理解しやすくするためのコーディングエージェント向けスキルです。命名、型、ドキュメント、ファイル構成、コミット前の確認項目を扱います。

主な機能

  1. 01検索しやすいシンボル名
  2. 02ブランド型IDと明示的な状態型
  3. 03検索可能なエクスポート文書
  4. 04ドメイン単位のファイル構成
  5. 05テストの併置と非推奨マーカー

活用シーン

  1. 01新しいエクスポート関数の命名
  2. 02コード検索を意識したモジュール構成
  3. 03コミット前のコード確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/modem-dev/skills --skill write-discoverable-code

インストール後、Claude Code で/write-discoverable-codeを入力して読み込まれたか確認します。

詳細

検索で見つけやすいコードを書く

コーディングエージェントは、文字列を検索し、ヒットした箇所の前後を少し読んでコードを見つけます。ホバーテキストも、定義へのジャンプも、セッションをまたぐ記憶もありません。これらのルールに従えば、5回ではなく1回の検索でコードを特定できます。

1. 名前は検索クエリです

  • エクスポートするシンボルには、2〜4語の名前を付け、そのうち少なくとも1語はドメインを表す語にします。 diff ではなく diffUserObjects、queue ではなく queueEventForDispatch とします。 約70万行のモノレポで測定したところ、1語のエクスポート名がリポジトリ全体で一意である割合は61%、3語の名前は96%、4語以上では98%でした。曲線の変曲点は3語です。grepで一意に見つかる最短の名前を使い、残りはドキュメントコメントに記載してください。
  • 汎用的な動詞には対象を付けます。 sanitize ではなく sanitizeEmailHtml、validateConfig ではなく validateSmtpConfig とします。一意性を確保するために必要な範囲だけ具体化し、それ以上は付けません。
  • シンボルの定義箇所は1つにします。 関数をファイル間でコピーしてはいけません。移動する場合は、同じ変更で元の定義を削除してください。共有ヘルパーは概念を表す名前の場所に1つだけ置き、それ以外の場所からはインポートします。
  • 汎用的な名前を区別するためにモジュールのパスに頼ってはいけません。 users/diff.ts と orders/diff.ts を区別するインポートはファイルの先頭にありますが、検索ヒットは300行目にあるかもしれません。コンテキストはフォルダーではなく、シンボル名(formatDurationMs など)に含めてください。例外:パス自体が意味を持つ、厳格で絶対的な規約がある場合(たとえば、すべてのコントラクトファイルが Input/Output をエクスポートする場合)。
  • 1つの概念には1つの表記を使います。 organizationId か orgId のどちらかを選び、どこでも統一してください。同義語を使うと、今後の検索がそのたびに分散します。類義語に近い別の表現を導入するのではなく、編集対象のコードベースにある語彙を再利用してください。
  • 動作や対象読者が変わったら、同じコミットで名前も変更します。 古い名前は開封率100%の誤情報です。これは可視性を示す印にも当てはまります。他のモジュールからインポートされるようになった _private ヘルパーには、公開名が必要です。
  • ファイル名も名前です。役割だけを表すファイル名は使わないでください。 config.ts、types.ts、utils.ts、helpers.ts、handlers.ts は、検索結果だけでは意味がわからず、リポジトリ内のほかのすべてのモジュールにある config/types/utils と名前が衝突します。ドメインを先頭に付け、config.ts ではなく billing-plan-config.ts としてください。(薄い再エクスポート用エントリーポイントとしてのみ、index.ts は許容されます。)

2. 型はエージェントが読み飛ばせないドキュメントです

  • プリミティブなIDにはブランド型を付けます。 z.string().brand<'UserId'>()(TS)または newtype(Rust)を使います。transferOwnership(userId: string, orgId: string) というシグネチャでは、引数を入れ替えても気付けません。ブランド型を使えば、概念名が示されるコンパイルエラーになります。
  • 権限が必要な操作には、ケイパビリティトークン型のパラメーターを使います(たとえば、生の接続ではなく OrgScopedDb を必須にします)。コメントは依頼ですが、必須の型は物理法則です。
  • 状態は判別可能なユニオンでモデル化し、暗黙のルールを持つnull許容フィールドの寄せ集めは避けます。
  • エージェントがそのまま引用できるように型に名前を付けます。 型名は、エージェントが自己修正に使うコンパイラーエラーで引用されます。OrgScopedDb は名前だけで意味がわかりますが、Ctx2 ではわかりません。any は避けてください。any を使うたびにコンパイラーが黙り込み、エージェントは再び推測することになります。

3. 検索でたどり着く場所に書く

  • すべてのエクスポートに1行のドキュメントコメントを付け、コード自体からは分からない最も重要な制約(単位、タイムゾーン、「挿入時刻ではなくソースの時刻」、所有権など)を記します。名前の定義が検索でたどり着く場所です。その1行がメッセージのすべてです。
  • ドキュメントコメントには、平易な言葉で表現を記します。 検索語は自然言語(「rate limit」「retry delay」など)で入力されますが、camelCase の識別子はフレーズ検索に一致しません。RateLimiter は「rate limit」で検索しても見つかりません。各エクスポートの上にあるドキュメントコメントには、検索する人が使うフレーズを、通常のスペース区切りの言葉で含めてください。たとえば、SessionExpiryChecker には /*_ Checks whether the user session has expired. _/ と記載します。そうすれば、「session expired」や「session has expired」の検索でここにたどり着けます。
  • インポートを読まなくても、モジュールの内容が分かるようにします。 インポートした各名前とそのドキュメント行だけで、読み手がソースモジュールを開かなくても十分に分かるようにします。開かなければならないなら、問題があるのはインポートの名前であり、読み手ではありません。
  • 文字列を分割しないでください。 イベント名、フラグ、エラーコードをテンプレート補間で組み立ててはいけません(`github.${entity}.${action}` のようにすると、github.pr.merged を検索できなくなります)。ループのほうが簡潔に思えても、文字列全体をリテラルで記述してください。
  • エラーメッセージは、一意なリテラルのプレフィックスで始めます。 ログで見つけたメッセージを検索すれば、すぐに throw の箇所にたどり着けるようにします。throw new Error(`Webhook signature mismatch for ${id}`) とし、throw new Error(`${prefix}: mismatch`) とはしません。
  • 1ファイルにつき検索可能な概念を1つにし、オーケストレーターは薄く保ちます。 「X はどこで実行されるのか?」に答えるコードは、コーディネーター、パイプライン、サービスクラスにインラインで書くのではなく、X の名前を付けたモジュールに置きます。名前は内部の仕組みではなく、読み手が知りたい対象に合わせます。オーケストレーターは、適切な名前のモジュールを呼び出す処理の流れとして読めるようにします。読み手が検索からオーケストレーターにたどり着いた場合、どの行からも、本当の実装まで1ステップでたどれるようにします。1つの大きなファイルに複数の概念の実装を埋め込むと、それらのどれを検索しても同じコードの壁に行き着きます。質問単位の概念ごとに名前の付いた置き場所が1つになるまで分割し、そこで止めます。1つの概念の内部でしか意味をなさないヘルパーはインラインに置き、些細な関数ごとにファイルを作って1つの答えを複数回の読み込みに分散させないでください。判定基準は両方向です。無関係な多くの質問に答えるモジュールは、複数の概念を抱えています。
  • テストを同じ場所に配置します(foo.test.ts を foo.ts の隣に置きます)。そうすれば、1回の検索で動作とその仕様を一緒に見つけられます。
  • 行き止まりに印を付けます。 古い経路に @deprecated を付け、新しい経路への参照を記載します。

コミット前の簡単なチェックリスト

  1. 新しいエクスポート名ごとに1回検索するだけで、実装を見つけられますか?
  2. 新しい関数の引数を2つ入れ替えたら、ビルドに失敗しますか?
  3. 呼び出し元が知っておくべきことのうち、シグネチャでは伝えられない唯一の情報(単位、タイムゾーン、所有権、順序など)を、定義のすぐそばに記載していますか?
  4. ログやエラーの文字列はすべて、ソース内にそのまま存在しますか?
  5. 名前を変えずに動作を変更したものはありますか?
  6. コードを移動したとき、元の場所から削除しましたか?

コメント