世界を動かす技術を、日本語で。

認知負荷が重要である

2025年8月30日原文(github.com)

概要

  • 認知負荷 がソフトウェア開発における最重要課題であることを解説
  • 認知負荷の種類 とその削減方法を具体例で紹介
  • 深いモジュール浅いモジュール の違い、設計原則への影響を説明
  • マイクロサービスや言語機能 が認知負荷へ与える影響を考察
  • 実用的なアドバイス と実体験をもとに、より良い設計選択を提案

認知負荷が重要な理由

  • ソフトウェア開発における 混乱や遅延の主因 は認知負荷にあり
  • コードを読む際、 変数値・制御フロー・呼び出し順序 などを短期記憶に保持する必要性
  • 一般的な人間は 4つ程度の情報 しか同時に保持できない制約
  • 複雑なコードや設計は 理解コスト・修正コスト の増加要因
  • 認知負荷の削減 が、開発効率や品質向上の鍵

認知負荷の種類と削減

  • 本質的認知負荷 :タスク自体の難しさに起因、削減困難
  • 余分な認知負荷 :情報提示方法や設計による負担、削減可能
    • 本稿では 余分な認知負荷 の削減に重点
  • 例:複雑な条件式は 中間変数の導入 で分かりやすさ向上
  • 例: 早期リターン によるネスト削減で「ハッピーパス」重視の思考負荷軽減
  • 継承の多用 は負荷増大要因、 合成の推奨

モジュール設計と認知負荷

  • 小さなメソッド・クラスが正義」という神話の見直し
    • 浅いモジュール :インターフェースが複雑、機能が小粒、相互作用の理解が困難
    • 深いモジュール :インターフェースは単純、機能は強力、内部に複雑さを隠蔽
  • UNIX I/O のようなシンプルなインターフェースの例
  • 情報隠蔽 の重要性、浅いモジュールでは十分な隠蔽が困難
  • モジュール責任の誤解 :「1つのこと」より「1人のユーザー・ステークホルダーへの責任」が本質
  • 認知負荷の観点から 責任範囲の設計 を見直す必要性

マイクロサービスと認知負荷

  • 浅いマイクロサービス の乱立は、診断・統合の難易度と認知負荷を増大
  • 分散モノリス の問題点:修正時の影響範囲拡大、デプロイ・運用コスト増
  • 適切な論理境界 の見極めには時間と情報が必要
  • ネットワーク層の導入は慎重に :必要になるまで単一モジュールを維持
  • 大規模サービスの事例 (Linuxのモノリシック設計)から学ぶ柔軟性と維持性

言語機能と認知負荷

  • 新機能の追加 は一時的な学習コストだけでなく、 将来的な再理解コスト も増
  • 機能の数より、互いに独立(直交)しているか が重要
  • C++ など複雑な言語では、仕様変更や例外的挙動の理解が長期的負担に
  • 選択肢を絞ることで認知負荷を低減

実体験からのアドバイス

  • 浅いクラス80個 のプロジェクトは、1年半後に理解困難
  • 深いクラス7個 のプロジェクトは、再開時も素早く把握可能
  • 「Clean Code」や「Small Functions」信仰の再考 を推奨
  • 認知負荷 という視点で、設計・分割・責任範囲を見直す重要性

まとめ

  • 認知負荷の最小化 が、最も普遍的かつ実践的な設計指針
  • 設計原則や流行語よりも、人間の制約 に着目した判断が重要
  • シンプルなインターフェース・適切な情報隠蔽・責任分担 が維持性と拡張性の鍵
  • 複雑さの隠蔽 ができているか、 認知負荷を増やしていないか を常に自問
  • 設計選択に迷ったら、認知負荷の観点から見直す ことが最良のアプローチ

Hackerたちの意見

おそらく、私は「変わり者の賢い開発者」の一人だと思う。抽象化を作ることを試みているんだけど、業界が「if文の山アーキテクチャ」に戻っているのを見て、ちょっとモヤモヤしつつも興味を持っている。シンプルだと思い込むのは簡単だし、理解している気になるのも簡単だし、割り当てられたJiraチケットを閉じるのも簡単だから、みんながこれを好む理由はわかる。タスクが割り当てられたら、関連しそうな場所を探して、if文を追加するだけ。テストして、失敗したらまたif文を追加。最終的にQAに送って、問題が見つかれば、別のif文で解決。リリースされたら、十分な割合で動作するから、失敗例には気づかない。実際にコードが正しい確率はほぼ0%。if文を追加し続けて、正しさに近づく感じ。もし数百万の人の個人データを漏らしても責任は問われないし、認知負荷も常に低い。でも、実際にもっと良い代替案があるのかはわからない。派手な抽象化やアーキテクチャを作ることはできるけど、それがコードの正しさを高めるとは限らない。特に企業環境では、ビジネスロジックのオーナーがそれを大切に扱わないから、美しい抽象化を作るのは難しい。「一つの注文が一つの住所に送られる、シンプルに、作って、あ、実は営業マンが大口顧客に約束したから、一つの注文が複数の住所に送れるようにしなきゃ」みたいな話、聞いたことあるでしょ?企業環境では、慎重でバグのない抽象化を作るのは無理。じゃあ、if文の山がビジネスソフトウェアにとって最善の策なの?

じゃあ、if文の山がビジネスソフトウェアにとって最善の策なの?「Big Ball of Mud」の論文を楽しんでみて。実際のシステムは劣化しやすい。まずは、何を作りたいかわからない状態で大きな泥の塊から始める。そしてシステムの一部が成長するにつれて、設計を改善していく。でもまた状況が変わって、美しい抽象化が崩れてしまう。生産ソフトウェアは常に変わっている。それが面白いところ。あなたの仕事は、ドメインモデリング、十分な抽象化、そして建設的な破壊を組み合わせてこれをサポートすること。村から成長する街のように。

先週Codex CLIで遊んでたんだけど、バグを修正するために特別なケースをコードに追加するのが好きみたい。パターンを指摘して新しい抽象化を作るように頼まないと、見えてこないんだ。彼は「ヒューリスティック」と呼ぶものを追加し続けるだけで、それはバグのときに出てきた特定の条件をテストするif文だった。特定のタイプのバグに対して10個のテストを書いたら、彼はそれを全部喜んで修正してくれる。でも、同じタイプのバグで別のテストを追加すると、当然失敗する。なぜならCodexが考えた修正は最初の10個のテストに合ったif文の山だったから。

この種のソフトウェアで素晴らしい抽象化を作れると思うけど、それを生き残らせたいなら、ビジネスロジックそのものには関わらせないようにしなきゃ。これは製品のようなものにしかできない:認証、監査ログ、データベースの抽象化(CQRS、イベントソーシング)、コンテンツ/翻訳管理、メッセージングインフラ、実際のインフラ。ビジネスそのものがそれらの抽象化に影響を与えたり、指示したりすることを許すと、また混乱が生じる。ビジネスロジックはごちゃごちゃになるのが当然で、それは誰も本当に気にしないからで、責任を開発者や他の誰かに押し付けられるから。逆に、「良いコード」と「悪いコード」を分けることもひどい結果を招くことがある。私が働いていたフィンテックで見た「解決策」の一つは、ビジネスの人々自身にロジックを持たせることで、意思決定エンジンの形で実現された。基本的に、ビジネス自身が自分の泥の塊を維持することを強制された。テストするのも理解するのもシミュレートするのも不可能だった。最終的に、ソフトウェアオペレーターが雇われて、基本的にグラフィカルインターフェースを使ってコードを書くジュニアレベルの開発者だった。何度か書き直されたけど、いつも2、3年後にはすべてが混乱してしまう結果になった。

コードはif文があってもシンプルにできる方法がたくさんある。if文をあちこちに散りばめているなら、それを持ち上げてみて。最終的に同じ場所に集まるから、すべての変動性が一つの場所で実装されて文書化される。抽象化は必要ない。入力と出力を正確にモデル化するのが非常に役立つ。統一データ型を決めるのはできるだけ後回しにして、その決定に合わせてプログラミング言語を使いやすくする。クラスの階層やパターンは、実際に何が起こっているのか確信が持てるときの最後の手段だ。さらに言えば、プログラミングが管理しやすい限り、関数やファイルは必要ない。別のファイルが必要になるのは、バージョン管理システムが使えない場合か、これらの日時ハンドラがどこでも一貫して再利用される必要があると確信している場合だけだ。現代のフルスタックプログラミングは、モデル、ミドルウェア、コントローラー、ビューなどで溢れていて、誰もがそのすべての分離を最初から必要としているわけではない。

ビジネスロジックの大半は「ラストマイル」ソフトウェアだよね。美しい抽象の上に構築されてるけど、それは正しいっていう抽象的なアイデアからじゃなくて、現実との痛い衝突から生まれたものなんだ。最終的に、良い抽象を作るための明確さを得ることができる。時にはラストマイルソフトウェアがその抽象に変わることもあるけど、そうならないことが多い。俺は、早すぎる段階でこういう抽象を作ろうとする賢い開発者たちと一緒に働いたことがあるけど、現実にぶつかると、ただの混乱した「if文のスープ」になっちゃうんだよね。

最近、同僚とこの話をしてたんだ。俺の意見では、たくさんの(ソフトウェア)エンジニアリングの知恵やベストプラクティスは、ビジネスの要件やロジックの前ではうまくいかないと思う。ハードエンジニアリングでは、もっと強く反発できるけど、それはもっと永続的で、命がかかっていることが多いからなんだ。でもソフトウェアの場合は、そうはいかない。俺は、本当に速く動くビジネスの制約や、短期的な利益のための馬鹿げたリクエスト(製品を維持するために)のおかげで、適切なソフトウェアエンジニアリングがほぼ不可能になっていると思うし、実際にはこれらのif-elseのネストが必要になってくるんだ。だから、ソフトウェアエンジニアリングとプロダクトエンジニアリングを区別すべきだと思う。

個人的には、最後の部分で本質に触れてると思う。現実の世界やビジネスはごちゃごちゃしてて、まさに「if文の山」なんだよね。問題自体が技術的だったり一般化できる場合は、抽象化によって何千ものif文を書く開発者が必要なくなるけど、ドメイン自体がごちゃごちゃしていて仕様が不明瞭なら、抽象化(やツール)が役立つのは柔軟性を持たせることだけだよ。矛盾がバグじゃなくて機能かもしれないからね…

抽象化はOKだけど、SomethingFactoriesはバカげてる。もしコードが実際のロジックよりも抽象化が多くて、その抽象化を管理するためにロジックが必要なら(例えばFactoryFactoriesや2つ以上の継承レベル)、戦略を見直した方がいいよ。

意図を表現したり、基盤となるドメインを説明する方が、単なる「抽象化」よりも価値があるかもしれないね。

ビジネスの人たちがビジネスロジックをちゃんと理解して、実装者に説明できるとは思えない。絶対に無理だよ。彼ら自身は理解してるかもしれないけど、コーダーじゃないし、コードの要件を書くことはできない。だから、少なくとも一人の実装者はアプリケーションの実態をしっかり理解する必要がある。めちゃくちゃ汚れるくらいにね。本当にユーザーが毎日どんな体験をしているのかを知り、気にかけるようになるまで。まあ、ほとんどの会社にとっては現実的じゃないけど、別々のサイロを作って、「ビジネスロジック」は「どうでもいいこと」って意味になって、if文で遊ぶことになるんだよね。(あるいは、まあ、モナドにハマってそれで遊ぶのもいいけど。そっちの方がクールだし。)

これの一部は、素晴らしいプログラミング本の一つ「Code Complete」の推奨を思い出させる。

Hacker Newsで議論の続きを見る