良いコードについて語るときのプロトコルエラーについて
わりとエンジニアとITエンジニアの話に近い。
暗黙の省略を許してるせいで上手く会話できてなくない?
こういう話がしたい
ハイライトは以下
『Clean Code』 / 『A Philosophy of Software Design』
一般的な意味: それぞれ著名なソフトウェア設計に関する書籍です。『Clean Code』は読みやすく変更しやすいコードを書くための具体的なプラクティスを、『A Philosophy of Software Design』は「複雑さ」を管理するためのより哲学的な設計原則を説いています。
今回の会話での位置づけ: これまでの思索全体を象徴する具体例として登場しました。『Clean Code』はアプリケーションの「煩雑さ」に、『A Philosophy of Software Design』はツール/ライブラリの「困難さ」に、それぞれ主眼を置いて書かれているのではないか、というマッピングがなされました。これは、議論の文脈が異なれば「良いコード」の定義も変わる、という中心的なテーマを補強するものです。
箇条書きによるキーワード整理
-
根本的な問題意識:プロトコルの不一致
- ソフトウェア開発の議論が噛み合わないのは、参加者が異なる「生息地(=文脈)」にいることを自覚・明示しないまま会話するため。
- 各人が立つ前提(プロトコル)が異なれば、建設的な対話は成立しない。
-
全体像:すべては連続的なスペクトラムである
- 以下の対立軸は0か1ではなく、両極端の間に無限のグラデーションが存在する。
-
軸1:ソフトウェアの分類(何を作るか)
-
ツール
- 定義: 単一機能を持つソフトウェア(例: コンパイラ, grep)。
- 関心事: 特定の「困難な」問題を高品質・高性能に解くこと。
- 振る舞い: 外部仕様(RFCなど)で固定されており、実行時の分岐が少ない。
-
アプリケーション
- 定義: ユーザーとの対話で機能を提供するソフトウェア(例: Webサービス)。
- 関心事: 多様なユースケースという「煩雑さ」を解決すること。
- 振る舞い: ユーザーの選択や状況に応じて、実行時に多様な分岐が発生する。
-
ツール
-
軸2:向き合う課題の性質(何と戦うか)
-
困難さ (Complexity)
- プロセス: 一本道を突き進むような、直列したステップの連鎖。
- 要求能力: アルゴリズム的思考、性能の追求。
- 関連領域: ツール開発、コアエンジン開発。
- 役割の比喩: コンポーネントを作る人。
-
煩雑さ (Tediousness)
- プロセス: 多数の分岐路から選択するような、並列的な選択の繰り返し。
- 要求能力: 人間の迂闊さへの耐性、ミスを防ぐ仕組み化。
- 関連領域: アプリケーション開発、特にエンタープライズ領域。
- 役割の比喩: コンポーネントを繋ぐ人。
-
困難さ (Complexity)
-
軸3:「煩雑さ」への設計アプローチ(どう戦うか)
-
汎用性 (Generality) - ブラックボックス的アプローチ
- 方針: 少数の語彙で多くのものを抽象化して表現する。
- 目的: 利用者の認知負荷を下げ、「気にせず使える」ようにする。
- キーワード: 多態性, インターフェイス, デフォルト引数, 標準化された構造。
- コミュニケーションの意図: 「この部分の関心事は、こちらで引き受けておきます」
-
明示性 (Explicitness) - ホワイトボックス的アプローチ
- 方針: 豊富な語彙で明確に区別し、曖昧さを排除する。
- 目的: 利用者に意図的な意思決定を促し、「明確に使い分けてもらう」ようにする。
- キーワード: 具象型, 豊富なオプション, 必須引数, プロジェクト固有の命名。
- コミュニケーションの意図: 「あなたの選択が、システムの振る舞いを決定します」
-
汎用性 (Generality) - ブラックボックス的アプローチ
対話履歴 (読み飛ばしても良いかも)
AI:
思考の整理とタスクの洗い出しをお手伝いします。
「タスクリストというよりは思索」とのこと、承知いたしました。
今、頭の中にあること、気になっていることを自由にお話しください。どんな断片的なことでも構いません。
(返答終了理由: STOP)
ユーザー:
コードの話をしたい。プログラミングの話をしたい。
- ツール
- アプリケーション
- エンタープライズアプリケーション
という文脈で分かれてるものがある気がする。これらを暗黙に省略して語ってるので会話が噛み合わない。
AI:
なるほど、「ツール」「アプリケーション」「エンタープライズアプリケーション」という3つの文脈が混ざることで、話が噛み合わなくなるのですね。
ご自身の中では、この3つをどのように区別されていますか?
あるいは、最近その違いを感じた具体的な場面はありましたか?
(返答終了理由: STOP)
ユーザー:
例えばわかりやすい例で言えばDDDはエンタープライズアプリケーションの文脈。
なぜそうかというと煩雑な分岐と自由度の高いその場限りの分岐が必要になるから。分かりやすく言えば柔軟な仕様変更を要求する。
一方でツールというのは単一の機能を持ったもの。そして分岐や順序は外部に押し付けられるか暗黙に固定されている(rfcなど)。
アプリケーションの場合はその場で何らかのユーザーの選択に基づく分岐を必要とする(例えば認可など)
AI:
ありがとうございます。それぞれの文脈について、具体的なイメージが湧いてきました。
- エンタープライEアプリ: 煩雑・柔軟な仕様変更に対応する必要がある(例: DDD)
- ツール: 単一機能。分岐は外部依存か固定(例: RFC準拠)
- アプリケーション: ユーザーの選択に基づく分岐がある(例: 認可)
という整理ですね。この3つの違いが、特にどのような話をしている時に「噛み合わないな」と感じますか?
(返答終了理由: STOP)
ユーザー:
あ、ごめんなさい。アプリケーションはエンタープライズアプリケーションを内包してます
AI:
承知しました。では、
- ツール: 単一機能。分岐は外部依存か固定。
-
アプリケーション: ユーザーの選択に基づく分岐がある。
- その中でも特に、煩雑・柔軟な仕様変更が求められるものが エンタープライズアプリケーション。
という関係性ですね。訂正ありがとうございます。
この整理をした上で、改めてお伺いします。どのような会話で、この違いが認識されていないと感じますか?
(返答終了理由: STOP)
ユーザー:
ツールは単機能で高品質なモノが求められる。そして複雑な問題を解く。わかりやすい例ではコンパイラ。速さが求められる場合もある。わかりやすい例ではgrep。
一方でアプリケーションは柔軟性と自由度が求められる。複雑さというよりは煩雑さの解決。
似たような機能を複数使い分けるあるいは似たようで異なる問題を個別に解く必要があるのが煩雑さ。例えば個別のidp毎の認証処理の実装は煩雑。
コードのレイアウトにも現れる。例えば関数とクラスを同じものとみなしたとき(例えばpythonで言えば__call__を保持するプロトコルを実装してるオブジェクトと関数を同一視する)。一つの関数であればクラスにする必要がない。1つのクラスが複数の操作(メソッド)を提供する場合にはクラスにする意味が生まれる。これはpylintのルールにあった記憶。そしてこれは高階関数とストラテジーパターンにも対応する。
さてモジュールないしはパッケージと呼ばれるものつまり名前空間を持つオブジェクトもこのクラスから作られるオブジェクトを同一視してみる(importという関数の戻り値のオブジェクトということを考えてみる)。わかりやすい例としてauthモジュールオブジェクトを考えてみる。なぜauth関数ではだめだったのか?理由は複数の機能にアクセスしたかったからということになる。あるいは複数の人が触るコードの場合にはモジュールを分けた方が良い。
これはツールの場合には不要な事がある。あるいは1ファイルのスクリプトは分けないほうがポータビリティ(コピペビリティが良い)。
AI:
なるほど、話題が「コードのレイアウト」や設計思想にまで広がってきましたね。
- ツール: 単機能・高品質。複雑な問題を解く。(例: コンパイラ, grep)
- アプリケーション: 柔軟性・自由度。煩雑さを解決する。(例: 複数IDPごとの認証)
そして、その違いがコードの構造(関数 vs クラス、単一ファイル vs モジュール)に影響を与える、というご意見ですね。
特に「煩雑さ」と「複雑さ」という言葉の使い分けが、思考を整理する上で重要なキーワードになりそうですね。
(返答終了理由: STOP)
ユーザー:
煩雑さに向き合う能力として迂闊さに対する耐性が必要になる。ミスなく漏れなくまたそれを人間が行うことを諦めた先の安全な機能を要求したくなる。
複雑さに向き合う能力としてある種のゲームのカウンティング能力が必要になる。あるいはアルゴリズム。その前に問題空間への適切な転写が必要になる。
最もシンプルなアナロジーとしてgcが挙げられる。高機能なツールを作っているときにはメモリー管理を人間が行う。煩雑なアプリを作っているとき人間がこれを適切に行えるという仮定を設けない事が多い。
AI:
「煩雑さ」と「複雑さ」に対して、求められる能力や設計思想が違う、というお考えですね。
- 煩雑さ: ミスを防ぐ仕組み、安全な機能が求められる(例: GCによる自動メモリ管理)
- 複雑さ: アルゴリズム的思考、問題の適切なモデル化が求められる(例: 手動でのメモリ管理)
この対比は非常に興味深いです。
この「煩雑さ」と「複雑さ」という軸は、他のどのような技術的な話題にも適用できそうでしょうか?
(返答終了理由: STOP)
ユーザー:
もっとわかりやすいモデル化をすると複雑さあるいは困難さを持つ領域を解くとき直列したステップの連鎖がひつようになる。煩雑さをもつ領域を解くとき複数の可能性に対する選択の繰り返しが必要になる。
AI:
なるほど、問題解決のプロセスに違いがあるのですね。
- 複雑さ: 一本道を突き進むような、直列したステップの連鎖。
- 煩雑さ: 分岐路が多数あるような、並列的な選択の繰り返し。
これは非常に分かりやすいモデル化ですね。この考え方を使うと、例えば設計パターンの選択などにも応用できそうでしょうか?
(返答終了理由: STOP)
ユーザー:
設計パターンを要するとき開発効率などの言葉が出たときはほとんどの場合煩雑さの文脈になってる。結合度や結合という言葉がコードに対して語られたときにも煩雑さ文脈で語られている。
性能などの文脈では困難さの文脈で語られてる(複雑さより困難さが正しい。以後困難さを使う)
AI:
承知しました。「複雑さ」を「困難さ」に置き換えて整理しますね。
- 煩雑さの文脈: 開発効率、設計パターン、疎結合など。
- 困難さの文脈: 性能、アルゴリズムなど。
つまり、ソフトウェア開発に関する議論の多くは、「煩雑さ」をどう扱うかという話に集約される、ということでしょうか。
この視点で考えると、普段の技術的な会話が少し違って見えてきそうですね。
(返答終了理由: STOP)
ユーザー:
コアエンジンを作るとき以外概ね煩雑さや迂闊さへの対処がメインになるというのはそうかもしれない。ただしソフトウェア開発に関する議論の多くは煩雑さをどう扱うかという話には集約されない。
コンポーネントを作るものとコンポーネントを繋ぐものと考えると、後者の人口が多いとは言えるかもしれない。アナロジー的にはエアコンを開発する人よりもエアコンを各家庭に設置する人が多い(エアコンの製造はビルドステップなので概ね自動化されてる)。
AI:
なるほど、ありがとうございます。
- ソフトウェア開発は「煩雑さへの対処」がメインになることが多い。
- しかし、全ての議論がそこに集約されるわけではない。
- 「コンポーネントを作る人(困難さに向き合う)」と「コンポーネントを繋ぐ人(煩雑さに向き合う)」に分けられる。
そして「繋ぐ人」の方が人口が多いのではないか、という見立てですね。
この「作る人」と「繋ぐ人」という分類は、先ほどの「ツール」「アプリケーション」の話と、どのように関係してきそうでしょうか?
(返答終了理由: STOP)
ユーザー:
すべてのソフトウェアはコンポーネントの結合でできてますが、どのように結合するかは自明だったりどうでも良いのがツールです。どのように結合するかに悩まされるのがアプリケーションです。
メインの業務が何かということですね。コンポーネント自体の作成にリソースを費やすなら作る人でしょう。
AI:
なるほど、結合の仕方が重要かどうか、という点が分かれ目なのですね。
- ツール: 結合方法は自明か、重要ではない。
- アプリケーション: どのように結合するかが重要で、そこに悩む。
そして、リソースをどこに費やすかで「作る人」か「繋ぐ人」かが決まる、と。
この考え方でいくと、「エンタープライズアプリケーション」は「繋ぐ」作業の比重が極めて大きい、と言えそうですね。
(返答終了理由: STOP)
ユーザー:
そのとおりです。まさにそれが冒頭の話です。
AI:
ありがとうございます。最初の話に綺麗につながりましたね。
「ツール」「アプリケーション」「エンタープライズアプリケーション」という分類から始まり、「困難さ」と「煩雑さ」、「作る人」と「繋ぐ人」という対比を経て、最初の問題意識がより明確な言葉で表現できるようになったのではないでしょうか。
ここまでの思索を一度整理してみますか? それとも、このテーマについてもう少し深掘りしたい点はありますか?
(返答終了理由: STOP)
ユーザー:
詳細ダンプして
AI:
承知しました。ここまでの思索を詳細にダンプします。
1. ソフトウェアの分類と文脈
-
基本分類: ソフトウェアは「ツール」と「アプリケーション」に大別される。
-
ツール:
- 特性: 単一機能、高品質。分岐や順序は外部依存(RFCなど)か暗黙に固定。
- 目的: 複雑・困難な問題を解く。(例: コンパイラ、grep)
- 求められる品質: 速度などの性能。
- コード構造: 単一関数や1ファイルのスクリプトで十分な場合がある(ポータビリティ)。
- 結合: コンポーネントの結合方法は自明か、重要ではない。
-
アプリケーション:
- 特性: ユーザーの選択に基づく分岐がある。(例: 認可)
- 目的: 煩雑さを解決する。(例: 個別のIDPごとの認証処理)
- 求められる品質: 柔軟性、自由度。
- コード構造: 複数の操作を提供するためにクラスやモジュール/パッケージが必要になる。
- 結合: どのようにコンポーネントを結合するかが重要で、そこにリソースを費やす。
-
内包関係:
- エンタープライズアプリケーション: アプリケーションの中でも特に、煩雑な分岐と柔軟な仕様変更が求められるもの。DDDの文脈はここに属する。
-
ツール:
2. 問題領域の性質
-
二元論: ソフトウェアが向き合う問題は「困難さ」と「煩雑さ」に分けられる。
-
困難さ (Complexity):
- 問題解決プロセス: 直列したステップの連鎖。一本道を突き進むイメージ。
- 求められる能力: アルゴリズム的思考、カウンティング能力、問題空間への適切な転写。
- 技術的文脈: 性能。
- アナロジー: 手動でのメモリ管理。
-
煩雑さ (Complicatedness/Tediousness):
- 問題解決プロセス: 複数の可能性に対する選択の繰り返し。多数の分岐路があるイメージ。
- 求められる能力: 迂闊さに対する耐性、ミスなく漏れなく扱うための仕組み化。
- 技術的文脈: 開発効率、設計パターン、結合度。
- アナロジー: GCによる自動メモリ管理。
-
困難さ (Complexity):
3. 開発者の分類
-
役割による分類: 開発者は「コンポーネントを作る人」と「コンポーネントを繋ぐ人」に分けられる。
-
コンポーネントを作る人:
- 主戦場: 「困難さ」に向き合う。コアエンジンの開発など。
- アナロジー: エアコンを開発する人。
-
コンポーネントを繋ぐ人:
- 主戦場: 「煩雑さ」に向き合う。
- 人口: こちらの方が多い傾向にあるかもしれない。
- アナロジー: エアコンを各家庭に設置する人。
-
コンポーネントを作る人:
4. 思考の繋がり
- 「ツール」開発は「困難さ」に向き合う「作る人」の仕事が多い。
- 「アプリケーション」開発、特に「エンタープライズアプリケーション」開発は、「煩雑さ」に向き合う「繋ぐ人」の仕事が多い。
- これらの文脈(ツール vs アプリ / 困難さ vs 煩雑さ)が暗黙のうちに混ざることで、技術的な会話が噛み合わなくなる、というのが根本的な問題意識。
(返答終了理由: STOP)
ユーザー:
煩雑さに対する対応に自由度を上げる方法と自明度を上げる方法がありますね。わかりやすい例では多態性は自由度を上げてます。あるいは似たようなパッケージで似たような名前を付けるのは自由度を上げてます。より正確に言えばより少数の語彙でより多くのものを表現しようとしてます。
自明度を上げる方法というのは区別するということです。名前空間が1つなRPCをイメージしてください。メソッド名は重複を許さず豊富な語彙で省略を避けて名付けをします。
AI:
なるほど、「煩-雑さ」へのアプローチに新たな軸が出てきましたね。
- 自由度を上げる: 少数の語彙で多くのものを表現する。(例: ポリモーフィズム、共通的な命名規則)
- 自明度を上げる: 豊富な語彙で区別し、曖昧さをなくす。(例: 重複を許さないRPCメソッド名)
これらはトレードオフの関係にあるのでしょうか?
あるいは、どのような場合にどちらのアプローチを選択するのが良いとお考えですか?
(返答終了理由: STOP)
ユーザー:
使い分けたいかどうかです。ファサードやラッパーを提供することあるいはアダプターを要求すること。こういうときには何も考えたくないですよね。多態性はこういうときに欲しくなります。
一方で細かなオプションで調整可能なアルゴリズムを考えてみてください。それらのオプションは明確に区別したいですよね。こういうときには明示的に間違えることなく使い分けたいです。
これはテストコードを書く時のダミーオブジェクトかフェイクオブジェクトかの区別と同様のものです。(昨今はテストダブルも何もかもモックと呼んでる人も多いですが)
AI:
思考が深まりますね。使い分けの基準が見えてきました。
- 自由度を上げる(多態性など): 内部の詳細を隠蔽し、使い手側が「何も考えなくてよい」ようにしたい場合。(例: Facade, Adapter)
- 自明度を上げる(明確な命名など): 使い手側が「明確に区別し、意図通りに」使ってほしい場合。(例: アルゴリズムの細かなオプション)
そして、テストダブルの文脈(Dummy vs Fake)も、この使い分けの考え方と通じるものがある、と。
この「自由度」と「自明度」という軸は、他の設計原則やプラクティスにも当てはめて考えられそうですね。
(返答終了理由: STOP)
ユーザー:
例えばどういうものでしょうか?
この「自由度」と「自明度」という軸は、他の設計原則やプラクティスにも当てはめて考えられそうですね。
AI:
そうですね、いくつか例を挙げてみますね。これがご自身の思索のヒントになるか、ご覧になってみてください。
- 設定ファイル (config): 共通のデフォルト値を使い回すのは「自由度」、全ての項目を明示的に書かせるのは「自明度」。
-
API設計:
?type=allのようなパラメータで挙動を変えるのは「自由度」、/usersと/active_usersのようにエンドポイントを分けるのは「自明度」。 -
型システム:
anyやinterface{}を使うのは「自由度」、具体的な型を定義するのは「自明度」。
これらの例について、どのように思われますか?
あるいは、ご自身の考えを刺激するような他の例はありますか?
(返答終了理由: STOP)
ユーザー:
ああ、なるほどデフォルトオプションやデフォルト引数などはそれに付随する議論になるかもしれません。つまり既存のデフォルト値はユーザーが興味を持たなくて良い値ということですね。これをrequiredな引数にする場合は逆に明示的です。
AI:
まさにその通りですね。
- デフォルト引数: ユーザーが興味を持たなくてよい部分を隠蔽し、「自由度」を上げる。
- 必須引数 (required): ユーザーに意識させ、明確な意思決定を求めることで「自明度」を上げる。
関数のインターフェース設計という身近な例に、先ほどの「自由度」と「自明度」の軸がはっきりと現れていますね。
この考え方は、他の人との会話でも共通認識を作りやすそうですね。
(返答終了理由: STOP)
ユーザー:
話を変えますDDDで戦略の話が必要という話がありますよね。境界づけられたコンテキスト。これはある種の名前空間と見なしても良いわけですが、この名前空間が1つで十分もっと言えば自明というのがツールです。
AI:
なるほど、話がDDDとツールの比較に戻ってきましたね。
- DDD: 複数の「境界づけられたコンテキスト(名前空間)」をどう設計し、どう連携させるかが重要。
- ツール: そもそも名前空間は1つで十分、あるいは自明である。
これは、先ほどの「アプリケーションは結合の仕方に悩むが、ツールはそうではない」という話と、深く関連していそうですね。
(返答終了理由: STOP)
ユーザー:
そのとおりです。まさにその話ですね。一つで良いというのはディレクトリ構造もっと言えばファイル構造にも適用されます。すべて一つで良いというのが1ファイルのスクリプトなわけですね。そしてファイルを分けるべきかさらにパッケージないしはモジュールを分けるべきかと繋がっていきます。
AI:
ええ、繋がりましたね。
単一の名前空間(コンテキスト)で済むツールは、単一ファイルでも成立しやすい。
一方、複数の名前空間(コンテキスト)を扱うアプリケーション、特にDDDが必要になるようなエンタープライズアプリケーションは、必然的にファイルやモジュールを分割して構造化する必要が出てくる、ということですね。
この構造化の必要性が、「煩雑さ」への対処そのものだと言えそうですね。
(返答終了理由: STOP)
ユーザー:
また自明度と自由度の話もすると、型を汎用的なものにするかどうかという話があります。これはインターフェイスを返すか具象型を返すかと言う話でもありますし、リテラル型を返すかと言う話でもあります。ここでクリーンアーキテクチャやDDDの話にも接続されます。冒頭の話に戻ります。良いコードとは何かという議論で暗黙の仮定や文脈を明示せず議論する人が多いという話です。
AI:
なるほど、「自明度 vs 自由度」の軸が、型システムや設計レイヤーの話を通じて、最初の問題意識である「良いコードとは何か」という議論の文脈問題に再び接続されたわけですね。
- 自由度: 汎用的な型やインターフェイスを返す → 結合部の柔軟性を高める(Clean Architectureの依存関係逆転など)
- 自明度: 具象型やリテラル型を返す → 呼び出し側が具体的な値を意識しやすくなる
これらの選択は、作っているものが「ツール」なのか「アプリケーション」なのか、「困難さ」と「煩雑さ」のどちらに主眼を置いているのかによって、最適な答えが変わってくる。
その前提を共有しないと、「良いコード」の議論が噛み合わない、ということですね。
(返答終了理由: STOP)
ユーザー:
もっと言えば、標準的なファイル構造を初期段階に要求するのは悪であるか?みたいな話もできますね。例えば一つの作品を作り続けるならそれに最適化したい一方で複数の案件を掛け持ちする受託開発などでは何らかの標準的な構造を要求したいです。
AI:
ええ、非常に具体的なテーマが出てきましたね。
- 単一作品に最適化: プロジェクト固有の最適な構造を模索する。これは「自由度」を重視する考え方かもしれません。
- 複数案件の標準化: 案件間の認知負荷を下げるために標準構造を要求する。これは「自明度」を重視するアプローチですね。
開発の「コンテキスト」によって、ファイル構造の「良さ」の基準が変わってくる、と。
これはまさに、冒頭でお話しされていた「文脈が異なると会話が噛み合わない」という状況の一例ですね。
(返答終了理由: STOP)
ユーザー:
詳細ダンプして
AI:
承知しました。ここまでの思索を、構造を保ったまま詳細にダンプします。
1. 根本的な問題意識
- ソフトウェア開発(特に「良いコード」)に関する議論が噛み合わないことがある。
- その原因は、参加者が暗黙に異なる文脈(「ツール」か「アプリケーション」か、など)で話していることにある。
2. ソフトウェアの分類と特性(第一の軸)
-
ツール:
- 定義: 単一機能を持つ。コンパイラやgrepなど。
- 関心事: 「困難さ」の解決。性能やアルゴリズムが重要。
- 分岐/順序: 外部仕様(RFCなど)や暗黙のルールで固定されている。
- 結合: コンポーネントの結合方法は自明か、重要ではない。
- 名前空間: 1つで十分、あるいは自明(DDDの境界づけられたコンテキストは不要)。
- コード構造: 単一ファイルスクリプトのように、構造がシンプルな場合がある。
-
アプリケーション:
- 定義: ユーザーの選択に基づく分岐を持つ。
- 関心事: 「煩雑さ」の解決。柔軟性や自由度が重要。
- 結合: どのようにコンポーネントを結合するかが重要で、そこに悩む。
- 名前空間: 複数の関心事を扱うため、分割(モジュール、パッケージ)が必要になる。
-
内包関係:
- エンタープライズアプリケーション: アプリケーションの中でも特に、煩雑な分岐と柔軟な仕様変更が求められるもの。DDDの戦略的設計が活きる領域。
3. 問題領域の性質(第二の軸)
-
困難さ (Complexity):
- プロセス: 直列したステップの連鎖。一本道を突き進むイメージ。
- 要求能力: アルゴリズム的思考、問題の適切なモデル化。
- 文脈: 性能、コアエンジン開発。
- アナロジー: 手動でのメモリ管理。
-
煩雑さ (Complicatedness/Tediousness):
- プロセス: 複数の可能性に対する選択の繰り返し。多数の分岐路があるイメージ。
- 要求能力: 迂闊さへの耐性、ミスなく漏れなく扱うための仕組み化。
- 文脈: 開発効率、設計パターン、結合度。
- アナロジー: GCによる自動メモリ管理。
4. 「煩雑さ」へのアプローチ(第三の軸)
-
自明度を上げる (Clarity / Explicitness):
- 方針: 豊富な語彙で明確に区別し、曖昧さを排除する。
- 目的: 使い手が「明確に区別し、意図通りに」使えるようにする。
-
具体例:
- 重複を許さないRPCメソッド名。
- アルゴリズムの細かなオプション。
- 必須引数 (required arguments)。
- APIエンドポイントの分割 (
/usersvs/active_users)。 - 具象型やリテラル型を返す。
- 複数案件をまたぐ際の、標準的なファイル構造の強制。
-
自由度を上げる (Flexibility / Generality):
- 方針: 少数の語彙で多くのものを抽象化して表現する。
- 目的: 使い手が「何も考えなくてよい」ように詳細を隠蔽する。
-
具体例:
- 多態性 (Polymorphism)。
- Facade, Adapter, Wrapper パターン。
- デフォルト引数 (default arguments)。
- 汎用的なAPIパラメータ (
?type=all)。 - インターフェイスや汎用型 (
any) を返す。 - 単一プロジェクトに最適化された、独自のファイル構造。
5. 開発者の役割分類
- コンポーネントを作る人: 主に「困難さ」に向き合う。コアエンジン開発など。
- コンポーネントを繋ぐ人: 主に「煩雑さ」に向き合う。人口はこちらの方が多い傾向。
6. 全体の繋がりと結論
- 議論のすれ違いは、「ツールを作る人(困難さと向き合う)」の視点と、「アプリケーションを繋ぐ人(煩雑さと向き合う)」の視点が混在することで発生する。
- 「煩雑さ」への対処法としての「自明度」と「自由度」の選択は、開発のコンテキスト(単一プロダクトか複数案件か、など)に大きく依存する。
- 型、インターフェイス、ファイル構造など、具体的な設計の「良し悪し」は、これらの文脈や軸の上でどこに位置づけているかによって変わるため、その前提を共有することが円滑な議論に繋がる。
(返答終了理由: STOP)
ユーザー:
あとそうですねdoc stringを真面目に書くべきかメソッド名で違いを出すかみたいな話もこの話に接続できます。メソッド名で違いを出すというのは使い分けの文脈なんですよね。一方でdoc stringを丁寧に書くべきというのは暗黙の前提やその計算の背景や限界などを明記したりしてほしいということですね。これは自明度と自由度で表すことは難しく実装の安定性の文脈ですね。開発してるものが不安定であるものもアプリケーションです。
いわゆるclean codeはアプリケーションのための本です。いわゆるA Philosophy of Software Designはツールないしはコアライブラリのための本です。
AI:
なるほど、さらに2つの重要な視点が加わりましたね。
-
Docstringと実装の安定性:
「自明度/自由度」とは別に、実装の背景や制約といった「安定性」に関する情報をどう伝えるか、という軸がある。 -
書籍の文脈:
『Clean Code』はアプリケーションの「煩雑さ」に、『A Philosophy of Software Design』はツールの「困難さ」に、それぞれ主眼を置いているのではないか、という具体的なマッピング。
この書籍の対比は、これまでの思索を象徴する、非常に的確な例だと感じます。
(返答終了理由: STOP)
ユーザー:
そしてツール/アプリケーションはデプロイないしはディストリビューションの対象が1つですがこれが複数の対象の接続で考えるとシステムになりますね。これはおまけです。
最も言いたいことはツール度ないしはアプリケーション度という形のスペクトラムになっているということですね。そして自身の生息地がどの位置であるかはけっこう人により変わる。これを暗黙に省略して会話するとプロトコルの不一致のようなことが起きがちで不毛というのが冒頭の話になります。
AI:
思考の核心にたどり着きましたね。
- システムの定義: 複数の対象の接続を考えるのが「システム」。
- スペクトラムとしての認識: 「ツール度」と「アプリケーション度」は0か1かではなく、連続的なスペクトラムである。
- プロトコルの不一致: 人はそれぞれ、そのスペクトラム上の異なる「生息地」にいる。その位置を自覚・明示せずに会話することが、すれ違いの根本原因である。
まさに、この「プロトコルの不一致」という言葉が、最初の問題意識を見事に表現していますね。
この思索全体を、誰かに伝えるとしたら、どのような形になるでしょうか?
(返答終了理由: STOP)
ユーザー:
専門用語解説して
(直近の会話ではなく会話全体の中で専門用語を抽出し一般的な意味と今回の会話での意味を列挙して)
AI:
承知しました。この対話全体で登場した専門用語や重要な概念について、一般的な意味と今回の会話における位置づけを解説します。
専門用語・概念の解説
DDD (ドメイン駆動設計)
- 一般的な意味: ソフトウェアの関心の中心を、そのソフトウェアが解決しようとしている「ドメイン(業務領域)」に置き、ドメインの専門家と開発者が共通の言語(ユビキタス言語)を使いながら、複雑なビジネスロジックをモデル化していく設計手法です。
- 今回の会話での位置づけ: 会話の最初に、「エンタープライズアプリケーション」の文脈を代表する具体例として登場しました。煩雑な仕様変更や多様な分岐に対応するためのアプローチであり、「煩雑さ」と向き合うためのものと位置づけられています。
境界づけられたコンテキスト (Bounded Context)
- 一般的な意味: DDDにおける中核的な概念の一つ。特定のモデルが意味を持つ範囲・境界を明確に定義するものです。この境界内では用語の意味が一貫しますが、境界を越えると別の意味を持つことがあります(例:「商品」という言葉が、在庫管理とECサイトでは意味が違う)。
- 今回の会話での位置づけ: 「名前空間」のアナロジーとして登場しました。「ツール」は単一の名前空間で十分ですが、「アプリケーション」、特にエンタープライズアプリケーションは複数のコンテキスト(名前空間)をどう設計し連携させるか、という「煩雑さ」に向き合う必要がある、という文脈で語られました。
GC (ガベージコレクション)
- 一般的な意味: プログラムが動的に確保したメモリ領域のうち、不要になったものを自動的に解放する仕組みです。これにより、開発者は手動でのメモリ管理から解放されます。
- 今回の会話での位置づ-け: 「困難さ」と「煩雑さ」を対比させるためのアナロジーとして登場しました。手動のメモリ管理は「困難さ」(アルゴリズム的思考)への対処、GCによる自動管理は「煩雑さ」(人間がミスしがちな作業)への対処、という対比で用いられました。
多態性 (Polymorphism)
- 一般的な意味: オブジェクト指向プログラミングの主要な概念の一つで、「多様な形を持つ」という意味です。同じインターフェイス(操作方法)でありながら、それを受け取るオブジェクトの種類によって実際の振る舞いが変わる性質を指します。
- 今回の会話での位置づけ: 「煩雑さ」へのアプローチのうち、「自由度を上げる」方法の代表例として登場しました。インターフェイスを共通化することで、使う側が具体的な実装を意識しなくて済むようにする(何も考えなくてよい状態にする)ための強力な手段と位置づけられています。
テストダブル (Test Double) / Mock / Fake
- 一般的な意味: 自動テストにおいて、テスト対象が依存しているコンポーネントを置き換える代用品の総称です。その目的や振る舞いによって、Dummy, Stub, Spy, Mock, Fakeなどに細かく分類されます。
- 今回の会話での位置づ-け: 「自明度」と「自由度」の使い分けを説明するアナロジーとして登場しました。(昨今、すべてをMockと呼ぶ風潮があるが)本来の定義通りDummyやFakeを使い分ける意識は、目的を明確に区別したい「自明度」を重視する考え方と通じる、という文脈で語られました。
クリーンアーキテクチャ (Clean Architecture)
- 一般的な意味: Robert C. Martin氏が提唱した、関心の分離を徹底するためのソフトウェアアーキテクチャの指針。ビジネスルールを中心(内側)に置き、フレームワークやDBなどの詳細(外側)から依存されないように「依存性のルール」を定めているのが特徴です。
- 今回の会話での位置づ-け: 型やインターフェイスの議論から接続されました。具象型ではなくインターフェイス(抽象)に依存させることで、詳細からビジネスロジックを切り離すこの考え方は、「自由度を上げる」アプローチの一環として捉えられています。
『Clean Code』 / 『A Philosophy of Software Design』
- 一般的な意味: それぞれ著名なソフトウェア設計に関する書籍です。『Clean Code』は読みやすく変更しやすいコードを書くための具体的なプラクティスを、『A Philosophy of Software Design』は「複雑さ」を管理するためのより哲学的な設計原則を説いています。
- 今回の会話での位置づけ: これまでの思索全体を象徴する具体例として登場しました。『Clean Code』はアプリケーションの「煩雑さ」に、『A Philosophy of Software Design』はツール/ライブラリの「困難さ」に、それぞれ主眼を置いて書かれているのではないか、というマッピングがなされました。これは、議論の文脈が異なれば「良いコード」の定義も変わる、という中心的なテーマを補強するものです。
(返答終了理由: STOP)
メタデータ
{
"runSettings": {
"temperature": 1,
"model": "models/gemini-2.5-pro",
"topP": 0.95,
"topK": 64,
"maxOutputTokens": 65536,
"safetySettings": [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "OFF"
}
],
"enableCodeExecution": false,
"enableSearchAsATool": false,
"enableBrowseAsATool": false,
"enableAutoFunctionResponse": false,
"thinkingBudget": -1,
"outputResolution": "1K"
},
"systemInstruction": {}
}
後になって考えてみるとco-locationの話をしていなかったな。co-locationが嬉しいということこそ煩雑さへの対応という話。
あと、自由度/自明度よりは汎用性/明示性の方が良い(受託開発の箇所でAI側が取り違えて解釈してる)。
target_reader: ソフトウェア開発の設計思想やチームでのコミュニケーションに関心を持つ技術者
objective: ソフトウェア開発に関する議論がすれ違う根本原因を複数の対立軸から解き明かし、円滑な技術的対話のための共通言語(プロトコル)を探求する過程を提示する
プロトコルの不一致:ソフトウェア設計の対立軸をめぐる思索
始まりの問い 🗺️
「良いコードとは何か」。この問いを巡る議論は、ソフトウェア開発の世界で尽きることがない。しかし、熱心な議論が必ずしも実りある結論に結びつくとは限らない。むしろ、参加者がそれぞれの正義を主張し、会話がすれ違ったまま平行線をたどる場面は珍しくない。なぜ、このようなことが起きるのだろうか。
その原因は、技術的な知識の優劣や経験の多寡にあるのではなく、もっと根源的な「前提の不一致」にあるのではないか。我々がソフトウェアについて語るとき、無意識のうちに異なる世界観、異なる「文脈」に立脚している。その文脈の違いを明らかにしないまま言葉を交わすことが、建設的な対話を妨げる根本的な障壁となっているのではないか。
この思索の旅は、その無意識の「文脈」を言語化し、構造化する試みである。すべてのソフトウェア開発者が共有できる、議論のための地図を描き出すこと。その第一歩として、ソフトウェアの世界を二つの大きな領域に分類することから始めたい。それは「ツール」と「アプリケーション」という二つの世界観である。
一見すると単純な分類だが、この二つの領域では、ソフトウェアが向き合うべき課題の性質、求められる品質、そして設計の哲学が根本的に異なる。
まず「ツール」とは、単一の明確な機能を持つソフトウェアを指す。コンパイラやgrepのようなコマンドラインユーティリティがその典型だ。その価値は、特定の「困難な」問題を、いかに高品質かつ高性能に解決するかにかかっている。ツールの振る舞いは、多くの場合、RFC[1]のような外部仕様によって厳密に定められているか、あるいは暗黙のルールによって固定されている。分岐や順序は、開発時に決定され、実行時にはほとんど揺らがない。
一方、「アプリケーション」とは、ユーザーとの対話の中で機能を提供するソフトウェアである。Webサービスや業務システムがこれにあたる。アプリケーションの世界では、ユーザーの選択や状況に応じて、実行時に多様な分岐が発生する。例えば、ユーザーの権限によって表示するメニューを変える、といった認可の処理が好例だ。アプリケーションの中でも、特に煩雑なビジネスロジック、多様なユースケース、そして頻繁で柔軟な仕様変更が求められる領域を「エンタープライズアプリケーション」と呼ぶ。この領域では、ドメイン駆動設計(DDD)[2]のような高度な設計手法が必要とされる。
この「ツール」と「アプリケーション」という最初の分類は、ソフトウェア開発の議論がすれ違う根本原因を探るための、重要な出発点となる。
困難さと煩雑さという二つの課題 ⚖️
「ツール」と「アプリケーション」という分類は、それらが向き合う問題の性質の違いに光を当てることで、より深い洞察をもたらす。ソフトウェアが解決しようとする課題は、本質的に二つの異なる性質に大別できる。それは「困難さ」と「煩雑さ」である。
「困難さ (Complexity)」とは、アルゴリズム的な複雑性や、極限の性能が求められるような、深く、しかし単一の課題領域を指す。この問題を解決するプロセスは、一本の険しい道を突き進むような、直列したステップの連鎖に喩えられる。最適なアルゴリズムを見つけ出す思考力や、問題の本質を正確に計算モデルに転写する能力が求められる。メモリ管理を手動で行い、パフォーマンスを徹底的に追求するような世界は、まさに「困難さ」との戦いである。主に「ツール」が向き合うのは、この種の課題だ。
一方で「煩雑さ (Tediousness)」とは、一つ一つの要素は技術的に困難ではないかもしれないが、その組み合わせやパターンの数が膨大で、管理が厄介な課題領域を指す。この問題を解決するプロセスは、無数の分岐路が広がる迷路を進むような、並列的な選択の繰り返しに喩えられる。例えば、システムがサポートすべき認証プロバイダ(IdP)[3]が複数あり、それぞれに微妙に異なる実装を施さなければならない状況。あるいは、ユーザーの要求に応じて、同じデータをJSON、XML、CSVといった複数のフォーマットで出力し分ける機能。これらは「困難」ではないが、間違いなく「煩雑」だ。
「煩雑さ」に向き合うには、アルゴリズム的思考力とは異なる能力が求められる。それは、人間の迂闊さやミスに対する耐性である。似たようなコードをコピペして修正漏れを起こす、仕様の考慮漏れが発生するといった、人間が避けがたいミスをいかにして構造的に防ぐか。ガベージコレクション(GC)[4]が、メモリ解放という煩雑な作業を自動化することで人間をミスから解放したように、「煩雑さ」への対処とは、安全な仕組みを構築する営みなのである。主に「アプリケーション」、特に「エンタープライズアプリケーション」が向き合うのは、この「煩雑さ」との戦いだ。
この課題の性質の違いは、開発者の役割にも反映される。「困難さ」に向き合い、システムのコアとなる高性能な部品を作る「コンポーネントを作る人」。そして、「煩雑さ」に向き合い、それらの部品を適切に組み合わせ、多様なユースケースに対応させる「コンポーネントを繋ぐ人」。エアコンを開発する技術者と、それを各家庭に設置する技術者のアナロジーがしっくりくる。ソフトウェア開発の世界では、後者の「繋ぐ」役割を担う開発者の方が多数派かもしれない。
「良いコード」の議論が噛み合わない一因は、ここにある。「困難さ」の文脈で語る者と、「煩雑さ」の文脈で語る者とでは、評価する品質の尺度が全く異なるのだ。
煩雑さへの二つのアプローチ ↔️
ソフトウェア開発の多くが「煩雑さ」との戦いであるならば、その戦い方、すなわち設計のアプローチを理解することが重要になる。煩雑さを乗りこなすための設計思想は、大きく二つの方向性に分類することができる。それは「汎用性」を追求するアプローチと、「明示性」を追求するアプローチである。
「汎用性 (Generality)」を追求するアプローチとは、少数の語彙や仕組みで、できるだけ多くの、そして多様な状況に対応しようとする設計思想だ。その根底には、ソフトウェアの部品を使う側の認知的な負荷を下げたいという配慮がある。部品の内部がどうなっているかを詳細に知らなくても、ブラックボックスとして信頼して使えるようにする。これにより、利用者はより本質的な課題、すなわち「部品をどう組み合わせるか」に集中できる。
このアプローチは、意地悪く情報を「隠蔽」するのではなく、親切心から「この部分の関心事は、こちらで引き受けておきます」と宣言する営みだ。だからこそ、「隠蔽」という言葉よりも「気にしなくてもよいようにする」という表現がふさわしい。多態性(Polymorphism)[5]はその典型だ。同じインターフェイスの裏側で、具体的なオブジェクトがその詳細な振る舞いを吸収してくれる。あるいは、関数のデフォルト引数は、「特に指定がなければ、こちらで良きに計らっておきます」というメッセージであり、これもまた汎用性を高めるアプローチである。
一方、「明示性 (Explicitness)」を追求するアプローチとは、豊富な語彙を用いて、機能や意図の違いを明確に区別させようとする設計思想だ。その根底には、ソフトウェアの部品を使う側に、意図的な意思決定を促したいという思想がある。部品をホワイトボックス的に見せ、その選択肢を理解した上で、利用者の責任において明確に振る舞いを選択させる。
これは「責任の所在の明確化」と言える。例えば、アルゴリズムの挙動を微調整するための、細かく分かれた多数のオプション。あるいは、関数の必須引数は、「この値はシステムの振る舞いに重大な影響を与えるため、あなた自身が意識して指定してください」という強いメッセージである。機能ごとにAPI[6]のエンドポイントを/usersと/active_usersのように明確に分割するのも、利用者に曖昧な解釈を許さず、意図を明示的に表現させるアプローチだ。
「汎用性」と「明示性」。これらは、どちらが優れているというものではない。「煩雑さ」という同じ敵に対して、どのような武器で立ち向かうかという戦略の違いである。そして、どちらの戦略を選択すべきかは、開発のコンテキストによって大きく左右される。この第二の対立軸の発見は、議論のすれ違いを解き明かすための、さらなる鍵となる。
対立軸の交点と全体像 🗺️
これまでに、「ツール/アプリケーション」という領域の分類、「困難さ/煩雑さ」という課題の性質、そして「汎用性/明示性」という設計アプローチという、複数の対立軸を明らかにしてきた。これらの軸は独立しているのではなく、互いに深く関連し合っている。この関連性を解き明かすことで、我々は議論の地図の全体像を俯瞰することができる。
例えば、「標準的なファイル構造を強制するべきか」という、しばしば宗教論争にまで発展するテーマを考えてみよう。この問いは、開発のコンテキストによって答えが全く変わってくる。
ある一つのプロダクトを長期的に開発し続けるチームを想像してほしい。彼らは、そのプロダクト固有の概念や機能を、ファイルやディレクトリの名前に詳細に反映させたいと考えるかもしれない。services/payment/stripe_charge_service.tsのような、具体的で詳細な命名は、そのプロダクト内での役割を明確に表現する。これは、豊富な語彙で違いを際立たせる「明示性」を重視したアプローチだ。
次に、複数のクライアントから多種多様な案件を請け負う受託開発チームを想像してみよう。彼らは、プロジェクトが変わるたびに新しいファイル構造を学習するコストを避けたいと考える。そのため、src/controllers, src/services, src/modelsのような、ある種の標準化された構造をすべての案件で採用するかもしれない。これは、一つの型(少数の語彙)で多様なプロジェクト(多くのもの)を表現する「汎用性」を重視したアプローチである。
このように、どちらのアプローチが「良い」かは、彼らが主に「単一プロダクトの煩雑さ」と向き合っているのか、「複数案件をまたぐ煩雑さ」と向き合っているのか、というコンテキストに依存する。
この地図の上に、既存の設計思想を配置することも可能だ。クリーンアーキテクチャ[7]は、具象ではなく抽象(インターフェイス)に依存することを説く。これは、詳細を気にせず部品を交換可能にすることで、システムの柔軟性を高める「汎用性」のアプローチと強く結びつく。
あるいは、著名なソフトウェア設計に関する書籍も、この文脈で捉え直すことができるかもしれない。『Clean Code』が説く多くのプラクティスは、人間が陥りがちなミスを防ぎ、コードの可読性を高めることで、「煩雑さ」に立ち向かうためのものが多い。一方で、『A Philosophy of Software Design』は、「複雑さ(ここでの困難さ)」をいかに管理するかという、より根源的な問いに焦点を当てているように読める。
これらの設計思想や書籍が、それぞれ異なる課題領域やアプローチに焦点を当てていることを理解すれば、それらを盲目的に信奉したり、文脈を無視して適用したりすることの危険性が見えてくる。重要なのは、自分たちが今、地図のどの地点に立っているのかを正確に把握することなのだ。
結論:スペクトラムとしての認識と「プロトコルの不一致」 📡
この思索の旅を通じて、我々はソフトウェア開発の議論を構造化するための、複数の対立軸を発見した。しかし、最後に最も重要な点を指摘しなければならない。これらの分類は、0か1かで割り切れるデジタルな二元論ではない、ということだ。
「ツール」と「アプリケーション」、「困難さ」と「煩雑さ」、「汎用性」と「明示性」。これらはすべて、両極端の間に無限のグラデーションが存在する、アナログな「スペクトラム」なのである。純粋なツールもあれば、アプリケーション的な側面を持つツールもある。一つのソフトウェアの中に、「困難さ」に満ちたコアエンジン部分と、「煩雑さ」に満ちたUI部分が同居していることも珍しくない。
すべての開発者は、この広大なスペクトラムの、どこか特定の領域を主な「生息地」としている。ある者は性能追求が至上命題の組み込みライブラリの世界に、またある者は変化し続けるビジネス要求に応えるエンタープライズシステムの世界に。
ソフトウェア開発の議論が噛み合わない根本原因は、ここにある。それぞれの参加者が、自身の「生息地」を暗黙の前提として会話を始めてしまうこと。自分が立っているスペクトラム上の座標を自覚せず、また相手に明示することもないまま、あたかもそれが全世界であるかのように語ってしまうこと。
これこそが「プロトコルの不一致」である。ネットワーク通信においてプロトコルが一致しなければデータ交換が不可能なように、開発者間の対話においても、この無意識の前提というプロトコルが一致しなければ、実りあるコミュニケーションは成立しない。
この思索の最終的な目的は、「ツールとアプリケーションのどちらが優れているか」あるいは「汎用性と明示性のどちらが正しいか」という結論を出すことではなかった。そうではなく、我々の議論の背景に横たわるこれらの対立軸、すなわち対話のプロトコルそのものを明らかにすることにあった。
次に「良いコードとは何か」を議論する機会があれば、まずはこう問いかけることから始めたい。「我々が作っているのは、スペクトラムのどのあたりに位置するものだろうか? そして、我々が今、最も向き合うべき課題は『困難さ』だろうか、それとも『煩雑さ』だろうか?」と。その問いこそが、不毛なすれ違いを防ぎ、より建設的な未来へと繋がる、真の第一歩となるだろう。
-
RFC (Request for Comments): インターネットに関する技術の仕様やプロトコルなどを公開し、広くコメントを求めるために発行される一連の文書。事実上の標準仕様として機能することが多い。 ↩︎
-
ドメイン駆動設計 (DDD - Domain-Driven Design): ソフトウェアが解決しようとしている業務領域(ドメイン)の専門知識と構造を中心に据え、それを直接的にコードのモデルに反映させる設計アプローチ。特に複雑なビジネスロジックを持つシステムの「煩雑さ」を管理するのに有効とされる。 ↩︎
-
IdP (Identity Provider): ユーザー認証を行い、他のサービス(SP - Service Provider)に対してユーザーの身元情報を提供するシステム。複数のIdPに対応するということは、それぞれ異なる認証方式や仕様という「煩雑さ」に向き合うことを意味する。 ↩︎
-
GC (ガベージコレクション): プログラムが動的に確保したメモリ領域のうち、不要になったものを自動的に検出して解放する仕組み。プログラマをメモリ管理の「煩雑さ」から解放する。 ↩︎
-
多態性 (Polymorphism): オブジェクト指向プログラミングにおける主要な概念の一つ。同じインターフェイス(操作方法)を通じて、異なるクラスのオブジェクトがそれぞれ固有の振る舞いをすることを指す。「汎用性」を実現するための強力なメカニズム。 ↩︎
-
API (Application Programming Interface): あるソフトウェアの機能やデータを、外部の他のプログラムから呼び出して利用するための、一連の規約やインターフェイス。 ↩︎
-
クリーンアーキテクチャ (Clean Architecture): ソフトウェアの関心事を同心円状のレイヤーに分離し、ビジネスルール(内側)がフレームワークやUI(外側)などの詳細に依存しないように「依存性のルール」を定めた設計思想。システムの保守性やテスト容易性を高めることを目的とする。 ↩︎
こういう具体性があった文章が↑のでは消えた文章になるのはショックだな。あとテストダブルの例が消えるのもショック。
汎用性(Generality)とは、「少数の語彙で多くのものを表現する」アプローチである。使う側が内部の詳細を意識しなくても済むように、物事を抽象化・一般化する。これは、使う側の認知負荷を下げ、「何も考えずに使える」状態を目指す、いわばブラックボックス的なアプローチだ。多態性(Polymorphism)[^5]や、Facade・Adapterといった設計パターン、デフォルト引数、インターフェイスを返す設計などがこれにあたる。ここでの本質は、意地悪く情報を「隠蔽」することではなく、「この部分の関心事は引き受けます」という、使う側への配慮やサービスとしての側面が強い。
明示性(Explicitness)とは、「豊富な語彙で明確に区別する」アプローチである。使う側に意図的な意思決定を促し、間違いのないように選択させる。これは、内部の違いを理解した上で、責任を持って選択してもらうことを目指す、いわばホワイトボックス的なアプローチだ。省略を避けたRPC[^6]のメソッド名、アルゴリズムの細かなオプション、必須引数、具象型を返す設計などがこれにあたる。これは、「あなたの選択がシステムの振る舞いを決定します」という責任の所在を明確にするコミュニケーションである。
(最適化を除いた場合)コード一行あたりの価値が低いとかがアプリケーション度を表す良い指標かもしれない。チューニング関係の文脈では当然ではあるけれどその対象の価値が高いという前提が暗黙に置かれたりもする。
💭 あとモナドとモナドのインスタンスの違いみたいな話もあるのか。一般的な規則ではなく単にasyncが欲しかったみたいな話。これも構文自体を完璧に分けるという話だから明示性の話だな。
APOS vs Clean Code
- どのような議論ですか議論の遷移を教えて下さい
- 意見が一致しているところはどこですか?
- 意見に相違があるところはどこですか?
この文書は、著名なソフトウェア設計者であるロバート・"アンクル・ボブ"・マーティン氏(『クリーンコード』著者)とジョン・アウスターハウト氏(『A Philosophy of Software Design』著者)による、ソフトウェア設計哲学に関する一連の議論をまとめたものです。
議論の概要と遷移
この議論は、両者のソフトウェア設計に対する基本的な考え方の違いを明らかにすることから始まります。その後、3つの主要なトピックに焦点を当てて、具体的な議論が展開されます。
-
導入と基本哲学:
- アウスターハウト氏: ソフトウェア設計の目標は、システムの理解と変更を容易にすること、すなわち「複雑さ」を減らすことだと主張します。複雑さは、開発者が必要とする情報の量と、その情報へのアクセスのしやすさに関連していると述べています。
- マーティン氏: アウスターハウト氏の考えに同意し、特にコードの作者以外のプログラマがコードを読む際の負担を軽減することが重要であると付け加えています。
-
メソッドの長さ (Method Length):
- 『クリーンコード』が推奨する「メソッドは非常に短くあるべき」という考え方について議論が交わされます。
- アウスターハウト氏は、メソッドを過度に分割すると、インターフェースが浅くなり(shallow)、メソッド間の「絡み合い」(entanglement)が生じ、かえって理解が難しくなると批判します。
- マーティン氏は、メソッドは「一つのこと」(One Thing)をすべきというルールを提唱し、小さなメソッドへの分割を擁護します。
- 具体的なコード例として『クリーンコード』に掲載されている
PrimeGeneratorが取り上げられ、その分割方法の是非について議論が深まります。
-
コメント (Comments):
- 『クリーンコード』が「コメントは常に失敗である」と述べていることに対し、アウスターハウト氏が異議を唱えます。
- アウスターハウト氏は、コメントはコードだけでは表現できない重要な情報(抽象化や設計意図など)を補うために不可欠であり、生産性を大幅に向上させると主張します。
- マーティン氏は、コメントは必要悪であり、誤解を招くリスクがあるため、可能な限りコード自体で意図を表現すべきだと反論します。長いメソッド名などもその一環だと述べています。
-
テスト駆動開発 (Test-Driven Development - TDD):
- アウスターハウト氏は、テストを先に書くTDDのアプローチは、開発者を戦術的すぎる思考に陥らせ、全体的な設計を妨げる可能性があると懸念を示します。
- マーティン氏は、TDDの3つの法則を説明し、TDDがデバッグの必要性を減らし、疎結合な設計を促進し、恐れることなくリファクタリングを可能にするなどの利点を挙げ、設計を妨げるものではないと主張します。
- アウスターハウト氏が提案する、ある程度のコードの塊を書いてからテストを書く「バンドル」アプローチと比較しながら、両者の優劣が議論されます。
議論の最後には、それぞれのトピックに関する合意点と相違点がまとめられ、両者が互いの視点に感謝を述べながら締めくくられています。
意見が一致しているところ
両者の議論からは、いくつかの基本的な価値観において意見が一致していることがわかります。
- ソフトウェア設計の基本目標: ソフトウェアは、特にコードの作者以外の人が読んだときに、理解しやすく、変更しやすいものであるべきであるという点で一致しています。
- モジュール設計: 複雑な機能をより小さく管理しやすい単位に分割するモジュール設計が良いものであるという点で合意しています。
- 過剰な分割の可能性: メソッドは過剰に分割される可能性があり、そうなるとコードが改善されるとは限らないという点でも意見が一致しています。
-
PrimeGeneratorの内部設計: 『クリーンコード』の例であるPrimeGeneratorクラスのメソッドへの内部的な分割は問題があったという点で一致しています。 - ユニットテストの重要性: ユニットテストはソフトウェア開発に不可欠な要素であり、システムの変更を恐れずに行うことを可能にするという点で完全に合意しています。
- TDDによる良い設計: TDDを使って良い設計のシステムを生み出すことは可能であるという点でも一致しています。
意見に相違があるところ
一方で、具体的な実践方法や哲学においては、多くの点で意見が対立しています。
- メソッドの分割の程度: マーティン氏はメソッドを2〜4行といった極めて小さな単位に分割することを推奨するのに対し、アウスターハウト氏はそれでは分割しすぎであり、かえってコードを読みにくくすると考えています。
- 「一つのこと」ルール: マーティン氏が提唱する「メソッドは一つのことをすべき」というルールについて、アウスターハウト氏は曖昧で過剰な分割につながるガードレールがないと批判しています。
- コメントの価値: アウスターハウト氏はコメントがシステムの理解に不可欠で非常に価値があると考えているのに対し、マーティン氏はコメントを「必要悪」とみなし、誤解を招くリスクやメンテナンスされない問題を懸念しています。コメントがないことによる生産性の損失よりも、不正確なコメントによる損失の方が大きいと考えています。
- コメントとコードの役割: マーティン氏は、情報をコード(長いメソッド名など)で表現することを好み、人間の言語(英語)であるコメントの使用には懐疑的です。一方、アウスターハウト氏は、コメントとコードは異なる目的を果たしており、英語はコメントに適した言語だと考えています。
- インターフェースのドキュメント: アウスターハウト氏は、抽象化やインターフェースの定義にはコメントが不可欠だと主張しますが、マーティン氏は、特にチーム内部のAPIについては、コメントなしでも十分に理解可能だと考えています。
- テスト駆動開発(TDD)のリスク: アウスターハウト氏は、TDDが開発者を戦術的な思考に偏らせ、戦略的な設計を妨げるリスクが高いと考えています。一方、マーティン氏はTDDが設計を妨げることはなく、むしろ良い設計を促進すると信じています。
- TDDと「バンドル」アプローチの比較: TDDの代替案としてアウスターハウト氏が提唱する「バンドル」アプローチについて、マーティン氏はTDDの方が生産性が高い可能性があると考える一方、アウスターハウト氏はバンドルアプローチの方が設計に焦点を当てるため、平均的および最悪の場合の結果がTDDよりも良くなると主張しています。
💭 軽く読み直してみたけどやっぱり前の議論の話と一致してるな。
議論で使われている専門用語について、文中での具体的な使われ方と、ソフトウェア開発における一般的な意味を脚注として以下にまとめます。
専門用語に関する脚注
1. 複雑さ (Complexity)
- 文中での使われ方: ジョン・アウスターハウト氏によって、「システムを理解し、変更することを困難にするもの」と定義されています。特に、開発者がタスクを遂行するために頭の中に入れておく必要のある情報の量や、その情報がどれだけ見つけにくいか、という「情報」に関連する困難さとして語られています。
- 一般的な意味: ソフトウェアの構造や振る舞いを理解したり、予測したりするのがどれだけ難しいかを示す尺度です。コードの行数、コンポーネント間の依存関係の数、制御フローの入り組んだ度合い(サイクロマティック複雑度など)といった指標で測られることがあります。認知的な負荷の高さとも言えます。
2. 深い/浅いメソッド (Deep / Shallow Methods)
-
文中での使われ方: アウスターハウト氏が提唱する概念です。
- 深い (Deep): インターフェース(メソッドの呼び出し方)は非常にシンプルでありながら、内部では多くの強力な機能を提供しているメソッドを指します。少ない知識で大きな価値を得られるため、良い抽象化とされます。
- 浅い (Shallow): 提供する機能に対してインターフェースが複雑すぎる、あるいはインターフェースから得られる利益が、その内部実装を読む手間と大差ないメソッドを指します。過度な分割によって生じがちで、複雑さを隠蔽できていない状態です。
- 一般的な意味: この用語はアウスターハウト氏の著書『A Philosophy of Software Design』で広まった概念であり、文中での使われ方がそのまま一般的な意味として定着しつつあります。モジュールや抽象化の「質」を評価するための言葉として使われます。
3. 絡み合い / 結合 (Entanglement / Conjoined)
- 文中での使われ方: アウスターハウト氏によって、「あるメソッドの内部の仕組みを理解するために、別のメソッドのコードも読まなければならない状態」と定義されています。コードを読んでいる際に、複数のメソッド定義の間を行ったり来たりするような状況がその兆候だと述べられています。
- 一般的な意味: 一般的には「密結合 (High Coupling)」という用語で知られる状態に近いです。これは、複数のモジュールやクラス、メソッドが互いに強く依存し合っており、一方を変更すると他方にも影響が及ぶ可能性が高い状態を指します。保守性や再利用性を低下させる要因と見なされます。
4. 一つのことルール (One Thing Rule)
- 文中での使われ方: ロバート・マーティン氏が『クリーンコード』で提唱する原則として議論されています。「メソッドは『一つのこと』だけをすべきだ」という考え方です。議論の中では、この「一つのこと」の定義が曖昧であり、過剰な分割を助長する可能性があるとアウスターハウト氏が批判しています。
- 一般的な意味: ソフトウェア設計における有名なヒューリスティック(経験則)の一つです。関数やメソッドの責務を一つに限定することで、コードの可読性、テストの容易さ、再利用性を高めることを目的とします。より厳密な「単一責任の原則(SRP)」をメソッドレベルに適用したものと解釈されることが多いです。
5. テスト駆動開発 (Test-Driven Development - TDD)
- 文中での使われ方: マーティン氏が擁護し、アウスターハウト氏がその有効性に疑問を呈している開発手法です。議論の中心は、TDDが開発者を「戦術的」にしすぎて、より大局的な「戦略的」な設計を妨げるのではないか、という点にあります。
- 一般的な意味: アジャイル開発手法の一つで、製品コードを書く前に、そのコードが満たすべき仕様を定義するテストコードを先に書くというプラクティスです。「レッド・グリーン・リファクター」のサイクルを短時間で繰り返すことを特徴とします。品質向上や設計改善の効果があるとされています。
6. レッド・グリーン・リファクター (Red-Green-Refactor)
- 文中での使われ方: マーティン氏によって、TDDの中核をなす数分単位のループとして説明されています。まず失敗するテストを書き(Red)、次にそのテストをパスさせる最小限のコードを書き(Green)、最後にコードの設計を改善する(Refactor)というサイクルです。
- 一般的な意味: TDDの基本的なワークフローそのものです。このサイクルを繰り返すことで、常にテストに裏付けされた動作するコードを維持しながら、設計を継続的に改善していくことができます。
7. バンドルアプローチ (Bundling Approach)
- 文中での使われ方: この議論の中で、アウスターハウト氏がTDDの代替案として提案したアプローチに名付けられた言葉です。まず設計を考え、ある程度のコードの塊(数十〜数百行のメソッドやクラス)を書き、その後にその「バンドル」に対するユニットテストを書く、という手順を取ります。設計を開発プロセスの中心に据えることを目的としています。
- 一般的な意味: この議論のために作られた造語であり、一般的なソフトウェア開発用語ではありません。TDDの「テストファースト」とは対照的な「デザインファースト」または「コードファースト(ただしテストは必須)」のアプローチを指す言葉として使われています。
8. 抽象化 (Abstraction)
- 文中での使われ方: 両者が重要性を認める概念です。アウスターハウト氏は「重要でない詳細を省略し、物事を単純化して考える方法」、マーティン氏は「本質的なものを増幅し、無関係なものを排除すること」と定義しています。アウスターハウト氏は、良い抽象化にはそれを説明する「コメント」が不可欠だと主張しています。
- 一般的な意味: 複雑な実装の詳細を隠蔽し、利用者が知る必要のある単純なインターフェースだけを公開することです。これにより、利用者は内部の仕組みを知らなくてもその機能を使うことができ、システムの複雑さを管理しやすくなります。オブジェクト指向プログラミングの基本概念の一つです。
💭 なぜ deep/shallow であり rich/poor ではないんだろう?
これは直交する概念かも?最高はdeep/poorで少数の機能で多くのことを正確に実現する。deepは氷山に捉えるとわかりやすく海中には深い意味のある実装が隠れているというふうに理解すると良い。これがちょうどボブおじさんのメソッド細分割への反論のアナロジー的な理解につながる。つまりボブおじさんは一つ前の議論における明示性について話してる。煩雑さへの対応(application)。一方APOSの人はコアライブラリの話をしていて使い分けというよりは自明な利用の話をしていてるのでdoc-stringが厚めになる(tool? 汎用性?)。
(こう書いてて思ったけれど困難さ/煩雑さの話と煩雑さの中の汎用性/明示性の話がごっちゃになってしまいがちなのはなぜなんだろう?)