認知負荷こそが重要

September 1, 2025 · View on GitHub

読みやすい版 | 中国語翻訳 | 韓国語翻訳 | トルコ語翻訳 | 日本語翻訳

これは生きた文書であり、最終更新は2025年8月です。あなたの貢献を歓迎します!

はじめに

世の中には多くのバズワードやベストプラクティスがありますが、そのほとんどがうまくいっていません。私たちにはもっと根本的な、誤りようのないものが必要です。

コードを読んでいて混乱を感じることがあります。混乱は時間とお金を消費します。混乱の原因は高い認知負荷にあります。これは決して抽象論ではなく、人間の根本的制約なのです。私たちはそれを実感できます。

コードを書くよりもコードを読んで理解することに費やす時間のほうがはるかに長いため、私たちは常に、過度の認知負荷をコードに埋め込んでいないかを自問すべきです。

認知負荷

認知負荷とは、開発者がタスクを完了するために考える必要がある量のことです。

コードを読むとき、変数の値、制御フローロジック、呼び出しシーケンスなどを頭に保持します。平均的な人はワーキングメモリに約4つのチャンクを保持できます。認知負荷がこの閾値を超えると、理解が一気に難しくなります。

完全に馴染みのないプロジェクトにバグ修正を依頼されたとしましょう。とても賢い開発者が貢献していたと聞かされました。多くの洗練されたアーキテクチャ、おしゃれなライブラリ、最新のテクノロジーが使われていました。つまり、著者は私たちに高い認知負荷を作り出していたのです。

認知負荷

私たちはプロジェクトの認知負荷を可能な限り減らすべきです。

認知負荷と中断

認知負荷の種類

内在的 - タスクの本来の難易度によって引き起こされます。これは削減できず、ソフトウェア開発の核心にあるものです。

外在的 - 情報の提示方法によって作り出されます。タスクに直接関係しない要因、例えば賢い開発者のクセのようなもので引き起こされます。大幅に削減可能です。私たちはこの種の認知負荷に焦点を当てます。

内在的 vs 外在的

外在的認知負荷の具体的で実践的な例にすぐに飛び込みましょう。


認知負荷のレベルを以下のように表記します: 🧠: 新鮮なワーキングメモリ、認知負荷ゼロ 🧠++: ワーキングメモリに2つの事実、認知負荷増加 🤯: 認知過負荷、4つ以上の事実

私たちの脳ははるかに複雑で未探索ですが、この単純化されたモデルで進めることができます。

複雑な条件文

if val > someConstant // 🧠+
    && (condition2 || condition3) // 🧠+++、前の条件が真で、c2かc3のいずれかが真である必要性
    && (condition4 && !condition5) { // 🤯、この時点で理解が破綻しがち
    ...
}

意味のある名前を持つ中間変数を導入します:

isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5 
// 🧠、記述的な変数があるので、条件を覚える必要がありません
if isValid && isAllowed && isSecure {
    ...
}

ネストしたif文

if isValid { // 🧠+、ネストしたコードは有効な入力にのみ適用
    if isSecure { // 🧠++、有効で安全な入力にのみ処理
        stuff // 🧠+++
    }
} 

早期リターンと比較してみましょう:

if !isValid
    return
 
if !isSecure
    return

// 🧠、以前のリターンは気にしません。ここに到達したということは、すべてがOK

stuff // 🧠+

ハッピーパス(正常系)だけに集中でき、ワーキングメモリをあらゆる前提条件から解放します。

継承の悪夢

管理者ユーザーのためにいくつかの変更を依頼されました:🧠

AdminController extends UserController extends GuestController extends BaseController

ああ、機能の一部はBaseControllerにあります、見てみましょう:🧠+ 基本的な役割メカニクスがGuestControllerで導入されました:🧠++ UserControllerで部分的に変更されました:🧠+++ ついにここに来ました、AdminController、コーディングしましょう!🧠++++

ああ、待って、AdminControllerを継承するSuperuserControllerがあります。AdminControllerを変更することで継承クラスで問題が起こる可能性があるので、まずSuperuserControllerを調べましょう:🤯

継承よりコンポジション(委譲)を好みましょう。詳細は割愛します。 - 参考資料はこちらが充実しています。

小さなメソッド、クラス、モジュールが多すぎる

この文脈では、メソッド、クラス、モジュールは交換可能です

「メソッドは15行未満であるべき」や「クラスは小さくあるべき」といった格言は、やや間違っていることが判明しました。

深いモジュール - シンプルなインターフェース、複雑な機能 浅いモジュール - インターフェースが提供する小さな機能に対して相対的に複雑

深いモジュール

浅いモジュールが多すぎると、プロジェクトを理解するのが困難になる可能性があります。各モジュールの責任を頭に置くだけでなく、それらすべてのインタラクションも把握する必要があります。浅いモジュールの目的を理解するために、まずすべての関連モジュールの機能を調べる必要があります。そのような浅いコンポーネント間をジャンプするのは精神的に疲れます。人間は線形に読むのが自然です。

情報隠蔽は最重要であり、浅いモジュールでは複雑性をそれほど隠せません。

私には2つのペットプロジェクトがあり、どちらも約5,000行のコードです。最初のプロジェクトには80の浅いクラスがあり、2番目のプロジェクトには7つの深いクラスしかありませんでした。これらのプロジェクトのメンテナンスを1年半していませんでした。

戻ってきたとき、最初のプロジェクトの80のクラス間のすべてのインタラクションを解きほぐすのが非常に困難であることに気づきました。コーディングを始める前に、膨大な認知負荷を再構築する必要がありました。一方、2番目のプロジェクトは、シンプルなインターフェースを持つ少数の深いクラスしかなかったため、すぐに把握できました。

最高のコンポーネントは、強力な機能を提供しながらシンプルなインターフェースを持つものです。 John K. Ousterhout

UNIX I/Oのインターフェースは非常にシンプルです。基本的な呼び出しは5つだけです:

open(path, flags, permissions)
read(fd, buffer, count)
write(fd, buffer, count)
lseek(fd, offset, referencePosition)
close(fd)

このインターフェースの現代的な実装は数十万行のコードを持ちます。多くの複雑性が内部に隠蔽されています。しかし、シンプルなインターフェースのおかげで使いやすいのです。

この深いモジュールの例は、John K. OusterhoutのA Philosophy of Software Designから取られました。この本はソフトウェア開発における複雑性の本質をカバーするだけでなく、Parnasの影響力のある論文On the Criteria To Be Used in Decomposing Systems into Modulesの最高の解釈も提供します。どちらも必読です。関連読み物:A Philosophy of Software Design vs Clean CodeIt's probably time to stop recommending Clean CodeSmall Functions considered Harmful

P.S. 責任が多すぎる肥大化したGodオブジェクト(神オブジェクト)を支持していると思われるなら、それは間違いです。

一つのことに責任を持つ

あまりにもしばしば、曖昧な「モジュールは一つの、そして唯一の一つのことに責任を持つべき」原則に従って、多くの浅いモジュールを作ることになります。このぼんやりとした「一つのこと」とは何でしょうか?オブジェクトをインスタンス化することは一つのことでしょうか?だからMetricsProviderFactoryFactoryは問題ないように見えます。そのようなクラスの名前とインターフェースは、実装全体よりも精神的に負荷が大きくなる傾向があります。それはどのような抽象化でしょうか? 何かが間違っています。

私たちはユーザーや利害関係者を満足させるためにシステムを変更します。私たちは彼らに対して責任があります。

モジュールは一つの、そして唯一の一つのユーザーまたは利害関係者に責任を持つべきです。

これが単一責任原則の本質です。簡単に言えば、一箇所にバグを導入して、2人の異なるビジネス関係者が苦情を言いに来た場合、原則に違反しています。モジュールで行うことの数とは関係ありません。

しかし今でも、この規則は害を与える可能性があります。この原則は個人の数だけ異なる方法で理解される可能性があります。より良いアプローチは、それがどのくらいの認知負荷を作り出すかを見ることです。一箇所での変更が異なるビジネス領域間で連鎖反応を引き起こす可能性があることを覚えておくのは、精神的に負荷が大きいです。それだけのことで、学ぶべき難しい用語はありません。

浅いマイクロサービスが多すぎる

この浅い-深いモジュール原則はスケールに依存せず、マイクロサービスアーキテクチャにも適用できます。浅いマイクロサービスが多すぎるのは良くありません - 業界は多少「マクロサービス」、つまりそれほど浅くない(=深い)サービスに向かっています。最悪で修正が困難な現象の一つは、いわゆる分散モノリスで、これはしばしばこの過度に粒度の細かい浅い分離の結果です。

かつて、5人の開発者チームが17個(!)のマイクロサービスを導入したスタートアップをコンサルティングしました。彼らは10ヶ月スケジュールが遅れていて、パブリックリリースには程遠い状態でした。新しい要件のたびに4つ以上のマイクロサービスでの変更が必要でした。そのような分散システムで問題を再現しデバッグするには膨大な時間がかかりました。市場投入時間と認知負荷の両方が許容できないほど高かったのです。🤯

これは新しいシステムの不確実性に対する正しいアプローチでしょうか?最初に正しい論理的境界を引き出すことは非常に困難です。重要なのは、情報が最も多い時点まで責任を持って待てるだけ決定を遅らせることです。最初にネットワーク層を導入することで、最初から設計決定を元に戻すのを困難にしています。チームの唯一の正当化は:「FAANG企業がマイクロサービスアーキテクチャが効果的であることを証明した」でした。現実に立ち返りましょう。

Tanenbaum-Torvalds討論では、Linuxのモノリシック設計が欠陥があり時代遅れであり、代わりにマイクロカーネルアーキテクチャを使うべきだと主張されました。確かに、マイクロカーネル設計は「理論的で美的な」観点から優れているように見えました。実用的な側面では――30年経った今、マイクロカーネルベースのGNU Hurdはまだ開発中で、モノリシックなLinuxが至る所で使われています。このページはLinuxで動いており、あなたのスマートティーポットもLinuxで動いています。モノリシックなLinuxで。

本当に分離されたモジュールを持つ良く作られたモノリスは、多くのマイクロサービスよりもはるかに柔軟であることが多いです。また、維持するのに必要な認知的努力もはるかに少なくなります。開発チームのスケーリングなど、別々のデプロイメントの必要性が重要になった場合にのみ、モジュール間、将来のマイクロサービス間にネットワーク層を追加することを検討すべきです。

機能豊富な言語

お気に入りの言語で新機能がリリースされるとワクワクします。これらの機能を学ぶのに時間を費やし、その上にコードを構築します。

多くの機能がある場合、数行のコードを書くのに30分を費やして、ある機能を使うか別の機能を使うかを考えるかもしれません。それは時間の無駄です。しかし、もっと悪いことに、後で戻ってきたときに、その思考プロセスを再現する必要があります!

この複雑なプログラムを理解するだけでなく、利用可能な機能から問題にアプローチするこの方法をプログラマーがなぜ決定したのかを理解する必要があります。 🤯

これらの発言は他ならぬRob Pikeによるものです。

選択肢の数を制限して認知負荷を減らす。

言語機能は、お互いに直交している限り問題ありません。

20年のC++経験を持つエンジニアからの考え ⭐️
先日RSSリーダーを見ていると、「C++」タグの下に未読記事が約300個あることに気づきました。昨年の夏以降、この言語についての記事を一つも読んでおらず、気分は良好です!

私は20年間C++を使っており、それは人生の約3分の2です。私の経験の大部分は、言語の最も暗い部分(あらゆる種類の未定義動作など)を扱うことにあります。それは再利用可能な経験ではなく、今それをすべて捨てるのはちょっと不気味です。

例えば、トークン||requires ((!P<T> || !Q<T>))requires (!(P<T> || Q<T>))で異なる意味を持つことを想像できますか。最初は制約分離、2番目は昔ながらの論理OR演算子で、これらは異なる動作をします。

自明な型のためのスペースを割り当て、そこに一連のバイトをmemcpyするだけでは、追加の努力なしにはオブジェクトの生存期間を開始できません。これはC++20以前の場合でした。C++20で修正されましたが、言語の認知負荷は増加しただけです。

物事が修正されても認知負荷は常に増加しています。私はプロとして、何が修正されたか、いつ修正されたか、以前はどうだったかを知っている必要があります。確かに、C++はレガシーサポートが得意で、それはあなたがそのレガシーに直面することも意味します。例えば、先月同僚がC++03でのある動作について尋ねました。🤯

初期化の方法は20通りありました。統一初期化構文が追加されました。今は21通りの初期化方法があります。ところで、初期化リストからコンストラクタを選択する規則を覚えていますか?情報の損失が最小の暗黙変換についてのもので、しかし値が静的に分かっている場合は、それから... 🤯

この増加した認知負荷は、手元のビジネスタスクによるものではありません。それはドメインの本質的な複雑性ではありません。歴史的な理由で存在しているだけです外在的認知負荷)。

私はいくつかのルールを決める必要がありました。例えば、そのコード行が明らかでなく、標準を覚えなければならない場合は、そのように書かないほうが良いです。ちなみに、標準は約1500ページの長さです。

決してC++を非難しようとしているのではありません。私はこの言語を愛しています。ただ、今は疲れているのです。

0xd34df00dに執筆していただき、ありがとうございます。

ビジネスロジックとHTTPステータスコード

バックエンドで以下を返します: 401 期限切れJWTトークンの場合 403 アクセス権限不足の場合 418 利用停止中のユーザーの場合

フロントエンドのエンジニアは、バックエンドAPIを使用してログイン機能を実装します。彼らは一時的に以下の認知負荷を頭の中に保持しなければなりません: 401は期限切れJWTトークンの場合 // 🧠+、一時的に覚えておく 403はアクセス権限不足の場合 // 🧠++ 418は利用停止中のユーザーの場合 // 🧠+++

フロントエンド開発者は(願わくば)彼らの側で何らかの数値ステータス → 意味辞書を導入し、後続の貢献者世代がこのマッピングを頭の中で再構築する必要がないようにするでしょう。

それからQAエンジニアが登場します: 「やあ、403ステータスを受け取ったけど、これは期限切れトークンなの、それともアクセス権限不足なの?」 QAエンジニアは、バックエンドのエンジニアがかつて作成した認知負荷を再構築しなければならないので、すぐにテストに飛び込むことができません。

なぜこのカスタムマッピングをワーキングメモリに保持するのでしょうか?ビジネス詳細をHTTP転送プロトコルから抽象化し、レスポンスボディに自己記述的なコードを直接返すほうが良いです:

{
    "code": "jwt_has_expired"
}

フロントエンド側の認知負荷:🧠(新鮮、頭に保持される事実なし) QA側の認知負荷:🧠

同じ規則があらゆる数値ステータス(データベースやその他の場所)に適用されます - 自己記述的な文字列を好みましょう。メモリを最適化するための640Kコンピューターの時代ではありません。

人々は401403の間で議論し、自分の心的モデルに基づいて決定を下すのに時間を費やします。新しい開発者が入ってきて、その思考プロセスを再構築する必要があります。コードの「なぜ」(ADR)を文書化して、新参者が下された決定を理解できるようにしているかもしれません。しかし結局のところ、それは全く意味をなしません。エラーをユーザー関連またはサーバー関連に分離することはできますが、それ以外は物事がぼやけています。

P.S. 「認証」と「認可」を区別するのはしばしば精神的に負荷が大きいです。認知負荷を減らすために、「ログイン」と「権限」のようなより簡単な用語を使うことができます。

DRY原則の乱用

Don't repeat yourself(自分を繰り返すな) - これはソフトウェアエンジニアとして教わる最初の原則の一つです。それは私たち自身に深く埋め込まれており、数行の余分なコードの存在に耐えられません。一般的には良い基本的な規則ですが、使いすぎると私たちが処理できない認知負荷につながります。

今日では、誰もが論理的に分離されたコンポーネントに基づいてソフトウェアを構築しています。多くの場合、これらは別々のサービスを表す複数のコードベースに分散されています。あらゆる繰り返しを排除しようと努力すると、関連のないコンポーネント間で密結合を作り出すことになるかもしれません。結果として、一部の変更は他の一見関係のない領域で予期しない結果をもたらす可能性があります。また、システム全体に影響を与えることなく個々のコンポーネントを置き換えたり変更したりする能力を妨げることもあります。🤯

実際、同じ問題は単一のモジュール内でも発生します。長期的に実際には存在しないかもしれない認識された類似性に基づいて、共通機能を早すぎる段階で抽出するかもしれません。これは変更や拡張が困難な不要な抽象化をもたらす可能性があります。

Rob Pikeはかつて言いました:

少しの依存よりも少しのコピーのほうが良い。

車輪を再発明しないことに強く惑わされ、私たち自身で簡単に書けるような小さな関数を使うために大きくて重いライブラリをインポートしがちです。

すべての依存関係は実質的に「あなたのコード」です。 インポートされたライブラリの10以上のレベルのスタックトレースを通して何が悪かったかを把握する(物事は悪化するから)のは苦痛です。

フレームワークとの密結合

フレームワークには多くの「魔法」があります。フレームワークに過度に依存することで、すべての今後の開発者にその「魔法」を最初に学ばせることを強制します。それには数ヶ月かかることがあります。フレームワークによって数日でMVPを起動できますが、長期的にはそれらは不要な複雑性と認知負荷を追加する傾向があります。

さらに悪いことに、ある時点でフレームワークは、アーキテクチャに合わない新しい要件に直面したときに重大な制約となる可能性があります。ここから人々はフレームワークをフォークして独自のカスタム版を維持することになります。新参者が価値を提供するためにどのくらいの認知負荷を構築(つまり、このカスタムフレームワークを学習)する必要があるか想像してみてください。🤯

決してすべてをゼロから発明することを提唱しているのではありません!

私たちは多少フレームワークに依存しない方法でコードを書くことができます。ビジネスロジックはフレームワーク内に存在すべきではありません。むしろ、フレームワークのコンポーネントを使用すべきです。フレームワークをコアロジックの外側に置きます。フレームワークをライブラリのような方法で使用します。これにより、新しい貢献者はまずフレームワーク関連の複雑性の層をかいくぐる必要なしに、初日から価値を追加できるようになります。

Why I Hate Frameworks

レイヤードアーキテクチャ

こうしたエンジニアリングのすべてに、ある種の興奮があります。

私自身、何年もヘキサゴナル/オニオンアーキテクチャの情熱的な提唱者でした。あちこちで使用し、他のチームにも勧めました。プロジェクトの複雑性が上がり、ファイル数だけでも倍増しました。多くのグルーコードを書いているように感じました。絶え間なく変化する要件に対して、複数の抽象化レイヤーにわたって変更を行う必要があり、結局それはすべて面倒になりました。🤯

抽象化は複雑性を隠すことになっていますが、ここではただ間接性を追加しているだけです。何が悪くて何が欠けているかを把握するために、呼び出しから呼び出しへとジャンプしなければならないのは、問題を迅速に解決する上で重要でありながら、負担が大きいです。このアーキテクチャのレイヤー分離では、障害が発生するポイントにたどり着くために余計な手間が指数関数的に増え、トレースも断片的になりがちです。そのようなトレースはそれぞれ、私たちの限られたワーキングメモリのスペースを占有します。🤯

このアーキテクチャは最初は直感的に理にかなっているように見えましたが、プロジェクトに適用しようとするたびに、良いことよりもはるかに害をなしました。最終的に、古き良き依存関係逆転原則(DIP)を支持して、すべてを諦めました。学ぶべきポートやアダプター、不要な水平抽象化、外在的認知負荷、それらは必要ありません

コーディング原則と経験
@flaviocopes

そのような階層化によってデータベースや他の依存関係を迅速に置き換えることができると思うなら、それは間違いです。ストレージを変更すると多くの問題が発生し、信じてください、データアクセス層にいくつかの抽象化を持つことは最小の心配事です。せいぜい、抽象化は移行時間の約10%(もしあれば)を節約できますが、実際の痛みはデータモデルの非互換性、通信プロトコル、分散システムの課題、そして暗黙のインターフェースにあります。

APIの十分なユーザー数があれば、 契約で何を約束しようと関係ありません: システムのすべての観察可能な動作が 誰かによって依存されます。

私たちはストレージ移行を行い、約10ヶ月かかりました。古いシステムはシングルスレッドだったので、公開されるイベントは順次でした。私たちのすべてのシステムがその観察された動作に依存していました。この動作はAPIコントラクトの一部ではなく、コードに反映されていませんでした。新しい分散ストレージにはその保証がありませんでした - イベントは順不同で来ました。抽象化のおかげで、新しいストレージアダプターのコーディングには数時間しかかかりませんでした。順不同イベントやその他の課題への対処に次の10ヶ月を費やしました。 抽象化がコンポーネントを迅速に置き換える助けになるというのは今では面白いことです。

では、そのようなレイヤードアーキテクチャの高い認知負荷の代価を支払うのはなぜでしょうか、それが将来報われないのであれば? さらに、ほとんどの場合、いくつかのコアコンポーネントを置き換えるその未来は決して起こりません。

これらのアーキテクチャは基本的ではなく、より基本的な原則の主観的で偏った結果にすぎません。なぜそれらの主観的な解釈に依存するのでしょうか?代わりに基本的な規則に従いましょう:依存関係逆転原則、単一の真実の源、認知負荷、情報隠蔽。私たちのビジネスロジックは、データベース、UI、フレームワークなどの低レベルモジュールに依存すべきではありません。インフラストラクチャを心配することなくコアロジックのテストを書けるべきで、それだけです。議論

アーキテクチャのために抽象化レイヤーを追加してはいけません。実用的な理由で正当化される拡張ポイントが必要なときにそれらを追加しましょう。

抽象化レイヤーは無料ではありません、それらは私たちの限られたワーキングメモリに保持される必要があります

レイヤー

ドメイン駆動設計

ドメイン駆動設計にはいくつかの素晴らしいポイントがありますが、しばしば誤解されています。人々は「私たちはDDDでコードを書く」と言いますが、これは少し奇妙です。なぜならDDDはソリューション空間よりも問題空間に関するものだからです。

ユビキタス言語、ドメイン、境界づけられたコンテキスト、集約、イベントストーミングはすべて問題空間に関するものです。それらはドメインについての洞察を学び、境界を抽出するのを助けることを意図しています。DDDは開発者、ドメインエキスパート、ビジネス関係者が単一の統一された言語を使って効果的にコミュニケーションを取ることを可能にします。DDDのこれらの問題空間の側面に焦点を当てる代わりに、私たちは特定のフォルダ構造、サービス、リポジトリ、その他のソリューション空間技術を強調する傾向があります。

私たちがDDDを解釈する方法が独特で主観的である可能性があります。そして、この理解の上にコードを構築する場合、つまり、多くの外在的認知負荷を作り出す場合 - 将来の開発者は運命づけられています。🤯

Team Topologiesは、認知負荷をチーム間で分割するのに役立つはるかに良い、理解しやすいフレームワークを提供します。エンジニアはTeam Topologiesについて学んだ後、多少似たような心的モデルを開発する傾向があります。一方、DDDは10人の異なる読者に対して10の異なる心的モデルを作り出すように見えます。共通の基盤になる代わりに、不要な議論の戦場になります。

馴染みのあるプロジェクトでの認知負荷

問題は、馴染みやすさが単純さと同じではないことです。それらは同じように感じます - 同じような精神的努力なしに空間を移動するその同じ容易さ - しかし非常に異なる理由によるものです。あなたが使用するすべての「賢い」(読む:「自己満足的な」)で非慣用的なトリックは、他のすべての人に学習ペナルティを課します。彼らがその学習を行うと、コードを扱うのが困難でなくなります。だから、すでに馴染みのあるコードを簡潔にする方法を認識するのは困難です。これが私が「新人」にあまりにも制度化される前にコードを批評してもらうようにする理由です!

前の著者がこの巨大な混乱を一度にではなく、一つの小さな増分ずつ作り出した可能性があります。だから、あなたが一度にすべてを理解しようとする最初の人なのです。

私のクラスでは、ある日見ていた広がりのあるSQL格納プロシージャを説明します。巨大なWHERE句に何百行の条件文がありました。誰かがどうしてこんなにひどくなるのを許したのかと尋ねました。私は彼らに言いました:「2つや3つの条件しかないときに、もう一つ追加してもどんな違いも生みません。20や30の条件があるときに、もう一つ追加してもどんな違いも生みません!」

あなたが行う意図的な選択以外に、コードベースに作用する「簡素化力」はありません。簡素化には努力が必要で、人々はあまりにもしばしば急いでいます。

Dan Northのコメントに感謝

プロジェクトの心的モデルを長期記憶に内在化していれば、高い認知負荷を経験することはないでしょう。

心的モデル

学ぶべき心的モデルが多いほど、新しい開発者が価値を提供するのに時間がかかります。

プロジェクトに新しい人をオンボードするときは、彼らが持つ混乱の量を測定してみてください(ペアプログラミングが役立つかもしれません)。40分以上連続して混乱している場合 - コードで改善すべきことがあります。

認知負荷を低く保てば、人々は会社に参加してから最初の数時間以内にコードベースに貢献できます。

これらのアーキテクチャは非常に退屈で理解しやすいです。誰でも大きな精神的努力なしにそれらを把握できます。

アーキテクチャレビューにジュニア開発者を関与させましょう。彼らは精神的に負荷の大きい領域を特定するのに役立ちます。

ソフトウェアの維持は困難です、物事は壊れ、節約できるすべての精神的努力が必要になります。

結論

ちょっと想像してみてください。もし第2章で推論したことが実は正しくなかったとしたらどうなるでしょう? その場合、いま否定した結論だけでなく、これまで正しいと受け入れてきた前章の結論までも、正しくないかもしれません。🤯

実感できますか? 意味を理解するために記事のあちこちを行き来しなければならず(=浅いモジュール!)、しかも、全体としてとても分かりにくい段落になっています。私たちは、あなたの頭に余計な認知負荷をつくり出してしまったのです。同僚にこのようなことをしてはいけません。

賢い著者

私たちが行う仕事の本質的なものを超えるあらゆる認知負荷を減らすべきです。


LinkedInXGitHub

読みやすい版

コメント

Rob Pike
良い記事です。

Andrej Karpathy (ChatGPT、Tesla)
ソフトウェアエンジニアリングに関する良い投稿。おそらく最も真実で、最も実践されていない観点。

Elon Musk
真実だ。

Addy Osmani (Chrome、世界で最も複雑なソフトウェアシステム)
賢い開発者が最新の設計パターンとマイクロサービスを使って印象的なアーキテクチャを作成した無数のプロジェクトを見てきました。しかし、新しいチームメンバーが変更を加えようとしたとき、すべてがどのように組み合わさるかを理解するのに何週間も費やしました。認知負荷がとても高く、生産性が急落し、バグが倍増しました。

皮肉なことに?これらの複雑性を誘発するパターンの多くは「クリーンコード」の名の下に実装されていました。

本当に重要なのは、不要な認知負荷を減らすことです。時には多くの浅いモジュールではなく、より少ない深いモジュールを意味することもあります。時には小さな関数に分割するのではなく、関連するロジックを一緒に保つことを意味することもあります。

そして時には賢いものよりも退屈で素直な解決策を選ぶことを意味します。最高のコードは最もエレガントで洗練されたものではありません - 将来の開発者(あなた自身を含む)が迅速に理解できるコードです。

あなたの記事は、ブラウザ開発で私たちが直面する課題と本当に共鳴します。認知負荷について言った多くのポイントと完璧に一致する、現代のブラウザが最も複雑なソフトウェアシステムの一つであることについて、あなたは絶対に正しいです。

Chromiumでこれを処理しようとする一つの方法は、慎重なコンポーネント分離とサブシステム間(レンダリング、ネットワーキング、JavaScript実行など)の明確に定義されたインターフェースです。Unix I/Oでの深いモジュールの例と似ています - 比較的シンプルなインターフェースの背後にある強力な機能を目指しています。例えば、私たちのレンダリングパイプラインは信じられないほどの複雑性(レイアウト、合成、GPUアクセラレーション)を処理しますが、開発者は明確な抽象化レイヤーを通してそれと相互作用できます。

不要な抽象化を避けることについてのあなたのポイントも本当に心に響きます。ブラウザ開発では、新しい貢献者にとってコードベースをアプローチしやすくすることとウェブ標準と互換性の本質的な複雑性を処理することの間で常にバランスを取っています。

時には、複雑なシステムであっても、最も単純な解決策が最良の解決策です。

antirez (Redis)
それについて完全に同意します :) また、私が信じているのは、言及された「A Philosophy of Software Design」から欠けているのは「設計犠牲」の概念です。つまり、時には何かを犠牲にして、シンプルさ、またはパフォーマンス、またはその両方を取り戻すのです。私はこのアイデアを継続的に適用していますが、しばしば理解されません。

良い例は、私がハッシュアイテムの期限切れを持つことを常に拒否したことです。これは設計犠牲です。なぜなら、特定の属性をトップレベルアイテム(キー自体)にのみ持つ場合、設計はよりシンプルになり、値は単なるオブジェクトになります。Redisがハッシュ期限切れを得たとき、それは良い機能でしたが、実際に多くの部分に多くの変更を必要とし、複雑性を高めました。

別の例は、私が今やっていること、Vector Sets、新しいRedisデータタイプです。私はRedisがベクトルについての真実の源ではなく、それらの近似版を取ることができるだけであると決定したので、ディスク上の大きなフロートベクトルを保持しようとすることなく、挿入時の正規化、量子化を行うことができました。多くのベクトルDBは、ユーザーが入れたもの(完全精度ベクトル)を覚えているという事実を犠牲にしません。

これらは単なる2つのランダムな例ですが、このアイデアをどこでも適用しています。今のことは:もちろん正しいものを犠牲にしなければなりません。しばしば、非常に大きな複雑性を占める5%の機能があります:それを殺すのは良いことです :D

インターネットからの開発者
あなたは私を雇わないでしょう... 私はリリースされたエンタープライズプロジェクトの実績で自分を売り込みます。

設計パターンを話すことができる人と働いたことがあります。私はそのように話すことは決してできませんでしたが、彼を理解できる数少ない人の一人でした。マネージャーは彼を愛し、彼はあらゆる開発会話を支配することができました。彼の周りで働く人々は、彼が後ろに破壊の跡を残していると言いました。私は彼のプロジェクトを理解できる最初の人だと言われました。メンテナビリティは重要です。私は最もTCOを気にします。いくつかの企業にとって、それが重要なことです。

しばらくGithubに行っていなかった後にログインし、なぜか無作為に見える誰かによるリポジトリ内の記事に連れて行かれました。「これは何だ」と思って、ホームページにたどり着くのに少し問題があったので、それを読みました。その時は実際には登録していませんでしたが、それは素晴らしいものでした。すべての開発者が読むべきです。それは基本的に、プログラミングのベストプラクティスについて私たちが言われてきたことのほぼすべてが過度の「認知負荷」につながると言っていました。つまり、私たちの心が知的要求によって蹴られているということです。私はしばらくの間これを知っていました、特にクラウド、セキュリティ、DevOpsの要求で。

また、それが何十年もやってきた実践について説明していたので気に入りました、しかし人気がないためにあまり認めることはありません... 私は本当に複雑なものを書き、すべてのヘルプが得られる必要があります。

考えてみると、私が正しければ、それはGithubの人々、非常に賢い人々が開発者がそれを見るべきだと思ったために現れました。私も同意します。

Hacker Newsのコメント (2)