10 ポイント 投稿者 GN⁺ 2025-08-31 | 1件のコメント | WhatsAppで共有
  • このオープンソースプロジェクトは、ソフトウェア開発における認知負荷を減らすさまざまな方法と事例を体系的に整理している
  • 不必要に複雑なコード、構造、抽象化は、開発者の生産性を損ない、保守コストを増加させる
  • モジュールは「小さくて浅いもの」よりも、シンプルなインターフェースと強力な機能を持つ深い構造が望ましい
  • 過度な抽象化、フレームワーク依存、DRY原則の乱用は、かえって認知負荷を増大させる原因になる
  • 最良のアーキテクチャは、シンプルで、新しい開発者でも素早く理解できるコードベースである

プロジェクト概要と意義

このGitHubのオープンソース資料は、ソフトウェア開発の中核原則のひとつとして「認知負荷(cognitive load)」に焦点を当てている。このリポジトリの最大の特徴は、チーム規模や技術トレンドに関係なく、開発者が実際に直面する複雑さの根源を、さまざまな事例と解決法で整理している点にある。他のベストプラクティス中心の資料と異なり、心理的・認知的な負担まで考慮しており、実務上の保守や新規メンバーのオンボーディングに強みを持つ。

序論

  • 開発現場で耳にするトレンディな概念やベストプラクティスは、実際の開発でしばしば失敗することがある
  • 実務で感じる混乱と、それによる時間・コスト損失の核心的な原因は、高い認知負荷にある
  • 開発者はコードを書くことよりも、理解して読むことにより多くの時間を費やす
  • 本文は、**不必要に発生する認知負荷(Extraneous Cognitive Load)**を減らす実用的な方法に集中している

認知負荷とは

  • 認知負荷とは、開発者が作業を完了するために頭の中に保持しなければならない情報量を意味する
  • 平均すると、一度に4個程度の「情報のチャンク」(条件、変数値など)しか短期記憶に保持できない
  • 認知負荷が閾値に達すると、理解や開発速度が大きく低下する

認知負荷の種類

  • 本質的認知負荷(Intrinsic):作業そのものの本質的な難しさに起因する。減らすことはできない
  • 余計な認知負荷(Extraneous):情報の提示方法、構造設計、不必要なパターンなどの人為的な複雑さに起因する。積極的に減らすことができる

実践的な例と改善策

複雑な条件文

  • 複数の条件が入れ子になったコードは、各段階で頭の中に記憶しておくべき内容が増え、認知負荷が増加する
  • 解決策:意味のある中間変数を導入し、各条件の目的を明確に分ける

ネストしたif文 vs Early Return

  • ネストしたif文よりも、Early Returnパターンの方が前提条件を記憶する負担を減らす
  • 「ハッピーパス(happy path)」だけを意識すればよいようにして、ワーキングメモリを解放する

継承構造の副作用

  • 深い継承体系(例:クラスA → クラスB → クラスC …)は、コードを理解するために多くの階層情報を同時に記憶する必要があり、認知負荷が急増する
  • 解決策:コンポジション優先。不要な継承の代わりに組み合わせ(合成)を活用する

浅いモジュール/関数が多すぎる問題

  • 80個の浅くて小さなクラスよりも、いくつかの強力でシンプルなインターフェースを持つディープモジュールの方が保守しやすい
  • 例として、UNIX I/Oのシンプルなインターフェース(open, read, write, lseek, close)が挙げられている

「単一責任の原則」の無分別な誤解

  • 「ひとつのことだけをする」という曖昧な解釈は、かえって浅く不明瞭な抽象化を量産してしまう
  • 実際には「1人のステークホルダーに対して責任を持つ」と解釈した方が、ビジネスとの関連性を理解しやすくなり、認知負荷も減らせる

マイクロサービスの乱用

  • 浅いマイクロサービスが多すぎると、各サービスの関係や連携情報を常に頭の中に保持しなければならず、認知負荷やデバッグ/リリースコストが増加する
  • 初期段階では、完成度の高いモノリシック構造の方がかえって保守に有利なことがある

言語機能/オプションの過剰さ

  • 言語自体の数多くの新機能(特にC++など)は、かえって「なぜこのように実装されたのか」を追跡することになり、記憶負担が蓄積する
  • ビジネスとは無関係な副次的認知負荷をむやみに増やしてしまう

HTTPステータスコードとビジネスロジックのマッピング

  • HTTPステータスコード(401, 403, 418 など)と内部ビジネス上の意味を恣意的に対応づけると、チーム全員がそれを覚えなければならない
  • 改善策:自己記述的な文字列コード(例:"jwt_has_expired")で一貫して伝える

DRY(Do not repeat yourself)原則の乱用

  • 複雑なコードベースで無理に重複排除だけを進めると、強い依存関係が生まれ、認知負荷や変更コストがかえって増加する
  • ローカルでは「少しのコピーの方がよい」という Rob Pike の名言が引用されている

フレームワーク依存とレイヤードアーキテクチャの弊害

  • フレームワークの「魔法」のような構造に深く依存すると、新しい開発者は内部ロジックの把握に長時間を要する
  • 抽象化レイヤーが積み重なると、実際に問題を追跡する場面で認知負荷が急増する
  • 根本原理(Dependency Inversion, Info Hiding, Cognitive Load control)に集中すべきである

ドメイン駆動設計(DDD)の誤解

  • DDDの核心は問題領域の分析にあり、フォルダ構造やパターンへの執着は、かえって主観的な解釈や認知負荷を招く
  • Team Topologies のようなフレームワークの方が、認知負荷の分割にはより効果的である

親しみやすさ vs シンプルさ

  • 親しみやすさはシンプルさと同じではない。慣れているから軽く感じるのであって、実際に構造が簡単とは限らない
  • 新しく入ったメンバーが40分以上混乱するなら、コード改善が必要なサインである

実際の成功/失敗事例

  • Instagramのように、シンプルなモノリシックアーキテクチャでも高いスケールと保守性を実現できる
  • 「本当に賢い開発者」が複雑な構造を作った会社ほど、むしろ失敗経験が多いことがある
  • すべての開発者が簡単に読めて素早くオンボーディングできる構造こそが、生産性向上において重要な役割を果たす

結論

  • 本質的な作業を超えた不必要な認知負荷は、すべての開発主体にとって有害である
  • 最良のコードとは、未来の他の開発者と自分自身ができるだけ素早く理解できるコードである
  • 「賢く見える」構造よりも、平凡で率直な解決策の方が、保守やチーム生産性の面で長期的に有利である

参考/所感

  • Rob Pike、Andrej Karpathy、Elon Musk、Addy Osmani、antirez(Redis開発者)など著名な開発者の意見も引用されている
  • Chromium、Redis、Instagram など、実際の大規模事例とも一致する示唆が示されている
  • シンプルさと明快さ、認知負荷の削減こそが、ソフトウェアの持続可能性の核心である

このオープンソースプロジェクトの価値

  • 多くのソフトウェア設計書やパターンが見落としがちな、現実の開発者体験と実践的事例を中心にした資料
  • 開発者オンボーディング、アーキテクチャレビュー、長期保守など、さまざまなチームに即効性のある実質的な助けを提供する
  • 「認知負荷」という明確な枠組みで、コードを見直すためのチェックリストとして機能する

1件のコメント

 
GN⁺ 2025-08-31
Hacker Newsの意見
  • John Ousterhoutの A Philosophy Of Software Design という本から最も大きな気づきを得た。このテーマに関する最高の本で、開発者ならぜひ読むことを勧める。要点は、ソフトウェア設計では複雑さを最小化することが目標であるべきだということ。ここでいう複雑さとは「変更するのがどれだけ難しいか」と定義され、この「難しさ」はコードを理解するのに必要な認知負荷によって決まる

    • 問題は、どんなルールも結局は判断力、経験、直感の代わりにはならないということ。あらゆるルールは論争の道具になり得るし、アーキテクチャ論争では絶対に勝てない。この記事がよいのは、不要な人はすでに知っていて、本当に必要な人には理解されないからだ。結局これは技術の問題ではなく、人と文化の問題だ。アーキテクチャは結局、人と文化から生まれる。Rob PikeとGoogleがいるからGoが生まれるのであって、本を一冊読んだだけでGoのような言語が生まれるわけではない

    • DRY原則(重複を避けよ)は、実際にアプリケーションを十分理解し、複数のバージョンを作った後でようやく検討すべき原則だと思う。最初はむしろ繰り返しながら問題空間を理解し、2番目のバージョンで保守しやすさを考えるべきだ。3番目のバージョンあたりでやっとDRYを適用してよいと思う

    • この本をJeff Bezosに金を払わずに手に入れるのは本当に難しい。もし著者のJohnを知っている人がいたら、この問題をぜひ伝えてほしい。大学生協にもなかったし、地元の書店やPowell’s書店でも手に入らなかった

    • 長いあいだ、完璧なソフトウェアの解法を探すことは諦めている。誰一人として完璧に整理できた人はいないように思う。結局、私たちの最良の武器は人々の知恵と経験だ。文脈も業界もチームも違いすぎて、数字や法則でぴったり定義することはできない。結局、私は設計において「ぐちゃぐちゃ」と「美しさ」のあいだのバランスを取ることを目標にしている。最も難しかったのは、ビジネスは不確実なのにソフトウェアは確定的であるため、ビジネス要件が常に変わり、それをコンピュータシステムの硬直性に合わせるのが難しい点だ。最近では、コードを変えようとするときに実際に不便さを感じたときだけリファクタリングを試みる。その場合でも最小限の整理しかしない。こうした反復的なリファクタリングを何度も行ううちに新しいパターンが生まれ、それを抽象化として切り出すこともある

    • この本こそがまさにそのテーマの最高峰だ。該当の記事もこの本から着想を得ている。著者のJohnとも記事の内容について一つ二つ会話したことがある

  • 他人の認知負荷を減らすコードを書ける能力は、非常に希少で難しいスキルだ。能力があっても継続的な努力が必要になる。結局、開発者は中核となるアイデアを圧縮して本質だけを示し、本当に必要な複雑さだけを露出させるべきだ。実際にこれがうまい例はほとんど見たことがない

    • 本当によくできたコードは、最終的に人に「これってもともと簡単な問題だったのか?」という錯覚を起こさせる。一方で、複雑さが目に見える「ハリボテの家」のようなコードは、努力の跡として評価され、昇進にまでつながる

    • これはインターフェースやUX/インタラクションデザインにもまったく同じように当てはまる。開発者は一般の人より認知負荷にずっと強いが、非技術者がうまく使えるものを作るには、自分自身の直感から離れなければならず、難易度が非常に高い。一般ユーザーが直感的に複雑な問題を解けるようなツールを設計するのは本当に難しい

    • たいていの抽象化は、要件変更にうまく耐えられない。私の好きなフレームワークは、永遠に完璧な抽象化レイヤーを作ることはできないと理解していて、意図的に「脱出口」を用意している。(例: Web UIフレームワークでhtml elementへのdirect referenceを提供するなど)未来を完璧に予測できないという賢さが重要だ

    • 実際、このスキルは多くの会社で必須というよりボーナス要素だ。主要なコードベースを見ればすぐわかる

  • 私は自分を「オタク気質のスマートな開発者」の一人だと思っている。抽象化を構築するのが好きだ。最近、業界が「if文の山アーキテクチャ」に回帰しているのが不思議でもあり心配でもある。単純に見えて理解しやすく、与えられたチケットだけ閉じればよいので、多くの人が好む理由はわかる。多くの開発プロセスがif文の山に依存していて、この方式がある程度以上のケースでは実際にうまく機能する。大量の個人情報流出のようなことが起きても、責任を取る人もあまりいない。しかし問題は、これよりよい代替案があるのかよくわからないことだ。派手な抽象化や格好いいアーキテクチャも、実際にはコードの整合性を高めてくれないように見える。特に企業環境では、ビジネスロジックの所有者たちがロジック自体をほとんど気にしないので、慎重に美しい抽象化を作ることができない。結局「注文は一つの住所にしか配送されない」という要件があっても、突然「大口顧客が複数の住所への配送を求めた」と変わる。このような混沌の中では、バグのない抽象化は存在しにくい。現実的には、企業向けソフトウェアではif文の山が最善なのではないかとさえ思う

    • 「if文の山」方式が最善かという問いには、現実の大規模ソフトウェアアーキテクチャを扱った Big Ball of Mud論文要約記事 を勧めたい。実際のシステムは常に進化し、最初は「泥の塊」として始まり、部分ごとに改善されるが、再び変化が来ると美しかった抽象化も崩れていく。重要なのは、小さな町が都市へ成長するように、ドメインモデリング、適度な抽象化、そして必要な破壊的変化の組み合わせでシステムを育てることだ

    • こうしたソフトウェアでも見事な抽象化を作ることはできるが、ビジネスロジックが介入した瞬間から維持が難しくなる。抽象化がきちんと残っている部分は主にプロダクト的な要素、たとえば認証、権限、ログ、データベース、ミドルウェア、インフラなどだ。ビジネス内部のロジックが抽象化に影響し始めると、結局またぐちゃぐちゃになる。ある場所では、ビジネス側の人が直接ロジックを管理するようにしたこともあったが、これはテストも難しく理解も不可能で、シミュレーションすらできなくなった。結局は運用担当が必要になり、junior開発者がグラフィカルツールでコードを書くような状況まで起きた。何度リライトしても、2〜3年後にはいつもごちゃごちゃになる

    • ビジネス側の人がビジネスロジックを実装者に完璧に説明することは絶対に不可能だ。彼ら自身は理解していても、コーディングの観点で表現できないからだ。むしろ実装者のうち最低一人は、本当に骨の髄までユーザー体験をしないと正しく理解できない。現実には企業は部門分離を強制し、「ビジネスロジック」は誰も気にしない領域になって、結局if文をいじるだけになる

    • 現実、つまりビジネスそのものがすでにif文の山なのが核心だ。問題やドメインが明確に技術的で、あるいは一般化できるなら、抽象化によってif文を減らせるかもしれない。しかしドメイン自体が混沌なら、抽象化にも結局「柔軟性」を前提として組み込むしかない。矛盾がむしろ機能になることさえある

    • Codex CLIのようなツールで遊んでみると、このツールはバグに出会うと常に特定ケース向けのif文パッチを生成する。こちらがパターンを直接教え、新しい抽象化を要求しない限り、「ヒューリスティック」と呼ぶif文を追加し続ける。特定タイプのバグ10件に対して10個のパッチが作られ、11番目の似たバグが出たら当然動かない。Codexが提示した解法はif文の繰り返しの集合だ

  • 完全に未知のプロジェクトで修正を頼まれる状況が、現実でどれほどよくあるのか疑問だ。プロジェクト異動が本当に頻繁でないなら、2年に1回程度だろう。経験不足ではなく、本当に難しい問題に集中するほうがよいと思う

  • Microsoftの開発組織で8年働き、初期のエンジニア類型モデルがとても有用だった。3つのペルソナがあり、後にはより複雑なフレームワークに置き換えられた。それでも元のペルソナは、今に至るまで私のキャリアで他のエンジニアと効果的にコミュニケーションする助けになっている。

    • Mort: 実用主義のエンジニア。ビジネス成果が最優先。if文の山でも素早く動けば十分。
    • Elvis: イノベーション優先。最新技術を使い、自分の天才性が知られることを望む。新しいフレームワーク導入、不安定なコード、可視性と革新への執着。
    • Einstein: 性能、優雅さ、技術的正確さに執着。コードの一貫性と技術的完成度をより重視する。 現実には、すべてのエンジニアはこの3つの混合体だ。PRと設計レビューを数回見れば、主な傾向はだいたい把握できる
    • ここにAmandaというエンジニア類型も必要だと思う。20年間、自分と他人のぐちゃぐちゃなコードをレビューしてきた結果、コードは何よりも「人が読むためのもの」だと身体で理解している。そういうAmandaたちだけでチームを組みたい

    • Mortは実用主義者、Einsteinは完璧主義者、Elvisは……正直、プロジェクトに害をなす存在だと思う。多少は動機づけになるが、理想のチームはMortとEinsteinをうまく混ぜたものだ。必要なだけ単純にしつつ、十分な正確さを確保して保守が苦痛にならないようにすべきだ。むしろMortに長期プロジェクトを任せておくと、いずれEinsteinのように気にし始める。余談だが、最近の自動コーディングエージェントはMort傾向が強すぎて、私が逆にMortとして管理しなければならないほどだ

    • ペルソナはよい道具だ。私の経験では、時間が経つと誤った近道に変質する。Elvisは実際には社内政治のための用語であり、Goodhartの法則のように、理屈の上では正しそうに見えるため、誰もが議論で使おうとして結局有用性が落ちた。Alan Cooperは昔、Visual Basic開発とともにペルソナ概念を導入していた。自分と違う視点、つまり科学者とファームウェア開発者では価値観が異なると理解することが重要だ。 関連書籍

    • 最高のエンジニアとは、この3つの傾向をプロジェクト、状況、個人的目標に応じて調整できる人だと思う。役割によって必要な配分は異なる。たとえばコンパイラ最適化ではEinsteinとMortがより必要で、ゲームエンジンのようなコードでは比率が違う。それぞれの特性が正確かどうかは議論の余地があるが、重要なのは誰もが時間とともに違う形で振る舞う必要があるという点だ

    • このやり方には境界が必要だと思う。人をあまりに単純に分類すると、不当で恒久的なレッテル貼りになりうる。経験上、むしろ経営陣はElvisよりMortを好む。実際の解決策はリーダーシップとマネジメントにあると思う。Mortにコード品質基準を要件に含めて求めれば、少し遅くなってもそこそこの成果物を出せる。Elvisには制限を、Einsteinsには実務的な締切条件を明確に示すべきだ。この方法には人間の複雑さを見落とす限界がある

  • AIがソフトウェア産業に害を及ぼすのではないかと心配している。AIは人間の限界を持たないので、非常に複雑で読みにくいが動くコードを書けるし、やがてそれが壊れたときには誰も直せなくなるだろう。だからjuniorエンジニアには、AIだけに依存せず、コードベースの特性を身につけるよう勧めている。そうしないと自分でコードを書ける能力を失ってしまう

    • AIは動かなくなってもまた直せるのだから、こういう主張には説得力がないと思う

    • 私の経験では、AIのコードを必ず理解できるようになるまで、ひたすら単純化するよう求めている。複雑なやり方ならもっと簡単にし、不要な外部ライブラリを使っているなら外させる。stdlibで解決するか、すでにある依存関係だけを使わせる。AIは私の代わりにコードを書くのであって、AI自身のために書くのではない

    • むしろAIのおかげで低レベル設計にかかる認知負荷が減り、上位アーキテクチャを考えることにもっと集中できるので、ソフトウェア品質が上がるかもしれないと思う

  • 記事で言及されている「スマートな開発者特有の癖」については、著者は自分が書いたコードや自分のスタイルのコードしか理解できないと言っているようにも見える。しかし認知負荷を減らす本当の秘訣は、読まなくてよいコードの量を最小化することにある。強い境界と明確なAPIは変更作業を容易にし、重要なのはコード全体ではなく、よく定義されたインターフェースから派生する複雑さだけを気にすればよいということだ。「スマートな開発者」の特異性は、むしろ表面的な問題だ

    • マイクロサービス概念の最大の利点はまさにここにある。2つのチームがAPIでしかやりとりせず、schemaのようなツールで境界を厳密に定義すれば、変更に対して非常に保守的になり、事前にとても深く考えるようになる。変わってほしくない箇所に人工的な摩擦を技術的に挿入する効果だと思う

    • デバッグしていてよく感じるのは、コールスタック上で直接デバッグしなければならないコードに過剰に最適化された部分があると、とても疲れるということだ。だから特定部分だけ関数に切り出してコミットを分ければ、望まないコード変更も簡単にロールバックでき、コードへの集中度も高まる。PRでは一度に上がってくるだろうが、それでも境界が明確ならずっとよい

    • 開発者はたいてい「契約とインターフェース」で考えるのがあまり得意ではない。内部実装ではなく、外に見える約束だけを見るようにすべきだ。コードを読まなくても理解できることが目標だ。もし私のコードを理解するために中を直接のぞかなければならないなら、それは失敗だ

    • APIがどれだけ明確でも、内部コードは簡単に理解できないことがある。表面上はうまく動いているように見えても、実際にはその内部まで掘り下げなければならない場合が多い

  • この記事が自分の経験をとてもよく要約してくれていて、本当にうれしい。正式に学んだプログラミング方法論のほとんどは、他人が私のコードを読まなければならない瞬間に逆効果になる。RustやC++のように複雑に隠せるコードはつらく、Cのようにテンプレート6層の下にコードを隠せない構造を見ると、むしろ安心する

    • 自分の経験と通じると言ってくれてうれしい。私も同じ理由でCが好きだったし、最近はGolangに移って、こちらの単純さが気に入っている
  • コードのアクセシビリティを第一級市民として扱うべきだという話が本当に刺さる。ルールは指針として使い、本当に実力のある人は、コードを読みやすくするために、いつルールを破るか、あるいは補うかを文脈に応じて判断する能力を持っている。結局、コードの認知的負荷とトレードオフを直感できる力が重要だ。重複であれ抽象化であれ、6か月後の自分を含む次の人を常に意識すべきだ。ルールをもう一つ増やそうという要求は、それ自体がまた別の問題の繰り返しだ。基本的なガイドライン以降は tacit knowledge、つまり経験から生まれる暗黙知の領域になる。時間がたてば、どのコードがよくてどのコードが悪いかを自分で感じるようになる。最終的には自分で体感するしかない

    • コードのアクセシビリティの本質には同意するが、筆者はgermane overhead(受け手の認知資源の消費)を見落としているのが惜しい。結局、コードのアクセシビリティを高めるには、intrinsic、extraneous、germaneの3つのオーバーヘッドをすべて考える必要がある
  • 「なぜ」を文書化しろとはよく言われるが、私は「なぜ」と「何を」を分けるのが難しいので、両方記録する。最悪のコメントは

    x = 4 // assign 4 to x 
    

    こういうものだ。コードとコメントを混ぜると読みにくく、コンテキストスイッチも激しい。代わりに

    // なぜこれをするのか、何をしようとしているのか
    setup();
    t = setupThing();
    t.useThing(42);
    t.theWidget(need=true);
    ...
    

    こんなふうにコメントとコードを明確に分けるほうがよい。それでも、「何を」だけでも書いてあるほうが、何もないよりはずっとよく、認知負荷も下げられる

    • この形式のコメントはたいてい保守が難しく、実装は年に5回は変わるので、すぐ古くなって混乱のもとになる。最近のプロジェクトでは、本当に難しいロジックと最上位ドキュメントだけを残して、大半のコメントを削除している

    • 両方(why/what)を文書化すべきときはあると思う。理由を明確にすべきときもあれば、動作内容を明確にすべきときもある。最近はコメントの価値が過小評価されていると思う。すべての行にコメントを付ける必要はないが、大半の「自己文書化コード」もそれほど読みやすくはない

    • 最近は、何をコード内に残し、何を設計文書や技術文書に残すかを決めるのが最も難しいと感じる。私の結論は、コード内コメントは直感的でない意図や隠れた意図を説明するために使うのがよいということだ。例: 「これをメモ化する理由はXのため」

    • ChatGPT風の x=4 コメントには笑ってしまった。ただ、提案された方式の欠点は、コメントがコードと食い違って古くなる危険があることだ。コードを修正するときはコメントも必ず更新しなければならない

    • 私は教育を受けたプログラマではなく物理学者なので、コメントを書くたびに上のやり方へ変えようと努力している。学生時代は、成績のためにはとにかくコメントをたくさん書くのが慣習だった