software-design-philosophy について
モジュール設計、API レビュー、リファクタリング、アーキテクチャ判断で複雑さを管理するためのソフトウェア設計フレームワークです。深いモジュール、情報隠蔽、戦略的なプログラミングなどの原則で、設計が理解や変更に与える影響を評価します。
主な機能
- 01複雑さの診断と評価
- 02深いモジュールと単純なインターフェース
- 03情報漏洩の検出と削減
- 04汎用性と現在の要件の両立
- 05コメントによる設計意図の記録
- 06日々の開発へのリファクタリング導入
活用シーン
- 01モジュールや API 設計のレビュー
- 02理解しにくいコードのリファクタリング
- 03モジュール間の情報漏洩の特定
- 04複雑さの観点からの設計判断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wondelai/ skills --skill software-design-philosophyインストール後、Claude Code で/software-design-philosophyを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/comments-as-design.mdcomplexity-symptoms.mddeep-modules.mdgeneral-vs-special.mdinformation-hiding.mdstrategic-programming.md詳細
ソフトウェア設計の哲学フレームワーク
ソフトウェアエンジニアリングの根本的な課題である複雑さを管理するための実践的なフレームワークです。モジュールの設計、API のレビュー、コードのリファクタリング、アーキテクチャ上の意思決定に関する助言の際に、これらの原則を適用してください。
基本原則
ソフトウェアを書くうえで最大の制約となるのは、作成しているシステムを理解する能力です。 複雑さは敵です。システムを理解しにくく、変更しにくくし、バグの原因になります。設計上の判断はすべて、「これはシステム全体の複雑さを増やすのか、減らすのか?」と問いかけて評価してください。目標は複雑さをゼロにすることではなく、不要な複雑さを最小限に抑え、必要な複雑さを管理できる場所に集約することです。
スコア評価
目標:10/10。 設計をレビューまたは作成する際は、8つのクイック診断項目のうち満たしている項目数を数えてスコアを付け(各項目≈1.25ポイント)、その後、次の基準に照らして妥当性を確認してください。
- 9-10 — 実装よりもはるかにシンプルなインターフェースを持つ深いモジュールであり、情報漏えいがありません(実装を変更しても呼び出し側に手を加える必要がありません)。インターフェースのコメントには設計意図が記されており、設計の改善が日常的に行われています。8つすべての診断項目に合格しています。
- 6-8 — おおむね深いモジュールですが、情報漏えい、浅いクラス、または文書化されていない抽象化が1、2件あります。5〜6項目の診断に合格しています。
- 3-5 — クラスの乱立や時間的な分解があり、情報漏えいが繰り返し発生し、コメントがコードの内容を言い換えるだけになっています。2〜4項目の診断に合格しています。
- ≤2 — 場当たり的なコードです。浅いモジュール、広範な情報漏えいがあり、設計意図が記録されていません。0〜1項目の診断に合格しています。
現在のスコア、不合格となった診断項目、および各項目を10/10にするために必要な具体的な変更を必ず明記してください。
ソフトウェア設計フレームワーク
複雑さを管理し、理解しやすく変更しやすいシステムを作るための6つの原則です。
1. 複雑さとその原因
基本概念: 複雑さとは、システムの構造のうち、理解や変更を難しくするあらゆる要素です。複雑さには、変更の波及、認知負荷、未知の未知という3つの症状があり、依存関係と不明瞭さという2つの原因があります。
主なポイント:
- 変更の波及:単純な変更に、多くの箇所の編集が必要になります
- 認知負荷:開発者が変更を行うために、多くのことを頭にとどめておかなければなりません
- 未知の未知:何を変更する必要があるのか、どの情報が関連するのかが明らかではありません。最も深刻な症状です
- 複雑さは少しずつ増していきます。何百もの小さな判断が積み重なって生じるため(「千の小さな傷による死」)、一つひとつの判断が重要です
コードへの適用:
| コンテキスト | パターン | 例 |
|---|---|---|
| 変更の波及 | 共有する知識を一元化する | 20個のファイルに #ff0000 をハードコードするのではなく、色定数を抽出する |
| 認知負荷 | 開発者が把握すべきことを減らす | バッファサイズ、エンコーディング、ロックモードを指定する代わりに open(path) を使う |
| 未知の未知 | 依存関係を明示する | 型システムとインターフェースにより、変更が影響する範囲を明らかにする |
| 不明瞭さ | 正確な名前を付ける | n ではなく numBytesReceived、delay ではなく retryDelayMs を使う |
問題を修正する前に、コードベースにどの症状があるのかを特定する必要がある場合は、references/complexity-symptoms.md を参照してください。症状ごとの認識テスト、依存関係の分類(構文的/意味的/時間的/隠れた依存関係)、C = Σ(cp·tp) のコスト計算式、および10行の危険信号一覧が記載されています。
2. 深いモジュールと浅いモジュール
中心となる概念: 最良のモジュールは深いものです。シンプルなインターフェースの背後に強力な機能を備えています。浅いモジュールは、提供する機能に対してインターフェースが複雑で、複雑さを隠すどころか増やしてしまいます。
有効な理由: インターフェースは、モジュールがシステムのほかの部分に課すコストであり、実装はその恩恵です。そのため、自分で再実装するより習得が難しいメソッドは、差し引きでマイナスになります。モジュールが存在する価値を持つかどうかを決めるのは、行数ではなく深さです。
主な知見:
- 深さ = 提供する機能 / 課すインターフェースの複雑さ(Unix のファイル I/O は深く、薄い Java I/O ラッパーは浅い)
- 「Classitis」:小さく浅いクラスを作りすぎる病。インターフェースが増えるたびに認知負荷も増します
- メソッドは小さければよいというわけではありません。大きさより深さが重要です
- 最良の抽象化は、少数のシンプルな概念の背後に大きな複雑さを隠します
コードへの適用:
| コンテキスト | パターン | 例 |
|---|---|---|
| 深いモジュール | シンプルな API の背後に複雑さを隠す | file.read(path) はディスクブロック、キャッシュ、バッファリング、エンコーディングを隠します |
| Classitis の解消 | 関連する浅いクラスを統合する | RequestParser + RequestValidator + RequestProcessor → 1 つの RequestHandler |
| インターフェースの単純化 | パラメーターとメソッドを減らす | コンストラクターに 15 個のパラメーターを渡すのではなく、妥当なデフォルト値を備えた config.get(key) を使います |
抽象化にそれだけの価値があるかを判断する際は、references/deep-modules.md を参照してください。深さの比率を示す変更前後のコード、Classitis を解消する具体例、事例(Unix I/O、GC、TCP/IP)を掲載しています。
3. 情報の隠蔽と漏洩
中心となる概念: 各モジュールは、ほかのモジュールが必要としない知識をカプセル化すべきです。1 つの設計判断が複数のモジュールに反映される情報漏洩は、ソフトウェア設計における最も重要な危険信号の 1 つです。
有効な理由: 1 つのモジュールにある設計判断は、そのモジュールだけで変更できます。同じ判断が N 個のモジュールに漏れると、1 回の編集が N 回の編集になり、コンパイラーは必要な変更を促してくれません。隠蔽によって、変更の増幅を局所的な変更に戻せます。
主な知見:
- 時間による分解は情報漏洩を引き起こします。コードを処理が行われるタイミングで分割すると、各段階で知識を共有せざるを得なくなります。代わりに、知識ごとに整理してください
- データ形式、プロトコル、共有された前提を通じた裏口からの漏洩は、最も見つけにくい形です
- デコレーターは、デコレート対象のインターフェースを公開するため、情報漏洩を起こしがちです
- 2 つのモジュールが知識を共有しているなら、それらを統合するか、その知識をカプセル化する新しいモジュールを作成してください
コードへの適用:
| コンテキスト | パターン | 例 |
|---|---|---|
| 形式の漏洩 | シリアライズを一元化する | どこでも json.dumps を呼ぶのではなく、1 つのモジュールが JSON のエンコードとデコードを担います |
| 時間による分解 | 時間ではなく知識ごとに整理する | 「設定の読み込み」と「設定の適用」を 1 つの設定モジュールにまとめます |
| プロトコルの漏洩 | 転送の詳細を抽象化する | MessageBus.send(event) は HTTP、gRPC、キューの違いを隠します |
変更のたびに 2 つのモジュールを連動して編集する必要がある場合は、references/information-hiding.md を参照してください。コードを使った 4 種類の漏洩(インターフェース、裏口、時間、デコレーター)、5 つの削減戦略、HTTP 処理の事例、検出テーブルを掲載しています。
4. 汎用モジュールと専用モジュール
中核となる考え方: 「ある程度汎用的」なモジュールを設計します。複数の用途に対応できる十分に汎用的なインターフェースと、現在のニーズに対応する実装を組み合わせます。「現在のニーズをすべて満たす、最もシンプルなインターフェースは何か?」と考えます。
効果がある理由: 意外なことに、汎用的なインターフェースのほうが通常はシンプルです。要件が増えるたびに個別対応のメソッドを追加すると複雑になりますが、汎用的なメソッドならそれらをひとつにまとめられます。反対方向に陥るのが落とし穴です。現在のニーズが求めていない汎用性は、将来の用途が実現するかもわからないのに、今の時点でコストを払う推測的な複雑さです。
主なポイント:
- 「ある程度汎用的」であることが、具体的すぎる設計と汎用的すぎる設計の間にある最適なバランスです
- 複雑さを下位レベルに押し下げます。下位レベルのモジュールで難しいケースを処理し、上位レベルをシンプルに保ちます
- 設定パラメーターは、しばしば判断を避けた結果です。パラメーターが増えるたびに、複雑さが呼び出し側に押し付けられます
- 迷ったら、まずはよりシンプルで汎用的なアプローチを実装します
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| API の汎用性 | ひとつの用途ではなく、概念に合わせて設計する | text.addBulletPoint() ではなく text.insert(position, string) |
| 設定の削減 | 動作を自動的に判定する | encoding パラメーターではなく、ファイルのエンコーディングを自動検出する |
| 過度な特化の回避 | 多数の専用メソッドより、ひとつの汎用メソッドを使う | storeUser()、storeProduct()、storeOrder() ではなく store(key, value, options) |
インターフェースの汎用性をどの程度にするかを決める際は、references/general-vs-special.md を参照してください。「現在のニーズをすべて満たす、最もシンプルなインターフェース」というテスト、設定パラメーターのアンチパターン、複雑さを下位レベルに押し下げる方法について、詳しく解説しています。
5. 設計ドキュメントとしてのコメント
中核となる考え方: コメントでは、コードを見ただけでは明らかでないこと、つまり設計意図、抽象化の根拠、不変条件、前提条件を説明します。低レベルの実装詳細を超える内容について、「良いコードは自己文書化されている」というのは神話です。
効果がある理由: コードに記録できるのは、実行する内容だけです。なぜほかの選択肢ではなくこの方法を選んだのか、あるいは何を暗黙に前提としているのかは記録できません。その根拠はシステムの中でも最も失われやすい情報です。作者の頭の中にしか存在せず、作者が担当を離れた瞬間に消えてしまうため、コメントはそれを記録できる唯一の機会です。
主なポイント:
- コメントには4つの種類があります。インターフェースコメント(最も重要で、抽象化を定義するもの)、データ構造のメンバーコメント、実装コメント、モジュール間コメントです
- コードを書く前にコメントを書くことで(コメント駆動設計)、考えを整理します
- コードから明らかな内容を繰り返さず、コメントは説明対象のコードの近くに置き、コードと一緒に更新します
- コメントを書くのが難しい場合、設計が複雑すぎる可能性があります
コードでの適用例:
| コンテキスト | パターン | 例 |
|---|---|---|
| インターフェースコメント | 実装ではなく、抽象化について説明する | 「位置に最も近いウィジェットを返します。しきい値内に該当するものがない場合は null を返します」 |
| データ構造コメント | 不変条件を説明する | 「リストは優先度の降順でソートされます。同順位の場合は挿入順で並びます」 |
| 実装コメント | 内容ではなく理由を説明する | "// 二分探索:リストは常にソートされており、100k 件以上の項目を保持できる" |
| モジュール間コメント | 関連する判断を結び付ける | "// このタイムアウトは RetryPolicy.java の再試行間隔と一致させる必要がある" |
コメントを書くときやレビューするときに、どのような内容を記載すべきか迷ったら、references/comments-as-design.md を参照してください。4種類のコメントと例、コメント駆動設計の手順、自己文書化コードという神話への反論について解説しています。
6. 戦略的プログラミングと戦術的プログラミング
基本概念: 戦術的プログラミングでは機能をすばやく動作させますが、近道を重ねるたびに複雑さが増していきます。戦略的プログラミングでは、優れた設計に10〜20%余分な労力を投じ、あらゆる変更を構造改善の機会と捉えます。
効果がある理由: 戦術的な速さは借り物です。近道を重ねると、その後の変更が難しくなります。一方、戦略的な投資は積み重なり、戦略的に設計されたシステムは数か月以内に作業しやすくなります。
重要なポイント:
- 戦術の竜巻:すばやくリリースする一方で、後始末を残す開発者。短期的には称賛されても、長期的には破壊的です
- 主な仕事は、たまたま動く優れた設計を作ることであり、たまたま設計がある動作するコードを書くことではありません
- スタートアップこそ、戦略的プログラミングが最も必要です。初期の近道は、チームの成長に伴って深刻な負債へと膨れ上がります
- あらゆる変更は投資の機会です。コードを少しでも良くしましょう。リファクタリングは特別なイベントではなく、すべての機能開発に含まれるものです
コードへの適用:
| コンテキスト | パターン | 例 |
|---|---|---|
| 戦術的な罠 | その場しのぎの応急処置を避ける | 「この特殊なケースだけ」のためにブール型のパラメーターを追加しない |
| 戦略的な投資 | 機能開発中に構造を改善する | 機能の追加と並行して、扱いにくいモジュールのインターフェースをリファクタリングする |
| 設計レビュー | 正しさだけでなく構造も評価する | 「動くか」だけでなく、「システムをよりシンプルにできるか」と尋ねる |
変更にどれほどの設計労力をかけるべきか判断するとき、またはその必要性を説明するときは、references/strategic-programming.md を参照してください。10〜20%の投資の考え方、戦術の竜巻のパターン、そしてスタートアップにこそ戦略的プログラミングが最も必要な理由が説明されています。
よくある間違い
| 間違い | うまくいかない理由 | 対処法 |
|---|---|---|
| 小さなクラスを作りすぎる | クラス炎上は深みのないインターフェースを増やし、境界が増えるたびに認知負荷がかかります | 関連する浅いクラスをまとめ、より深いモジュールにする |
| 処理の順序でモジュールを分割する | 「読み取り、処理、書き込み」という分割では、モジュール間で知識を共有せざるを得ません | 共通の知識を使うコードを1つのモジュールにまとめる |
| インターフェースに実装を露出する | 呼び出し側が内部実装に依存し、変更の影響が広がります | 抽象化を中心にインターフェースを設計し、形式やプロトコルを隠す |
| コメントを任意のものとして扱う | 設計の意図や前提が失われ、新しく参加した人が誤った推測をします | まずインターフェースのコメントを書き、コードとともに保守する |
| あらゆるものに設定パラメーターを用意する | 呼び出し側に委ねたパラメーターは、判断を避けたということです(§4 を参照) | 動作を自動的に決定し、妥当なデフォルト値を用意する |
| その場しのぎの戦術的な応急処置 | 近道が積み重なり、システムが使いものにならなくなります | 10〜20%余分に投資し、あらゆる変更を設計改善の機会と捉える |
| 単なる中継メソッド | 引数を別のメソッドに渡すだけのメソッドは、機能を追加せずインターフェースを増やします | 中継処理を呼び出し側または呼び出し先に統合する |
| 特定のユースケースに合わせた設計 | 特殊用途のインターフェースに特殊ケースが積み重なります | 問いかけましょう。現在のすべてのニーズを満たす、最もシンプルなインターフェースは何でしょうか? |
クイック診断
| 質問 | いいえの場合 | 対応 |
|---|---|---|
| 各モジュールについて、一文で説明できますか? | モジュールの役割が多すぎるか、目的がありません | 一貫性のある、説明可能な責務に分割します |
| インターフェースは実装よりシンプルですか? | モジュールが浅く、複雑さが外部に漏れています | さらに隠蔽し、浅いクラスをより深いクラスに統合します |
| 呼び出し元に影響を与えずに実装を変更できますか? | 境界を越えて情報が漏れています | 漏れている知識を一つのモジュールにカプセル化します |
| インターフェースのコメントは抽象化について説明していますか? | 設計意図が失われ、モジュールが誤用されます | モジュールの仕組みではなく、モジュールが保証する内容を文書化します |
| コードレビューで設計について議論していますか? | レビューでバグは見つかっても、複雑さの増大は見逃されます | レビュー基準に「複雑さを減らせるか?」を追加します |
| 各モジュールは重要な設計判断を隠していますか? | モジュールが情報ではなくコードを中心に構成されています | 各モジュールが特定の知識を担うように再構成します |
| 実装を読まずに、新しいメンバーがモジュールの境界を理解できますか? | 抽象化が文書化されていないか、情報が漏れています | インターフェースのコメントを改善し、インターフェースをシンプルにします |
| 設計改善に時間の10~20%を費やしていますか? | 機能を追加するたびに負債が蓄積します | すべてのPRに設計改善を含めます |
さらに読む
詳細な例を含む完全な方法論については、以下をご覧ください。
- "A Philosophy of Software Design"(John Ousterhout 著、第2版)
著者について
John Ousterhout は、スタンフォード大学の Bosack Lerner コンピューターサイエンス教授であり、Tcl スクリプト言語と Tk ツールキットの開発者です。スタンフォード大学の CS 190 コースをもとに A Philosophy of Software Design を執筆し、数十年にわたるシステム構築の経験を、さまざまな言語や規模に適用できる原則としてまとめました。