John Ousterhoutの実践的設計観を探り、Tclの遺産、OusterhoutとBrooksの関係、そして複雑性が製品にもたらすコストについて解説します。

John Ousterhoutは研究と実システムの両方にまたがるコンピュータ科学者・エンジニアです。彼はTclを創り、現代のファイルシステム設計にも寄与し、長年の経験を一つのシンプルでやや耳障りな主張に凝縮しました:複雑性はソフトウェアの最大の敵である。
このメッセージが今も有効なのは、ほとんどのチームが機能不足や努力不足で失敗するのではなく、システム(と組織)が理解しにくく、変更しにくく、壊れやすくなることで失敗するからです。複雑性はエンジニアのスピードを遅くするだけでなく、製品意思決定、ロードマップの確信度、顧客信頼、インシデント頻度、さらには採用にまで漏れ出します—オンボーディングが何ヶ月もかかるようになるからです。
オースターハウトのフレームは実践的です:システムが特殊ケースや例外、隠れた依存、そして「今回はこれでいいか」という修正を蓄積するとき、そのコストはコードベースに留まりません。製品全体が進化させるのに高くつくようになります。機能は時間がかかり、QAは難しくなり、リリースはリスクが高まり、チームは何かに手を付けること自体を避け始めます。
これは学術的純度を求める呼びかけではありません。すべての近道には利子が付きます—そして複雑性は最も利息の高い負債です。
アイデアを具体化するため、以下の三つの角度からオースターハウトの主張を見ます:
これは言語マニア向けだけではありません。製品を作る人、チームを率いる人、ロードマップのトレードオフを決める人であれば、複雑性を早期に見抜き、それが制度化するのを防ぎ、単純さを第一級の制約として扱うための実行可能な方法を学べます。
複雑性は「コードが多い」や「難しい数式」ではありません。それは、変更したときにシステムがどう振る舞うかとあなたが思っていることの間のギャップです。小さな編集がリスキーに感じられるとき、つまり影響範囲を予測できないとき、そのシステムは複雑です。
健全なコードでは次の質問に答えられます:「これを変えたら他に何が壊れるか?」複雑性はその問いを高価にします。
よく隠れているのは:
チームは複雑性を出荷の遅れ(調査に時間を取られる)、バグの増加(振る舞いが予測できないため)、脆弱なシステム(変更に多くの人やサービスの調整が必要)として感じます。それはオンボーディングにも負荷をかけます:新しい仲間はメンタルモデルを構築できず、コアフローに触るのを避けます。
一部の複雑性は本質的です:ビジネスルール、コンプライアンス要件、現実世界のエッジケース。これらは削れません。
しかし多くは偶発的です:わかりにくいAPI、重複ロジック、「一時的」なフラグが恒久化すること、内部詳細を漏らすモジュール。これが設計上の選択が生み出す複雑性であり、継続的に削減できる唯一の種類です。
Tclは実務的な目標から出発しました:ソフトウェアを自動化し、既存アプリケーションを再構築せずに拡張するのを簡単にすること。Ousterhoutは「十分なプログラマビリティ」をツールに与え、ユーザーやオペレーター、QAなどがワークフローをスクリプト化できるように設計しました。
Tclはグルー言語の概念を普及させました:小さく柔軟なスクリプト層で、より速い低レベル言語で書かれたコンポーネントを繋ぎます。すべてをモノリスに組み込む代わりに、コマンド群を公開してそれらを組み合わせて新しい振る舞いを作れます。
このモデルが影響力を持ったのは、実際の仕事の流れに合っていたからです。人々は製品だけを作るのではなく、ビルドシステム、テストハーネス、管理ツール、データ変換器、ワンオフ自動化を作ります。軽量のスクリプト層はそれらを「チケットを切る」から「スクリプトを書く」へと変えます。
Tclは組み込みを第一級の関心事にしました。インタープリタをアプリに落とし込み、クリーンなコマンドインターフェースをエクスポートすれば、即座に設定可能性と高速な反復が得られます。
このパターンは今日のプラグインシステム、設定言語、拡張API、組み込みスクリプトランタイムの至る所に現れます—構文がTclに似ているかどうかは関係ありません。
また安定したプリミティブ(ホストアプリのコア能力)と変更されやすい合成(スクリプト)を分離する設計習慣を強めました。うまく行けば、ツールはコアを不安定にせずに速く進化します。
Tclの構文や「すべてが文字列」モデルは直感的でないと感じられることがあり、大きなTclコードベースは強い慣習なしには理解しづらくなります。より豊富な標準ライブラリ、より良いツール、より大きなコミュニティを提供する新しいエコシステムが現れると、多くのチームは移行しました。
しかしそれはTclの遺産を消しません:拡張性と自動化が単なるオプションではなく、製品機能であり、システムを使う人や維持する人の複雑性を劇的に下げうるという考えを普通にしたのです。
Tclは一見厳格な考えに基づいて作られていました:コアを小さく保ち、合成を強力にし、スクリプトを十分に読みやすくして人々が常に翻訳を必要とせずに協働できるようにする。
多くの特殊化された機能を出すより、Tclはプリミティブ(文字列、コマンド、簡単な評価規則)をコンパクトにし、それらを組み合わせることを期待しました。
この哲学は設計者を少ない概念に向かわせます。もし2〜3の一貫したビルディングブロックで10のニーズを解決できるなら、学ぶ必要のある表面積が縮まります。製品やAPI設計における教訓は明快です。
設計の罠は作り手の利便性を最適化することです。ある機能は実装が簡単(既存のオプションをコピー、特殊フラグを追加、角ケースにパッチ)でも、製品を使うのを難しくします。
Tclの強調はその逆でした:メンタルモデルを締めること(単純に保つこと)。実装が裏で多く働かなければならなくても、ユーザーが覚える概念を増やさない方が良い。
提案をレビューするときは問うべきです:これはユーザーが覚える概念の数を減らすか、例外を一つ増やしていないか?
ミニマリズムはプリミティブが一貫しているときに助けになります。似た二つのコマンドが端ケースで微妙に振る舞いが異なると、ユーザーは些細な知識を覚えざるをえません。少数のツールでも規則が微妙に変わると「鋭いエッジ」になります。
キッチンを想像してください:良い包丁、フライパン、オーブンがあれば多くの料理を作れます。アボカドだけをスライスするガジェットはワンオフ機能です—売りやすいが引き出しを散らかします。
Tclの哲学は包丁とフライパンを推します:汎用的な道具がきれいに合成できるようにして、毎レシピごとに新しいガジェットを必要としないようにする。
1986年、Fred Brooksは挑発的な結論を含むエッセイを書きました:ソフトウェア開発を一気に桁違いに速く、安く、信頼性を高める単一のブレークスルー("銀の弾")は存在しない、と。
彼の主張は進歩が不可能だという意味ではありません。ソフトウェアはほとんど何でも作れる媒体であり、その自由は独特の負担を伴います:我々は作りながら常に作る対象を定義しているのです。より良いツールは助けになりますが、最も難しい部分を消し去るわけではないと彼は言います。
ブルックスは複雑性を二つに分けました:
ツールは偶発的複雑性を圧縮できます。高水準言語、バージョン管理、CI、コンテナ、マネージドDB、良いIDEから得たものを思い出してください。しかしブルックスは本質的複雑性が支配的であり、ツールの改善だけでは消えないと主張しました。
現代のプラットフォームがあっても、チームは要求交渉、システム統合、例外処理、振る舞いの一貫性維持にエネルギーの多くを使います。表面は変わる(デバイスドライバの代わりにクラウドAPI)が、核心の課題は変わりません:人間の要求を正確で保守可能な振る舞いに翻訳することです。
この点がオースターハウトが突っ込む緊張点です:本質的複雑性を消せないなら、規律ある設計でどれだけ「どれだけがコードに漏れるか」を減らせるか?
人々は時に「オースターハウト対ブルックス」を楽天主義と現実主義の対立のように表現しますが、経験ある二人のエンジニアが同じ問題の異なる側面を説明していると読む方が有益です。
ブルックスの“No Silver Bullet”は単一の魔法がハードな部分を消さないと主張しますが、オースターハウトはそれ自体に異議は唱えません。
彼の反論はより狭く実践的です:多くのチームは複雑性を避けられないものとして扱いますが、その多くは自己誘発的です。優れた設計は複雑性を意味のある程度で減らせます—ソフトウェアを「簡単」にするわけではなく、変更しにくさを減らすのです。混乱が日々の作業を遅くするのだから、それは大きな主張です。
ブルックスは本質的困難に注目します。ソフトウェアは厄介な現実や変化する要求、コード外に存在するエッジケースをモデル化しなければなりません。優れたツールや賢い人々がいても、それらは消えません。管理するだけです。
論争より両者の合意点は多いです:
「どちらが正しいか?」と問う代わりに、こう問ってください:今四半期にコントロールできる複雑性はどれか?
チームは市場の変化やドメインの本質的難易度はコントロールできません。しかし、新機能が特殊ケースを増やすか、APIが呼び出し側に隠れたルールを覚えさせるか、モジュールが複雑さを漏らすかどうかはコントロールできます。
ここに実行可能な中間地帯があります:本質的複雑性を受け入れ、偶発的複雑性には容赦なく厳しくなること。
ディープモジュールは多くのことを行い、少ない理解しやすいインターフェースを公開します。「深さ」はモジュールが肩代わりする複雑性の大きさを表します:呼び出し側は厄介な詳細を知らなくてよく、インターフェースも強制しません。
浅いモジュールは逆です:小さなロジックをラップするだけで、パラメータや特殊フラグ、呼び出し順、あるいは「覚えておけ」のルールを通じて複雑性を外側に押し出します。
レストランで考えてください。深いモジュールはキッチン:メニューから「パスタ」と頼めば仕入れや茹で時間、盛り付けを気にしなくて良い。
浅いモジュールは材料と12ステップの説明書を渡して自分の鍋を持ってこさせる「キッチン」です。仕事はまだ行われますが、顧客の肩に移っています。
新しい層は多くの決定を一つの明白な選択にまとめるなら有益です。
例:save(order) を公開し、内部でリトライ、シリアライズ、インデックスを処理するストレージ層は深いモジュールです。
層が嫌いなのは、名前を変えるかオプションを増やすだけのときです。もし新しい抽象が取り除くより多くの設定を導入するなら――例えば save(order, format, retries, timeout, mode, legacyMode)――それは浅い可能性が高い。コードは「整理されて見える」が、認知負荷はすべての呼び出し箇所に現れます。
useCache, skipValidation, force, legacyのようなブール)がある。ディープモジュールは単にコードをカプセル化するだけでなく、意思決定をカプセル化します。
良いAPIとは多機能なだけではありません。人が作業中に頭の中に保持できるものです。
オースターハウトの設計レンズは、APIを思考量で評価することを促します:覚えるべきルールの数、予測すべき例外の数、間違ったことをしてしまう容易さ。
人間に優しいAPIは小さく、一貫していて、誤用しにくいことが多いです。
小さいことは乏しいことを意味しません—表面積が少数の組み合わせ可能な概念に集中していることを意味します。一貫性はシステム全体で同じパターン(パラメータ、エラーハンドリング、命名、戻り値)で動くことを意味します。誤用しにくいとは、安全な経路へ導くこと:境界での検証、早期に失敗する型や実行時チェックなど。
追加されるフラグやモード、「念のため」の設定はすべてユーザーに対する税です。たとえ5%の呼び出し側だけが必要としても、100%の呼び出し側がそれの存在を学び、必要かどうか迷い、他のオプションとどう相互作用するかを考える必要があります。
APIが隠れた複雑性を蓄積するのは、単一の呼び出しではなくその組合せの指数性です。
デフォルトは親切です:大多数の呼び出し側が意思決定を省略しても合理的な振る舞いを得られます。慣習(一つの明白なやり方)はユーザーの頭の中の分岐を減らします。命名も実作業を助けます:意図に合った動詞と名詞を選び、類似操作は類似命名にする。
もう一つの注意点:内部APIは公開APIと同じくらい重要です。製品の複雑性の大部分は裏側にあります—サービス境界、共有ライブラリ、ヘルパーモジュール。これらのインターフェースを製品として扱い、レビューとバージョニングの規律を適用してください(参照:/blog/deep-modules)。
複雑性は一度にやってくることは稀です。締め切り圧力の下で正当なように見える小さなパッチが蓄積していきます。
ひとつの罠は機能フラグの氾濫です。フラグはロールアウトに有用ですが残ると各フラグが可能な振る舞いの数を増やします。エンジニアは「システム」についてではなく「フラグAがオンでユーザーがセグメントBのとき以外のシステム」について考え始めます。
もうひとつは特殊ケースロジック:"エンタープライズ顧客はXが必要"、"地域Yの場合は例外"、"アカウント作成から90日以上経過していると除外"。これらはコードベースに散らばり、数ヶ月後にはどれがまだ必要か誰も把握していないことが多い。
三つ目は漏れる抽象です。APIが呼び出し側に内部詳細(タイミング、ストレージ形式、キャッシュ規則)を理解させると複雑性は外側に広がります。1つのモジュールが負担する代わりに、すべての呼び出し側が扱うことになります。
戦術的プログラミングは今週を最優先します:迅速な修正、最小の変更、“出すこと”が目的。
戦略的プログラミングは来年を最優先します:同じ種類のバグを防ぎ将来の作業を減らす小さな再設計。
危険なのは「メンテナンス利息」です。迅速な回避策は今は安く感じますが、利息を支払う羽目になります:オンボーディングの遅れ、壊れやすいリリース、誰も触りたがらない古いコード。
コードレビューに軽い問いを追加してください:「これ新しい特殊ケースを追加していないか?」「APIはこの詳細を隠せるか?」「ここに残す複雑性は何か?」
重要なトレードオフは短い決定記録に残す(数行で十分)。そして各スプリントに小さなリファクタ予算を確保して、戦略的修正が課外活動扱いにならないようにします。
複雑性はエンジニアリングに閉じたものではありません。スケジュール、信頼性、顧客の体験に漏れ出します。
システムの理解が難しいとすべての変更に時間がかかります。リリースはより多くの調整、より多くの回帰テスト、「念のため」レビューを必要とし、タイム・トゥ・マーケットが遅れます。
信頼性も損なわれます。複雑なシステムは誰も完全に予測できない相互作用を生み、バグはエッジケースとして現れます:クーポン、保存済みカート、地域税ルールが特定の組み合わせになったときだけチェックアウトが失敗する、など。こうしたインシデントは再現が難しく修正も遅くなりがちです。
オンボーディングは目に見えない重荷になります。新メンバーは有益なメンタルモデルを構築できず、リスクのある領域には触れず、理解しないままパターンをコピーしてさらに複雑性を増やします。
顧客は振る舞いが「特殊ケース」由来かどうかを気にしません。彼らには一貫性のなさとして届きます:設定がすべてに適用されない、到達経路でフローが変わる、機能が「大抵動くがときどき動かない」。信頼が低下し、離脱が増え、採用が停滞します。
サポートは長いチケットと往復により複雑性を支払い、運用はアラートやランブック、慎重なデプロイで支払います。すべての例外は監視・文書化・説明が必要になります。
「もう一つ通知ルールを追加してほしい」という要望を想像してください。追加は迅速に思えますが、振る舞いの分岐を増やし、UIコピー、テストケース、ユーザーの誤設定の可能性を増やします。
一方で既存の通知フローを単純化することは:ルールタイプを減らし、明確なデフォルトを設け、Webとモバイルで一貫した振る舞いにすること。ノブは少なくなりますが驚きが減り、製品は使いやすく、サポートしやすく、進化が速くなります。
複雑性を性能やセキュリティと同様に計画し、測り、保護してください。複雑性が出荷の遅延で初めて見えるなら、すでに利子を払っています。
機能スコープと並んで、リリースが導入できる新たな複雑性の量を定義します。予算は単純で良い:「新しい概念を追加するなら必ず一つ削る」や「新しい統合は古い経路を置き換えること」など。
計画時にトレードオフを明示してください:ある機能が3つの新しい設定モードと2つの例外を必要とするなら、既存概念に収まる機能よりも"コスト"を高く設定するべきです。
完璧な数値は不要です—方向性を示すシグナルがあれば良い:
これらをリリース毎に追い、決定に結び付けます:「公開オプションを2つ増やした;代わりに何を削除または簡素化したか?」
プロトタイプを「作れるか?」だけで評価するのはやめてください。代わりに「使っていて単純に感じ、誤用しにくいか?」を答えさせてください。
機能に詳しくない人に現実的なタスクをプロトタイプで試してもらい、成功までの時間、尋ねた質問、誤った仮定を測ります。そこが複雑性のホットスポットです。
この点で現代のビルドワークフローは偶発的複雑性を減らすのに役立ちます—ただし繰り返しが短くミスを元に戻しやすいことが前提です。たとえばチームが Koder.ai のようなビブコーディングプラットフォームを使ってチャット経由で社内ツールや新しいフローをスケッチする場合、planning mode(意図を明確にする)や snapshots/rollback(危険な変更を素早く元に戻す)といった機能は初期の実験を安全に感じさせます。プロトタイプが本採用されるなら、ソースコードをエクスポートしてここで述べたディープモジュールやAPIの規律を適用できます。
「複雑性の掃除」を定期化(四半期ごと、またはメジャーリリースごと)し、「完了」の意味を定義します:
目的は抽象的な「きれいなコード」ではなく、概念数の減少、例外の減少、安全に変更できることです。
Ousterhoutの「複雑性が敵である」という考えを週間単位の習慣に落とし込むためのいくつかの動きです。
混乱を引き起こしているサブシステムを一つ選んでください(オンボーディングの痛み、繰り返すバグ、「これはどう動く?」質問が多い箇所)。
内部フォローとしては、計画時の「複雑性レビュー」(/blog/complexity-review) やツール群が偶発的複雑性を減らしているかを素早くチェックする(/pricing)などが考えられます。
最後に一つ問いかけ:今週、もし一つだけ特殊ケースを削除できるとしたら、あなたは何を最初に削りますか?
複雑性とは、システムを変更したときに「起こるだろう」と期待することと、実際に起こることの差です。
小さな変更がリスクに感じられるとき、つまり影響範囲(テスト、サービス、設定、顧客、あるいはエッジケース)を予測できないときに複雑性を実感します。
次のような兆候を探してください:
本質的複雑性はドメインから来ます(規制、現実世界のエッジケース、ビジネスルール)。これは削除できず、正しくモデル化するしかありません。
偶発的複雑性は自分たちで招いたものです(漏れる抽象、重複ロジック、モードやフラグの氾濫、不明瞭なAPI)。これは設計や簡素化作業で着実に減らせます。
**ディープモジュール(深いモジュール)**は多くの処理を内部で担い、外部には小さく理解しやすいインターフェースを公開します。リトライ、シリアライズ、順序、不可変条件などの「面倒」を吸収するので、呼び出し側は内部ルールを知らなくてよいことが多いです。
実用的なテスト:ほとんどの呼び出し側が内部ルールを知らずに正しく使えるなら深い。順序や規則を覚えさせないと使えないなら浅い(shallow)です。
共通の症状:
legacy, skipValidation, force, modeなど)。浅いモジュールは一見整理されて見えますが、複雑性を呼び出し側に移してしまいます。
設計の実践ルール:
「もう一つオプションを追加しよう」と思ったら、多くの呼び出し側がその選択を考えなくて済むようにインターフェースを再設計できないかまず問うべきです。
機能フラグは安全なロールアウトに有用ですが、長引くと各フラグが振る舞いの数を増やします。管理方法の例:
長期化したフラグはエンジニアが考える「システム」の数を増やします。
ロードマップで複雑性を明示的に扱うことです:
複雑性を計画段階で可視化し、組織的に取引できるようにします。
タクティカル(戦術的)プログラミングは今週を最適化します:素早いパッチ、最小変更、すぐ出す。
ストラテジック(戦略的)プログラミングは来年を最適化します:再発するクラスのバグを防ぎ将来の作業を減らす小さな再設計。
実用的なヒューリスティック:修正に「呼び出し側の知識」(「先にXを呼べ」や「本番でこのフラグを立てろ」)が必要なら、複雑性をモジュール内部に隠す戦略的な変更が求められます。
Tclの教訓は、小さなプリミティブと強力な合成の力です。今日の類似例:
設計目標は同じ:コアをシンプルで安定に保ち、変化はクリーンなインターフェースを通じて起こすこと。