maintainable-code について
maintainable-codeは、コードを理解しやすく、変更や引き継ぎがしやすくなるよう支援する開発スキルです。設計より先に調査と検証を重視し、依存関係、境界、凝集性、情報共有の指針を示します。
主な機能
- 01変更前に既存の動作を調査
- 02設計前に技術的な未知を検証
- 03副作用と依存関係を明示
- 04変更軸と凝集性に沿って構成
- 05判断と根拠を共有
活用シーン
- 01変更前に不慣れなコードベースを把握
- 02新しい変更の構造を選定
- 03依存関係とモジュール境界を確認
- 04他者がコードを把握しやすくする
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ gabrielmoreira/ skills --skill maintainable-codeインストール後、Claude Code で/maintainable-codeを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/activation.scenarios.tsderived.scenarios.tsmaintainable-code.scenarios.tsevals/fixtures/changed-one-file-and-something-else-broke/package.jsonevals/fixtures/changed-one-file-and-something-else-broke/src/invoice.jsmoney.jsstatement.jsstatement.test.jsevals/fixtures/maintainable-skip-disposable-script/package.jsonsetup.shevals/fixtures/maintainable-skip-disposable-script/src/CalcTax.jsFetchOrders.jsgetUserData.jsindex.jsindex.test.jsparse_invoice.jsSendEmail.jsevals/fixtures/this-file-is-long-split-it/package.jsonevals/fixtures/this-file-is-long-split-it/src/checkout.js詳細
保守しやすいコード
基本原則。 実際の複雑さを見えるようにし、必要のない複雑さを取り除きます。
- リポジトリで実証されている慣例を最優先します。 これらのデフォルトは、作業の流れ、境界、テスト、または再開しやすさが向上する場合にのみ適用します。
- 最も重要なのは、変更前に調査することと、影響と依存関係を明示することです。 この2つができていれば、残りの8つは適用しやすく、また省略しやすくなります。
- これは、進行中の作業の途中で開いたものです。 これはその作業を進める方法であり、作業に取って代わるものではありません。始める前に何をしていたかを明確にし、終わったらその作業に戻ります。
言語
- ユーザーの言語で返信します。
- リポジトリに永続的に残す内容は、プロジェクトで確立されている言語を使います。
- 慣例がない場合は、明快な国際英語をデフォルトにします。
該当するセクション
すべての行を読んでから、該当する項目に取り組みます。取り消しにくいものを優先します。 以下に10のセクションがありますが、1つのタスクで必要になるのは通常2つ以下です。セクションはすでに読み込まれているため、該当箇所に進むコストはかかりません。10すべてを流し読みして、どこにもたどり着かないことのほうがコストになります。
- セクション1と2は、ほかより先に扱います。 調査し、不明点を実証してから取り組みます。疑問が残ったまま選ぶ構造は、設計を装った推測です。
- セクション3から10は、手順ではなく考慮事項です。 番号はページ上の順序を示すものであり、作業の順序ではありません。
| 次のような場合... | 読むセクション |
|---|---|
| 最後まで読んだことのないコードを変更しようとしている。名前から動作を推測している | §1 変更前に調査する |
| 技術的な不明点が残っているのに構造を選んでいる。実行して確かめず、ドキュメントだけを見てライブラリを選んでいる | §2 観察可能な証拠に基づいて設計する |
| ガード処理に正常系の流れが埋もれている。関数の動作を理解するために2回読まなければならない | §3 主な流れを分かりやすくする |
署名に明示されていない I/O、クロック、Math.random、グローバルに内部からアクセスしている。広範なコンテキストオブジェクトを下位に渡している。開始したものがなくなった後も処理が実行され続ける | §4 影響と依存関係を明示する |
| 共有機構に呼び出し元の名前がハードコードされている。単一の呼び出し箇所のために依存関係が追加されている | §5 機構を汎用的に保つ |
| 変更のまとまりではなく種類ごとにファイルを分類している。1つの機能の編集で6つのフォルダーに手を入れる | §6 変更の軸に沿って整理する |
| ファイル名が内容の半分を表さなくなっている。異なる理由で変更される2つの役割をモジュールが担っている | §7 単位の凝集性を保つ |
| 長いファイルを分割しようとしている。最初のバージョンを動かす前に階層構造を描いている | §8 分割する前にファイルを階層化する |
| 同じものに2つの名前がある。ドメイン独自の言葉があるのに、フレームワークの用語を使っている | §9 安定したドメイン用語を使う |
| 説明を受けなければレビューできない変更である | §10 段階的に伝える |
1. 変更前に調査する
- 手を加える前に、既存の実装をたどります。
- 現在の動作。
- エントリーポイント。
- 依存関係。
- テスト。
- ローカルのパターン。
- 適合するパターンを再利用します。
- 既存のアーキテクチャに並ぶ形で、別のアーキテクチャを作らないでください。
- 既存のアーキテクチャの都合で、自分なら選ばない配置を強いられる場合は、望ましかった配置とともに、そのことを作業中に記録します。 妥協した時点で記録すれば改善項目になりますが、会話の中に残したままでは、作業が完了した後に改善案として提案されることはありません。
- 気づいた類似点をすべて抽象化しないでください。 2つのものが似ているからといって、一緒に変更される証拠にはなりません。
2. 観察可能な証拠をもとに設計する
- 実装する前に、結果をどのように示すかを決めます。
- 主要な経路を描き、筋の通った最小限のコアを構築してから、境界をつなぎます。
- 計画は暫定的なものとして扱います。 実装によって、より適切な契約、データ形式、ユーザー体験が明らかになったら、計画を見直します。
- 不明点が技術的なものであれば、コードの置き場所を決める前に検証します。 独立したプローブであれば、配置を二度検討する手間をかけずに疑問に答えられます。不明点が解消する前に配置を決めるのは、設計を装った推測です。
- 不明点が構造上のものに限られる場合は、配置先はすでに決まっています。 そのため、まず構造を組み替えてから、そこに構築します。
- プローブからコードを活用することはできますが、その位置づけまで引き継いではいけません。 再導出に多大なコストがかかるなら、扱いにくい統合を通じて機能している経路は維持します。コードは残せますが、何も検証できていないという事実は残せません。
3. メインフローを分かりやすく保つ
- 問題が必要とする最小限のレイヤーにします。
- 重要な判断とその背景は、近くにまとめます。
- 二次的な詳細は、適切な名前を付けた単位の背後に移します。
サービス形式のアプリケーションでは、次の構成が有用なデフォルトです。
composition root -> route -> use case -> focused subtask -> connector
- ネイティブな構造のほうが明快な場合は、この骨格を無理に当てはめません。 ライブラリ、フレームワーク主導のアプリケーション、パイプライン、小さなスクリプトには、それぞれに適した構造があります。
4. 副作用と依存関係を明示する
- 次のものは、目に見える境界に置きます。
- ネットワークとファイル。
- プロセスの状態。
- 時刻と乱数。
- 外部サービス。
- 状態を持つ、差し替え可能である、ライフサイクルに影響される、または I/O を行う依存関係は、明示的に渡します。
- パターンに合わせるためだけに、安定した純粋なユーティリティや定数を注入しないでください。
- グローバルや広範なコンテキストオブジェクトは避けます。 これらは、単位が実際に何を必要としているかを隠します。
- 共有コンテキストが意図的な拡張契約である場合は、問題ありません。
- シグネチャで明示されていることと、実行中に把握できることは同じではありません。 依存関係をすべて正しく注入していても、進行中の処理、その開始者、停止方法を把握する手段がシステムにないことがあります。リトライ、サブスクリプション、バックグラウンド更新のように、副作用が開始した呼び出しより長く続く場合は、呼び出し箇所ごとのキャンセル経路を設けるのではなく、全体を一元管理し、状態を確認したり停止を指示したりできる場所を設けます。
- 各モジュールが依存できる方向を明示し、ツールでそれ以外を拒否します。 それぞれが一つの責任を持ち、依存の方向が一つである二つのファイルは、すでに一つのモジュールです。合意ではなく強制する必要がある場合、パッケージも同じ境界になります。
- 人が覚えておくルールより、プロジェクトが実行する循環依存チェックを優先します。 双方向依存には正当な例外がないため、強制するコストが最も低い境界であり、ビルドに組み込む価値のある最初の境界です。
5. メカニズムを汎用的に保ち、依存関係を慎重に選ぶ
- 汎用エンジンに、特定の呼び出し元に属するビジネス上の値を取り込んではいけません。
- 可変部分は、契約、設定、データ、または拡張ポイントに置きます。
- 複雑さを大幅に減らせる場合は、保守されているライブラリを採用します。
- 単純な処理のために依存関係を追加することは避けます。 また、すでに解決済みの難しい問題に対して、自作の代替品を作ることも避けます。
- ライブラリ独自の語彙をアプリケーションの語彙にしたくない場合は、ライブラリをその境界内にとどめます。
6. 変更の軸に沿って構成する
- 製品アプリケーションでは、機能別またはドメイン別のフォルダーを優先します。 振る舞いが分かりやすくなり、関連する変更をまとめられます。
- ライブラリ、インフラストラクチャ、フレームワークのパッケージでは、それが自然な公開境界である場合、技術別または機能別の構成を優先します。
7. 単位のまとまりを保つ
- 関数、クラス、モジュールには、細かな手順を一つだけではなく、一貫した目的が必要です。
- メインの流れを明確にできる箇所で、関心事を切り出します。 パース、検証、遷移、再試行、フォーマット処理などです。
- 関数を短くするために、一つの判断をヘルパーに分散させないでください。
- 小規模で決定的なサポート関数は、原則として純粋関数に保ちます。
- 小規模なエンジンは、ビジネス上の値や隠れた I/O から切り離します。 ステートマシン、スケジューラー、再試行コントローラーなどです。
8. ファイルを分割する前に、レイヤー化する
- 補助ツールは例外です。 作業そのものではなく、作業を支援するスクリプトにレイヤー化の利点はありません。ファイル全体をまとめて読め、移動でき、コピーでき、削除できることが、低コストを保ちます。以下はすべて、構築するシステムに関するものです。
- リポジトリ内で確立されている順序に従います。
- 新聞の比喩。 規約がなければ、メインの流れを先頭に置き、それを支える要素を必要になる順に続けます。最初の画面を読み終えたところで止めた人にも、最も重要な内容が伝わります。
- 領域とは、まだ分割していない区分です。 ファイル単位で考えるモジュラーモノリスの考え方では、境界は物理的なものになる前に論理的なものです。サブユースケース、ポート、それを実装するものは、それぞれ形が固まるまで名前付きのレイヤーとして扱えます。
- まだ正当性を説明できない境界は、いずれ破られます。 形を確かめるための実験そのものが、その形をゆがめます。動作させるためにレイヤーをまたぎ、元に戻すのは現在のタスクではないため修正が先送りされ、次の実験では別の箇所がゆがみます。ゆがみは積み重なりますが、一つひとつは今もアーキテクチャのように見えます。単一ファイルもゆがみますが、それは普通の乱雑さとして目に見え、一度の編集で整えられます。
- 形が見えてきたら、分割する前に、そこにあるものに名前を付けます。 ファイルが実際に担っているもの、つまり判断、状態、遷移、作用、通信、マッピングを列挙します。ファイルが大きくなった行数ではなく、それらの間で分割します。このリストを作らずに分割すると、一つの責務を二つに分けてしまい、どちらの断片も互いなしでは意味をなさなくなります。
- 準備としてのリファクタリングは、推測ではなく、必要になってから行います。 流れとその構成要素が明確になってから分割します。三度目の法則があるのは、三度目の出現で形が見えるからです。それ以前にレイアウトを選ぶのは、設計の衣装を着た推測にすぎません。
- 振る舞いの局所性。 分割とは、読み手が両方の断片を見つけられるという賭けです。エディターを使い、コードベースに何年も携わっている人には通常見返りがありますが、検索でコンテキストを集める読み手には、見返りが得られることは少なく、それを失っても気づきにくいものです。
- クラス炎。 Ousterhout が、浅い単位を大量に生み出す分解を指して名付けた言葉です。それぞれは単純でも、隠された実装よりインターフェースのほうが大きなコストとなるため、システム全体はかえって複雑になります。
- フローチャートの手順ではなく、情報を隠します。 Parnas の基準は、物事の発生順ではなく、それぞれの部分が他の部分から何を隠すかによって分けることです。説明できるサブシステムは一つの単位になりますが、断片ではそうならないことがほとんどです。
- 空行で区切られたグループは、凝集性を確認するきっかけとして扱い、自動的に切り出すルールにはしないでください。
9. 安定したドメイン用語を使う
- ドメインで定着している語彙を再利用します。
- なじみがあり、正確な用語を選びます。
- 関連する操作には対称的な名前を付け、近くにある名前から予測できるようにします。
- スタイルの統一を徹底するために、有効なフレームワークやドメインの用語を別の名前に変えないでください。
10. 段階的に伝える
- まず役立つ回答や決定を示し、次に補足の詳細、最後にリスクを伝えます。
- 回答スタイルに関する詳しいガイダンスは、
skill://progressive-readingを参照してください。
これまでの各セクションをすべて満たしても、合格とはならないテスト
分解がこの10項目をすべて満たしていても、実際に開発している人が途方に暮れることがあります。 このスキルでは、内部からそれを見抜けません。各ルールは単独で確認できます。しかし、その後に重要となる問いを問うものはありません。
- これを書いていない人でも、重要な振る舞いを見つけられますか? 導き出すのでも、図から再構築するのでもなく、見つけられるかどうかです。
- 小さな変更にも、まずコードの調査が必要ですか? 境界が純粋でレイヤー構成が洗練されていても、編集のたびに1週間の読み込みから始まることがあります。
- 答えが「いいえ」なら、構造に問題があります。 上の評価がどれほど高くても関係ありません。案内してもらわないと理解できない洗練さは、保守しやすいとは言えません。内輪向けです。
これらを適用する前に
- この原則が、プロジェクトの規模、フレームワーク、既存の契約に合っているか確認してください。
- 正当な例外は、形式だけの遵守よりも優先されます。
- 理由を明示してください。 そうすれば、次の読者が元に戻してしまうことを防げます。
コメント
Markdown 対応