こんにちは、Xイノベーション本部の米久保です。
こちらは、電通総研テックブログ アドベントカレンダー2024の12月10日の記事です。
はじめに
ITエンジニア同士で会話するときに、「メンタルモデル」や「認知負荷」などの認知科学に由来する用語をよく耳にするようになりました。ソフトウェア開発は結局のところ人間によって行われる社会的な営みなので、人間の心のクセ、つまり認知特性に注目することはその活動をより良いものにする上で重要です。
デザインの領域では人間中心デザイン(Human-centered design, HCD)に代表されるように、人間の認知特性を考慮に入れたアプローチが主流となっており、ユーザーのニーズ、能力、行動に合わせたデザインを行います。
ソフトウェアの品質は外部品質と内部品質とに分かれます。外部品質は性能や使い勝手のようにユーザーが利用時に認識できるもので、内部品質は保守のしやすさのようにユーザーには見えないものです。実行時の実体としてのソフトウェアは、UIを通してその存在を知覚します。ソフトウェアの利用者に対して提供するUIとUXは、デザイナーが中心となりユーザーのニーズを満たすようにデザインします。
さて、ソフトウェアの物理的な実体であるソースコードのユーザーは誰でしょうか。それを書いたり読んだり変更したりする開発者ですね。内部品質はソフトウェアが素早くかつ持続的に価値を提供するために非常に重要ですが、ソースコードを日々取り扱うユーザーである開発者に焦点を当てて考える必要があるでしょう。
そのためには人間の認知特性を理解した上で、ソフトウェア開発プロセスや個々のアクティビティをデザインする必要があります。この記事では、「認知負荷」「認知バイアス」「エラー」を題材に、ソフトウェア開発活動の中でも特に設計にどう活用できるかを考察します。
認知負荷
認知負荷理論
認知負荷理論は、オーストラリアの教育心理学者、ジョン・スウェラー氏によって1980年代に提供されたものです。脳内の情報処理を行う作業場所、ワーキングメモリに対してかかる負荷のことを認知負荷と呼びます。ワーキングメモリの容量は大きくないため、認知負荷が高すぎるとオーバーフローを引き起こしてしまいます。ワーキングメモリをいかに効率に使うかが重要です。
認知負荷理論において、認知負荷は3つに分類されます(参考文献1,2)。
分類 | 説明 |
---|---|
課題内在性負荷 | 対象そのものが本質的に持つ複雑さによる認知負荷 |
課題外在性負荷 | 対象とは直接関係しない、外的要因による認知負荷 |
学習関連負荷 | 学習のための認知活動で発生する負荷 |
課題内在性負荷は、対象の本質的な複雑さに由来するものなので、その総量を減らすことは基本的にできません。「分割して統治せよ(Divide and conquer)」に従い、一度に取り扱う範囲を小さくすることは可能でしょう。
課題外在性負荷は、本来不必要な複雑さが持ち込まれたことによる負荷であり、ノイズと言えます。可能な限り除去するべきです。
学習関連負荷は対象を理解し、知識を活用して問題解決のための推論を行っていく上で適切な認知負荷と言えます。この負荷に割り当てるワーキングメモリが不足すると、「脳がパンクしてフリーズ」してしまうのです。
ソフトウェア設計と認知負荷理論
ソフトウェアが解決すべき問題は、たいてい複雑です。複雑なものを複雑なまま取り扱うのは大変なので、分割して小さくすることが大切です。
ただし、やみくもに分割すればいいというわけではありません。分割の仕方を間違えると、要素同士が複雑に絡まりあい、全体として「complicated」な構造になってしまいます。要素の数は多く多層的であっても、一貫性のある「complex」な構造を目指して分割する必要があります。
そのような構造化を行うためには、モジュールやコンポーネントに割り当てる責務を明確にした上で、それを実現するために必要な関連性の高いデータや操作を一箇所に集めた「高凝集」な設計が大切になります。そして、他の責務を担うモジュールやコンポーネントとのやり取りは明確に定義されたインターフェースを介した「疎結合」な設計とし、開発者が一度に取り扱う関心事を限定すべきです。
関心事の分離により、一度に取り扱う課題内在性負荷を適切なサイズにコントロールできます。では課題外在性負荷についてはどうでしょうか。負荷を軽減するために考慮すべき設計原則やプラクティスをいくつか挙げます。
方針と詳細の分離
インターフェースをうまく使い、方針と詳細を分離すること、依存性の向きを詳細から方針側へと向けることは重要です。データベース製品や物理的なテーブル構造、O/Rマッパーの実装制約といったテクノロジーの詳細に関する知識が、ビジネスロジックに入りこまないようにするべきです。適切なネーミング
関数名や変数名は一貫性があり、明確に意味を読み取れるネーミングを心がけましょう。プロジェクトで定められた規約に沿うことも重要です。一貫性のないネーミングはノイズとなり、読み手に混乱を与えます。抽象度の統一
publicメソッド、そこから呼ばれるprivateメソッド、さらにその先のprivateメソッド、と構造化する際は各メソッド内に記述する処理の抽象度をそろえることが大事です。異なる抽象度の処理が混じっていると、それに気を取られて認知負荷が高まってしまいます。
開発者にかかる認知負荷をできる限り低減することが重要です。
認知バイアス
認知バイアスとは思考の偏りを表す心理学の用語で、この存在によって人間はしばしば非合理な選択や判断をしてしまいます。近年注目されている行動経済学は、認知バイアスに基づく人間の合理的でない行動が経済活動や社会現象に与える影響を研究する学問です。
認知バイアスによるトラップは日常生活のさまざまな場面に潜んでいますが、ソフトウェア開発においても例外ではありません。
ある仕様を満たすソフトウェアを実装するとしましょう。皆さんは、コードを書き始める前にどの程度設計を行いますか? 20年以上前にUMLが流行した当初、詳細なクラス図やシーケンス図、ステートチャート図などを作成することが多くの現場で推奨されていました。コーディング前に事前に完全な設計を行うことをBDUF(Big Design Up Front)と呼びます。
物事は想定通りには運ばないものです。実装を進めるにつれ、考慮漏れ、エッジケースなどさまざまな問題が発見されます。その場しのぎの継ぎ接ぎの対応を積み重ねていくと、設計はどんどん歪んでいきます。リファクタリングの必要性は感じるものの、納期のプレッシャーに負けてそのままコミットしてしまった、という経験は誰しもあるのではないでしょうか。
このような現象には、どのような認知バイアスが影響しているでしょうか(参考文献 3)。
計画錯誤
過去に失敗をしていたとしても、今回はうまくいくだろうという楽観主義によって、甘い計画を立ててしまうバイアスを計画錯誤と言います。今回のケースでは、事前に設計をしたのだからうまく実装が進められるだろう、と楽観的に考えてしまうことです。確証バイアス
自分の信念や仮説を支持する情報ばかりに注目し、逆の証拠を軽視したり無視したりしてしまうのが確証バイアスです。事前設計の不完全さを示す兆候があってもそれを無視し、軌道修正することなく実装を進めていってしまいます。サンクコスト効果
このまま続けたら損を出すことがわかっているのに、これまで注ぎ込んだ労力や費用を考えるとやめることができない現象をサンクコスト効果と呼びます。設計のまずさに気づいてはいるが、今さら引き返すことができないという状態です。
このような認知バイアスのトラップを避け、設計をうまく進めるにはどうしたらよいでしょうか。
まず、事前に完全な設計をできるという考えを捨てることです。ベテランほど、解くべき問題に対する完成形がある程度イメージできてしまうので、それに固執しがちです。しかし、それはあくまで仮説としての設計と捉えましょう。
また、テスト駆動開発(TDD)のプラクティスも役立つでしょう。シンプルなテストケースから始めて、オンデマンドで漸進的に設計を進めるので、途中の軌道修正も容易です。Red - Green - Refactor のサイクルで頻繁にリファクタリングの機会が訪れるため、サンクコストからも解放されます。
エラー
人間はエラー(誤り)を起こします。D.A.ノーマン博士は、人間がエラーを起こす原因を理解しその原因が少なくなるようなデザインをすること、生じたエラーの発見と訂正が容易となるデザインをすることの重要性を説いています(参考文献 4)。また、エラーを以下のように分類しています。
- エラー
- ミステーク:間違ったゴールまたはプラン形成による、不適切な行為
- ルールベース:誤った規則に従ってしまう
- 知識ベース:誤った知識や不完全な知識により問題を間違って捉えてしまう
- 記憶ラプス:忘却によりゴール設定やプラン設定を誤ってしまう
- スリップ:意図とは異なる行為をしてしまうこと
- 行為ベース:行為を間違えてしまう
- 記憶ラプス:行為を忘れてしまう
- ミステーク:間違ったゴールまたはプラン形成による、不適切な行為
デザイン上の工夫としては、たとえば以下の方法が挙げられています。
制約
物理的な制約によってユーザーが行える操作に制限を加え、エラーを起こりにくくする。アンドゥー
システムにおいてアンドゥー(undo)コマンドを提供し、行った操作を元に戻せるようにする。
さて、ソフトウェア開発に目を移すと、JSTQBのシラバスには以下のように書かれています(参考文献 5)。
人間はエラー(誤り)を犯す。そのエラーがソースコードや他の関連する作業成果物の欠陥(フォールトまたはバグ)となる。
ソフトウェア開発においても、人間のエラーの防止、発見、訂正を行うための仕組みが重要と言えるでしょう。具体例としては以下が挙げられます。
リンタ(Linter)
ESLintのようなリンタを導入することで、プログラム上の誤りを早期に発見し訂正できます。構文エラーの他にも、未使用の変数など将来のエラーにつながる問題点を事前に発見できます。テスト駆動開発
Red - Green - Refactor のサイクルで小さな振る舞いを少しずつ実装していくため、誤った実装をした瞬間にそれを検出し、訂正できます。契約による設計(Design by Contract, DbC)
契約による設計は、コンポーネントの提供者とクライアントとの間で、双方が満たすべき条件を明確に表明するという設計手法です。具体的には、事前条件、事後条件、不変条件を明確に定義し、それが満たされない場合には処理を中止します。
他の具体例を見てみましょう。以下のTypeScriptのサンプルコードでは、Project
オブジェクトを生成しています。
const departmentId: number = 1; const managerUserId: number = 2; // of(departmentId: number, managerUserId: number, projectName: string): Project // 引数の順番を間違えてしまっている! const project = Project.of(managerUserId, departmentId, 'Sample Project');
of
メソッドの引数には、本来「部署ID」「PMのユーザーID」の順番に指定する必要があるのですが、その順番を間違えてしまっています。どちらのIDもnumber
型であるため、コンパイル時にこの誤りは検出できません。
筆者は過去に実際の開発プロジェクトにおいて、このような誤りを見たことがあります。不幸なことに、開発者がテストで用いた部署とユーザーとがどちらも同じID値であったために欠陥検出ができずに、後工程まで持ち越されてしまいました。(これは、テストデータ設計においても誤り防止の観点が必要であることを示しています)。
このような誤りは、型チェックにより検出をしたいものです。number
のようなプリミティブ型を直接使うのではなく、ドメインプリミティブ型を導入しましょう。
const departmentId = new DepartmentId(1); const managerUserId = new UserId(2); // of(departmentId: DepartmentId, managerUserId: ManagerUserId, projectName: string): Project // 以下はコンパイルエラー const project = Project.of(managerUserId, departmentId, 'Sample Project');
※脇道に逸れてしまいますが、TypeScriptの型は構造の一致によって判定されるため、単純にid
をプロパティに持つクラスを定義しただけでは上記のような型チェックをかけることができません。ブランド型という実装テクニックを使って、DepartmentId
とUserId
が異なる型と判定されるようにする必要があります。
export class DepartmentId { private _departmentIdBrand!: never; constructor(public id: number) {} } export class UserId { private _userIdBrand!: never; constructor(public id: number) {} }
アンダースコアのついたプロパティはダミーであり実際に使用されることはありません(そのためnever
型で定義します)。このプロパティの存在によって構造に差異が生じ、異なる型と見なされるわけです。
このように、人間が起こしてしまうエラーに対して、仕組みとして対処できないか検討してみるとよいでしょう。
まとめ
人間の認知の仕組みに注目し、開発者をユーザーと捉えて人間中心の設計を行うことは、開発者体験の向上に繋がります。開発者体験は内部品質の向上に寄与し、ひいてはソフトウェアが顧客やユーザーに提供する価値が増大するのではないでしょうか。
参考文献リスト
- 『認知負荷および認知負荷理論 (Cognitive Load Theory) をもう少し正確に理解するための心理学研究・知見の紹介』(Zenn記事)
- 『「快適な」学習のために〜認知負荷理論入門』(note記事)
- 『イラストでサクッとわかる!認知バイアス 誰もが陥る思考の落とし穴』(池田まさみ、森 津太子、高比良美詠子、宮本康司 プレジデント社)
- 『誰のためのデザイン? 増補・改訂版 認知科学者のデザイン原論』(D.A.ノーマン、新曜社)
- 『テスト技術者資格制度 Foundation Level シラバス』(JSTQB認定テスト技術者資格認定委員会)
執筆:@tyonekubo、レビュー:@kobayashi.hinami
(Shodoで執筆されました)