# ソフトウェア複雑性 ## 定義 ソフトウェア複雑性(software complexity)とは、システムを理解し変更することを困難にする性質全般を指す。*A Philosophy of Software Design* 第1章によれば、プログラムが進化して機能を獲得するにつれてコンポーネント間に微妙な依存関係が生まれ、複雑性は時間とともに不可避に蓄積する。これは開発者が変更のたびに関連する要因を頭の中で把握し続けることを困難にし、開発を遅らせ、バグを生み、そのバグがさらに開発を遅らせコストを増すという悪循環を作る。プログラムが大規模になるほど、また関わる開発者数が多いほど複雑性の管理は難しくなる。開発ツールは複雑性への対処を助けるが、それだけでは限界があり、より強力なシステムをより安価に構築するには、ソフトウェアの設計そのものを単純にする方法を見つけなければならない。 複雑性と戦う一般的アプローチは2つある。第一は、コードをより単純で明白なものにすることで複雑性そのものを排除するアプローチである(特殊ケースの排除、識別子の一貫した使用など)。第二は、複雑性を完全になくすのではなく、モジュール設計によってカプセル化(隠蔽)し、あるモジュールを扱うプログラマが他のモジュールの詳細をすべて理解しなくても作業できるようにするアプローチである。本書はこの2つの軸に沿って以降の章で具体的な設計技法を展開していく。 ソフトウェア開発は本質的に漸進的(incremental)かつ反復的(iterative)な営みであり、設計を前もって完全に確定させることはできない。ソフトウェアは可鍛性(malleability)が高く、実装の途中でも大きな設計変更を許すため、これは建物や船や橋のような物理的システムの設計とは性質が異なる。歴史的にはウォーターフォールモデル(要求定義・設計・実装・テスト・保守を順に完了させ、設計をフェーズ末で凍結するモデル)が取られてきたが、大規模ソフトウェアの設計上の含意を実装前にすべて見通すことは不可能であり、初期設計の問題は実装が進んでから初めて顕在化する。ウォーターフォールモデルはその段階での大幅な設計変更を想定していないため、開発者は全体設計を変えずに継ぎ当てを重ねることになり、結果として複雑性が爆発する。この反省から、今日の多くの開発プロジェクトはアジャイル開発のような漸進的アプローチを採り、小さな機能部分集合を設計・実装・評価するサイクルを繰り返すことで、システムがまだ小さいうちに初期設計の問題を修正する。漸進的開発のもとでは設計は決して完了しない。初期設計がベストであることはほとんどなく、開発者は常に設計課題・複雑性について考え、時間の一部を設計改善に充てるべきだ、というのが本書の立脚点である。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 1 Introduction]]) 第2章はこの複雑性を実務的に定義する。複雑性とは、システムの構造に関係し、そのシステムを理解し変更することを困難にするものすべてである。コードの動作が読み取りにくい、小さな改善に大きな労力がかかる、どこを直せばよいか不明瞭である、あるバグを直すと別のバグが生まれる、といった事象はすべて複雑性の現れであり、システムが理解・変更しやすければ単純、そうでなければ複雑と判定される。複雑性はある開発者がある時点である目標を達成しようとする際に体験するものであり、システム全体の規模や機能の豊富さそのものとは必ずしも一致しない。大規模で高機能なシステムはしばしば複雑性の定義も満たすが、それは扱いにくいことが多いからにすぎず、扱いやすい大規模システムは本書の定義上「複雑」ではない。逆に小規模で機能が少ないシステムでも十分複雑になりうる。複雑性は最も頻繁に行われる活動によって決まる度合いでもあり、システムの一部がどれほど複雑でも、そこがめったに触れられないなら全体の複雑性への影響は小さい。著者はこれを粗い数式で表す。システム全体の複雑性 C は、各部分 p の複雑性 c_p を、開発者がその部分に費やす時間の割合 t_p で重みづけした総和(C = Σ c_p · t_p の趣旨)であり、複雑性を決して見られない場所に封じ込めることは、それをほぼ完全に排除するのとほぼ同等の効果を持つ。 この定義における最重要の立場は、**複雑性は書き手より読み手の目で測られる**ということである。自分が書いたコードを単純だと思っても、他の開発者がそれを複雑だと言うなら、それは複雑である。この食い違いに気づいたときは、なぜ相手にとって複雑に見えるのかを尋ねる価値があり、自分の意見と相手の意見の断絶から学べる教訓が多い。開発者の仕事は自分が扱いやすいコードを書くことだけでなく、他者も扱いやすいコードを書くことである。 複雑性は3つの症状として現れる。第一に**変更増幅(change amplification)**、一見単純な変更が多数の異なる箇所のコード修正を要求すること。第二に**認知負荷(cognitive load)**、あるタスクを完了するために開発者が知っておく必要のある情報量。認知負荷は行数だけでは測れず、行数が少なくても把握が難しい実装より、行数が多くても認知負荷が低い実装の方が単純な場合がある。第三に**未知の未知(unknown unknowns)**、どのコードを変更すべきか、あるいはタスク遂行にどんな情報が必要かが自明でない状態であり、著者はこの未知の未知を3症状のうち最悪のものだと明言する。変更増幅や高い認知負荷は、修正箇所や必要情報さえ分かれば変更を正しく完了できるが、未知の未知では何をすべきかも、提案した解決策が機能するかどうかさえ分からない。 複雑性の原因は2つに絞り込まれる。**依存(dependency)**と**不明瞭さ(obscurity)**である。依存とは、あるコード片が単独では理解・変更できず、他のコードとの関係を考慮しなければならない状態を指す。依存はソフトウェアに不可欠で完全には排除できず、設計を行うたびに意図的に依存を作り出してもいるため、設計の目標は依存の数を減らし、残った依存をできるだけ単純かつ自明にすることになる。不明瞭さとは、重要な情報が自明でない状態を指し、しばしば依存の存在そのものが不明瞭であることに起因する。依存は変更増幅と高い認知負荷を、不明瞭さは未知の未知と(部分的に)認知負荷を生む。 最後に複雑性は**漸増的(incremental)**である。単一の破局的な誤りによってではなく、何百・何千という小さな依存や不明瞭さが積み重なることで生じる。1件の変更が持ち込む複雑性は個別には「大したことはない」と正当化されがちだが、全開発者がその態度を取れば複雑性は急速に蓄積し、いったん蓄積すると1件を直しても目に見える改善は得にくい。複雑性の増大を抑えるには「ゼロ・トレランス」の哲学が要るとされる(第3章で詳述)。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 2 The Nature of Complexity]]) 第18章は、第2章が定義した複雑性の原因のうち「不明瞭さ(obscurity)」の裏返しである**明白さ(obviousness)**への対処を扱う。コードが明白であるとは、読み手が一読しただけで動作や意味についての最初の推測が正しくなる性質を指す。著者はここでも「明白さは読み手の心の中にある」と明言し、書き手は自分のコードの不明瞭さには気づきにくく、他人のコードの不明瞭さのほうが気づきやすいと指摘する。したがって明白さを判定する唯一確実な方法はコードレビューであり、レビュアーが「明白でない」と言えば、それが書き手にどれほど明白に見えようと不明瞭だということになる。明白さを高める主要技法は、既に論じられた良い命名(第14章)と一貫性(第17章)に加え、空白の judicious な使用とコメントによる補完である。逆に明白さを損なう要因として、イベント駆動プログラミング、汎用コンテナ、宣言と割り当ての型の不一致、読み手の期待に反する振る舞いの4つが挙げられる。著者はこれらを踏まえ、ソフトウェアは書きやすさ(ease of writing)ではなく読みやすさ(ease of reading)のために設計すべきだという設計原則(本書巻末 Summary of Design Principles の14番目)を導く。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 18 Code Should be Obvious]]) 1996年出版の *Handbook of Software Reliability Engineering* 第12章は、これとは大きく異なる**測定工学の伝統**からソフトウェア複雑性を扱う。Munson & Khoshgoftaar は複雑性を「コードそのものから客観的に測定できる静的属性」とみなし、Halstead のソフトウェアサイエンス(演算子・被演算子の総数・一意数から合成するプログラム量 $V$・推定労力 $\hat E$ 等、サイズ複雑度を捉える)と McCabe の循環的複雑度 $V(G)=e-n+p$(制御フローグラフの独立経路数、制御フロー複雑度を捉える)を代表的な複雑性メトリクスとして提示する。これらのメトリクスは互いに強く相関する(多重共線性)ため、主成分分析で少数の直交ドメイン(典型的にはサイズ系・制御フロー系)へ縮約し、その固有値加重和として単一の**相対複雑度 ρ**(平均50・標準偏差10に尺度化)を定義する。相対複雑度はモジュールの故障数・変更数と統計的に関連づけられ、判別分析・重回帰分析による故障傾向モジュールの予測に使われる。(Source: [[@1996__McGrawHill__Handbook of Software Reliability Engineering - Chapter 12 Software Metrics for Reliability Assessment]]) ## 横断的知見 - **「複雑性は読み手が体験する主観的性質」対「複雑性は静的メトリクスで客観的に測定できる」という、22年隔てた二つの伝統の対比**: *A Philosophy of Software Design*(2018)は本ページの定義の核として「複雑性は書き手より読み手の目で測られる」と明言し、変更増幅・認知負荷・未知の未知という**開発者の主観的体験**として複雑性を定義する。これに対し *Handbook of Software Reliability Engineering* 第12章(1996)の Munson & Khoshgoftaar は、複雑性を LOC・Halstead ソフトウェアサイエンス・McCabe 循環的複雑度といった**コードから機械的に算出できる静的メトリクス**として扱い、主成分分析による直交化を経て単一の相対複雑度 ρ に集約し、故障数と回帰的に結びつける。前者が「複雑性は誰かがコードを読んで初めて生じる」という認識論的な立場を取るのに対し、後者は「複雑性はコードが書かれた時点で決定される測定可能な量である」という存在論的な立場を取る。両者は同じ語(complexity)を使いながら、測定可能性についてほぼ正反対の前提から出発している点で対照的である。ただし1996年の著者ら自身も「測れるから測る」ことには無批判ではなく、McCabe の $V(G)$ に一律の magic value(閾値15など)を設計規約として課す実務を、モジュール分割がかえってモジュール結合複雑性とシステム全体の故障数を増やしうると批判しており、この点は「複雑性の低減それ自体が目的化する」ことへの警戒として、Ousterhout の議論と遠くない直感を共有する。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 2 The Nature of Complexity]], [[@1996__McGrawHill__Handbook of Software Reliability Engineering - Chapter 12 Software Metrics for Reliability Assessment]]) - **結論章による定義の再確認と投資回収という新たな主張**: 第21章(Conclusion)は本書が「複雑性という一つの主題のみ」を扱ってきたと明言し、第1・2章が定義した複雑性(依存・不明瞭さを原因とし、変更増幅・認知負荷・未知の未知として現れる性質)への対処が「ソフトウェア設計における最も重要な課題」であると総括する。これは第1・2章の定義そのものを繰り返すのではなく、本書全体が単一の目標(複雑性への対処)のもとに一貫して組み立てられていたことを最終章で確認する働きをしている。加えて第21章は、第1・2章や第18章では扱われなかった論点として、複雑性削減への投資(深く汎用的なモジュール設計・丁寧な文書化・設計スキルの向上)は初期コストを伴うが、モジュールの再利用や後の変更のたびに時間を節約する形ですぐに回収されると主張する。すなわち複雑性は単に「測定・診断すべき性質」であるだけでなく、「その削減に投資すれば早期に元が取れる対象」でもあるという経済的な側面を、本書は結論部で付け加えている。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 2 The Nature of Complexity]], [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 21 Conclusion]]) - [[本質的複雑性と偶発的複雑性]](既存 concept)は Fred Brooks の区別に依拠し、複雑性を「本質的(問題そのものに内在し除去不可能)」と「偶発的(エンジニアリング努力で解消可能)」という**発生源**で分類する。これに対し本書第2章は発生源による分類を採らず、変更増幅・認知負荷・未知の未知という**観測可能な症状**と、依存・不明瞭さという**構造的原因**で複雑性を定義する。両者は「複雑性をどう切り分けるか」という同じ問題への異なるアプローチであり、本書のアプローチは Brooks/SRE Book の分類が抱えていた「本質か偶発かをどう判定するか、具体的な手続きが示されていない」という課題に対し、少なくとも原因側(依存・不明瞭さの有無)は個々のコード片について観察・特定できる形で提示している点で対照的である。(Source: [[@2016__OReilly__SRE Book - Chapter 9 Simplicity]], [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 2 The Nature of Complexity]]) - 第2章が定義した複雑性の原因のうち「不明瞭さ」は、第18章でレッドフラグ「Nonobvious Code」として具体化される。第2章は不明瞭さを「重要な情報が新しい開発者にとって自明でない状態」とだけ定義し対処には踏み込まなかったが、第18章はその裏返しである「明白さ(obviousness)」を、読み手が判定する性質・コードレビューという判定手段・良い命名や一貫性という既出の対処技法・イベント駆動プログラミングや汎用コンテナ等の悪化要因という形で具体化する。両章を突き合わせると、第2章が「複雑性は書き手ではなく読み手が体験する」と定義した立場を、第18章は「明白さは読み手の心の中にある」という形でそのまま引き継いでおり、複雑性論全体を貫く一貫した視点になっている。もう一つの原因である「依存」の同様の具体化は、深いモジュール(第4章)・情報隠蔽(第5章)など本書前半の章が担う。(Source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 2 The Nature of Complexity]], [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 18 Code Should be Obvious]]) - **ドメイン駆動設計(DDD)は、SRE本第9章が「本質的複雑さを小さく管理しやすい塊に分割する」技法として名指しした唯一の具体的手法であり、本ページの本質的/偶発的複雑性の分類と第2章の依存・不明瞭さによる原因分類のどちらに位置づくかを検討する材料になる**: 『SREエンタープライズロードマップ』第3章は SRE 本第9章(簡潔性)を要約する中で、偶発的な複雑さを減らすインセンティブを与える一方、本質的に複雑なものは DDD のようなより小さく管理しやすい塊に分割すべきだと述べる。これは Brooks/SRE Book の「本質的複雑性は除去不可能」という立場(本ページ既出の横断的知見)を前提としつつ、除去できないなら「小さく管理しやすい塊に分割する」という対処法を具体的に示す点で、本質的複雑性への処方箋が定義だけにとどまらないことを補う。ただし DDD による分割は、*A Philosophy of Software Design* 第2章の語彙で言えば「依存を減らす」というより「認知負荷を配分し直す」(担当者ごとに把握すべき情報量を小さくする)ことに近く、依存・不明瞭さという構造的原因の分類のどちらに厳密に位置づくかは、両ソースを突き合わせただけでは確定できない (Source: [[@2022__OReillyJapan__SREエンタープライズロードマップ - Chapter 3 SREの原則]], [[@2016__OReilly__SRE Book - Chapter 9 Simplicity]], [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 2 The Nature of Complexity]])。 - **ローコンテキスト/ハイコンテキストという DevOps 由来の軸は、第18章「明白さ(obviousness)」が扱う不明瞭さの解消技法と同じ機能を、個々のコードでなく組織のプロセス全体に対して担う**: 第3章は、SRE がプレイブック・ドキュメント・DiRT(Disaster Recovery Testing)演習を通じて「物事をローコンテキスト化する」ことの重要性を DevOps から再利用する概念として挙げる。*A Philosophy of Software Design* 第18章は、コードにおける不明瞭さの解消を「読み手が一読しただけで動作の推測が正しくなる」明白さの追求として論じたが、対象はあくまでソースコードの命名・一貫性・コメントに閉じていた。第3章のローコンテキスト化は同じ「読み手(この場合は障害対応者・新任者)が前提知識なしに理解できる」という目標を、コードでなくプレイブックや訓練という組織的な成果物に対して適用したものであり、明白さという概念の射程がコードの外側(運用ドキュメント・組織プロセス)にも一般化できることを示唆する (Source: [[@2022__OReillyJapan__SREエンタープライズロードマップ - Chapter 3 SREの原則]], [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 18 Code Should be Obvious]])。 ## 未解決の問い - 複雑性を「排除する」アプローチと「カプセル化する」アプローチは、実際の設計判断の場面でどちらを優先すべきか、あるいはどう組み合わせるべきか。本書は第2章時点では複雑性の定義・症状・原因のみを扱い、この優先順位には答えていない。以降の章(第4章「Modules Should Be Deep」等)で解消されるべき問い。 - アジャイル開発のような漸進的アプローチは複雑性の「爆発」を防ぐと第1章で述べられるが、漸進的開発それ自体が複雑性を生む(場当たり的な設計変更の蓄積)リスクとどう両立するのか。本書後半(第16章「Modifying Existing Code」等)で扱われる可能性がある。 - 依存・不明瞭さという構造的原因による複雑性の分類と、Brooks/SRE Book の本質的/偶発的という発生源による分類は、両立する2つの軸なのか、それとも一方が他方を包摂するのか。両分類を同一のコード片に適用した具体例が今のところどちらのソースにもなく、未検証。 - Munson & Khoshgoftaar の測定可能な静的メトリクス(McCabe・Halstead・相対複雑度 ρ)は、Ousterhout の依存(dependency)・不明瞭さ(obscurity)という構造的原因のどちらに対応するのか、あるいはどちらにも対応しないのか。循環的複雑度は制御フローの分岐数を数える点で「依存」に近そうだが、両ソースを突き合わせただけでは対応関係を確定できない。今後、複雑性メトリクスの実証研究(SDP: Software Defect Prediction 系)を ingest する際に検討したい。 ## 関連 - [[本質的複雑性と偶発的複雑性]](既存 concept。発生源による分類。本書の症状・原因ベースの分類との突き合わせは上記「横断的知見」参照) - [[技術的負債]](既存 concept。複雑性の蓄積と技術的負債の関係は後続章の ingest 時に検討する) - [[主成分分析]](Munson & Khoshgoftaar の複雑性メトリクス直交化の手法的基盤) / [[障害予測]](相対複雑度 ρ と故障数の統計的関連づけ) - 実体: [[John Ousterhout]] / [[John Munson]] / [[Taghi Khoshgoftaar]] / 書籍: [[wiki/entities/A Philosophy of Software Design|A Philosophy of Software Design]] / [[wiki/entities/Handbook of Software Reliability Engineering|Handbook of Software Reliability Engineering]] - source: [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 1 Introduction]] / [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 2 The Nature of Complexity]] / [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 18 Code Should be Obvious]] / [[@2018__YaknyamPress__A Philosophy of Software Design - Chapter 21 Conclusion]] / [[@2022__OReillyJapan__SREエンタープライズロードマップ - Chapter 3 SREの原則]] / [[@1996__McGrawHill__Handbook of Software Reliability Engineering - Chapter 12 Software Metrics for Reliability Assessment]] - MOC: [[Software Engineering - MOC]] ## 出典 - John Ousterhout, *A Philosophy of Software Design*, Yaknyam Press, 2018, Chapter 1–2, 18, 21. - James Brookbank, Steve McGhee 著, 山口 能迪 訳, 『SREエンタープライズロードマップ』, Google Japan G.K., 2022, 第3章(SRE本第9章「簡潔性」の要約、DDD による本質的複雑さの分割、ローコンテキスト/ハイコンテキストの比較) - John C. Munson, Taghi M. Khoshgoftaar, "Software Metrics for Reliability Assessment", in Michael R. Lyu (ed.), *Handbook of Software Reliability Engineering*, IEEE Computer Society Press / McGraw-Hill, 1996, Chapter 12, §12.2.1–12.2.4.