「プログラマー脳」を2025年08月23日に読んだ。
目次
- メモ
- 1.2 コーディングに影響を与えるさまざまな認知プロセス p7
- 1.3.2 プログラミング作業に関係する認知プロセス p11
- p16
- 2.1.4 なぜ馴染みのないコードを読むのは難しいのか? p21
- 2.3 読めるコードよりも見えるコードのほうが多い p27
- 2.3.1 アイコニックメモリ p28
- アイコニックメモリとコード p29
- 3.4 文法を長く記憶に留めるには p49
- 3.4.1 情報を記憶する2つの形態 p50
- 3.4.3 情報を覚えることで記憶が強化される p51
- 3.4.4 能動的に考えることで、記憶を強化する p52
- 5.1 「変数の役割」フレームワーク p80
- 5.1.2 ほぼすべての変数をカバーできる11の役割 p81
- 6.2 メンタルモデル p113
- メンタルモデルは長期記憶とワーキングメモリの両方に存在する p121
- 6.3 想定マシン p122
- 6.3.1 想定マシンとは何か p122
- 6.3.2 想定マシンの実例 p123
- 6.4 想定マシンと言葉 p125
- 6.5 想定マシンとスキーマ p129
- 6.5.1 なぜスキーマが重要なのか p129
- 6.5.2 想定マシンは意味論的なものか
- 7.1 2つ目のプログラミング言語を学ぶのは、最初の言語を学ぶよりも、なぜ簡単なのか p132
- 7.2.1 概念変化で誤認識をデバッグする p141
- 8.5.1 名前の雛形 p167
- アンラーニング p196
- 10.3.2 自動化するとなぜプログラミングが速くなるのか p200
- 11.2 中断されるプログラマー p215
- 11.2.2 割り込みが発生すると、その後どうなるのか p217
- 11.2.5 マルチタスクに関するいくつかの考察 p223
- 型システムはエラーを防ぐ p228
- 13.1 オンボーディングプロセスにおける問題点 p244
- 13.2.2 概念を具体的に見るか抽象的に見るかの違い p250
メモ
1.2 コーディングに影響を与えるさまざまな認知プロセス p7
ここで、それぞれのプログラムを読むときに脳内で起こる、3つの異なる認知プロセスに注目してみましょう。
すでに述べたとおり、それぞれに異なる種類の混乱は異なる認知プロセスの問題に関連しており、そして、それらはすべて記憶に関連しています。
この章では、ここからそれらの認知プロセスについて詳しく見ていくことにします。
知識不足というのは、長期記憶(LTM:Long-Term Memory)に、関連する情報が十分に存在しないことを意味します。
長期記憶というのは、あなたの記憶がすべて永続的に保持されている場所のことです。
一方、情報不足は、短期記憶(STM:Short-Term Memory)に関する問題です。
収集された情報は、一時的に短期記憶に保持されますが、たくさんの異なる事柄をいろいろな場所から探し出さなければならないような場合には、以前に獲得した情報を忘れてしまう可能性があります。
そして最後に、一度に多くの情報を処理しなければならないような場合は、思考が生じる場所であるワーキングメモリ(working memory)に負荷がかかってしまいます。
3つの種類の混乱がそれぞれ異なるどの認知プロセスと関係しているのかを簡単にまとめてみました。
■知識不足=長期記憶の問題
■情報不足=短期記憶の問題
■処理能力の不足=ワーキングメモリの問題
これらの3つの認知プロセスは、コードを読むときだけではなく、プログラミングの文脈だけを考えても、コードを書く、システムのアーキテクチャを設計する、ドキュメントを書くなど、すべての認知的活動に関係しています。
1.3.2 プログラミング作業に関係する認知プロセス p11
たとえば、クライアントから送られてきたバグの報告を見るときのことを考えてみましょう。
そのバグの原因は、off-by-oneエラー、つまり、ループの回数が1回ずれていることのようです。
このバグレポートは、あなたの感覚器官、すなわち、それを読んだのであれば目から、スクリーンリーダーを使っていたのであれれば耳から、あなたの脳に取り込まれます。
そして、バグを修正するには、あなたは数カ月前に自分で書いたコードを読まなければなりません。
コードを読んでいる間、読んだ内容は短期記憶に読み込まれています。
その間に、長期記憶から、数ヵ月前にどのような実装をしたか、たとえばアクターモデルを使ったことなどを思い出します。
さらに、長期記憶は、経験の記憶だけでなく、たとえばoff-by-oneエラーをどのように修正すればよいかといったような事実に関する情報をもたらします。
バグレポートの情報などの短期記憶からもたらされる新しい情報、長期記憶からもたらされる経験に基づく個人的な記憶と、似通ったバグの修正方法など関連する事実は、目の前の問題について考えるための場所であるワーキングメモリに渡されます。
p16
ハロルド・アベルソン(Harold Abelson)とジェラルド・ジェイ・サスマン(Gerald Jay Sussman)、ジュリー・サスマン(Julie Sussman)によって書かれた「Structure and Interpretation of Computer Programs」*2(1996年、MIT Press)という書籍に、「プログラムは人間が読むために書かれ、機械が実行するのは、そのついででなければならない」というよく知られた一文が書かれています。
それは確かにそうかもしれませんが、現実には、プログラマーはコードを読む練習よりもコードを書く練習ばかりしています。
2.1.4 なぜ馴染みのないコードを読むのは難しいのか? p21
例で示したように、コードを読んだ後にそのコードを再現するという作業は、簡単ではありません。
なぜコードを記憶するのは難しいのでしょうか。
その最大の理由は、短期記憶の容量が限られていることです。
目で見たコードを処理するために、そこに書かれているコードの一字一句を短期記憶に保持することは物理的に不可能です。
第1章でも解説したように、短期記憶は読み聞きした情報を短期間に保持します。
この「短期間」というのは本当に短期間で、研究によれば短期記憶は30秒以上記憶を保持できないともいわれています。
30秒を過ぎると、その情報は長期記憶に移動されるか、そうでない場合には永遠に失われてしまうのです。
誰かが電話越しに電話番号を読み上げているのに、書き留める術がない状況を想像してみてください。
すぐにどこかに書き留める方法(物理的なキャッシュですね)を見付けない限り、その番号を覚えておくことは難しいでしょう。
短期記憶に存在する制約は、記憶を保持できる時間だけではありません。
第2の制約は、その容量です。
コンピュータの場合と同じく、あなたの脳内でも、長期記憶装置は短期記憶装置よりもずっと大きな容量が用意されています。
とはいっても、コンピュータのRAMのサイズは数ギガバイトが確保されているかもしれませんが、人間の脳の短期記憶装置はずっと小さい容量しかありません。
あなたの短期記憶には、情報を記憶するスロットがたった数個あるだけです。
20世紀の最も影響力のある認知科学研究者の1人であるジョージ・ミラー(George Miller)は、1956年の論文「The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information(マジカルナンバー7プラスマイナス2:我々の情報処理における記憶容量の制約)」で、この現象について言及しています。
より新しい研究では、実際には短期記憶の容量はさらに小さく、2から6つしかないと見積もられています。
この容量の限界は、ほとんどすべての人に当てはまり、今のところ、科学者は短期記憶のサイズを大きくする確実な方法を見付けられていません。
人間が1バイト以下の記憶容量でさまざまなことを行うことができるのは、奇跡といってもよいのではないでしょうか。
2.3 読めるコードよりも見えるコードのほうが多い p27
短期記憶の詳細について見ていく前に、情報が脳に取り込まれる際にどういうことが起こるのかについて、少し見ておくことにしましょう。
情報が短期記憶に到達する前には、感覚記憶と呼ばれる場所を通過します。
コンピュータにたとえると、感覚記憶はマウスやキーボードなどの入力装置と通信を行うI/Oバッファのようなものだと考えることができます。
周辺機器から送られてきた情報は、I/Oバッファに一時的に格納されます。
これは、人間の感覚記憶も同じです。
視覚、聴覚、触覚からの入力は、ここに一時的に保持されます。
視覚、聴覚、味覚、嗅覚、触覚の5つの感覚はそれぞれに、感覚記憶の領域を持っています。
プログラミングの文脈で議論するに際して、そのすべてを見ていく必要はないので、本章では視覚に関する感覚記憶に限定して説明していくことにします。
その感覚記憶は、アイコニックメモリと呼ばれます。
2.3.1 アイコニックメモリ p28
情報が短期記憶に届く前に、情報はまず感覚器を通じて感覚記憶に入ってきます。
コードを読む際には、情報はあなたの目を通して入ってきて、その後、一時的にアイコニックメモリに保持されます。
アイコニックメモリについて理解するためのおもしろいたとえを1つ紹介しましょう。
大晦日の夜、あなたは線香花火を持っています。
あなたが線香花火を素早く動かすと、空中に絵を描くことができます。
なぜそんなことができるのかは、考えたことはないかもしれません。
しかし光の中にパターンを見ることができるのは、アイコニックメモリのおかげなのです。
アイコニックメモリは、見たばかりの映像から生まれる視覚刺激を、少しの間だけ記憶するようにできています。
別の体験も試してみましょう。
この文章を読んだ後に目を閉じると、ほんの少しの間、ページの形を「見」続けることができるはずです。
これもまた、アイコニックメモリのおかげなのです。
コードを読む際にアイコニックメモリがどのように使われているかを見ていく前に、それについて知られていることについて紹介します。
アイコニックメモリの研究の先駆者の1人は、1960年代に感覚記憶についての研究を行っていた、アメリカの認知心理学者ジョージ・スパーリング(George Sperling)です。
彼の最も有名な研究*4は、3人の被験者に、3×3または3×4文字のグリッドを見せるというものです。
このグリッドは、視力検査で使われるようなものですが、図2.5に示したように、すべての文字が同じサイズで書かれていました。
被験者は20分の1秒(0.05秒あるいは50ミリ秒)の間、画像を見せられ、その後、グリッドの一番上の行や左の列などを、ランダムに選んで思い出すように指示されました。
人間は、50ミリ秒では文字を読むことは不可能です。
なぜなら、人間の目の反応速度はおよそ200ミリ秒(5分の1秒)で、これは決して遅くはありませんが、この実験においては十分な速さではありません。
しかし、この実験においては、被験者は約75%の確率で、グリッドからランダムに選んだ行や列の文字をすべて記憶できていました。
この実験では、被験者にランダムな行を思い出してもらっているため、75%の正答率が得られたということは、被験者は約75%の確率で、グリッドにある9個または12個の文字のほとんど、あるいはすべてを記憶できていたことになり、これは短期記憶に記憶できるよりも多い数です。
これは、3人の被験者がとりわけ優れた記憶力を有していたというわけではありません。
彼らは、グリッドに書かれたすべての文字を再現することを求められると、一部分だけを思い出すことを求められたときに比べて、ずっと悪い結果しか出せませんでした。
この実験では、被験者は半分程度の文字しか思い出すことができないのが一般的なのです。
この2番目の発見は、短期記憶に関して我々がすでに知っている知識と一致します。
そして、スペリングが行った実験で得られた、短期記憶には最大6個の情報を保持できるという知見とも一致します。
しかし、多くの被験者が、3つか4つだけの文字だけを尋ねられたときに、そのすべてを記憶できていたという事実は、文字のグリッド全体が、どこかに記憶されていたことを示しています。
そして、その「どこか」は、容量が限られている短期記憶とは異なる場所のはずです。
スパーリングは、視覚から入ってくる情報の記憶される場所をアイコニックメモリと呼びました。
彼の実験からわかるように、アイコニックメモリに格納された情報のすべてが、短期記憶によって処理されるわけではないのです。
アイコニックメモリとコード p29
たった今見たように、あなたが見たものは、最初にアイコニックメモリに記憶されます。
しかし、アイコニックメモリに記憶されたものすべてが短期記憶で処理されるわけではありません。
したがって、コードを読み進める際には、あなたは何を処理するのかを選択する必要があります。
しかし、この選択は意識的に行われるものではなく、コードの特定の部分を意図せず見逃してしまうこともあります。
つまり、理論的には、あなたは短期記憶で処理できる以上のコードに関する情報を記憶できていることになります。
この知識を用いて、あなたはコードをより効率的に読むことができます。
つまり、まず短期間コードを見て、何を見たのかを振り返ることができるのです。
この「コードをざっと眺める」練習は、コードの最初のイメージをつかむのに役立ちます。
3.4 文法を長く記憶に留めるには p49
ここまでで、プログラミング言語の文法を記憶することは、コードのチャンク化に役立ち、検索にかかる時間を大幅に短縮できるため、重要であることがわかってきたはずです。
また、練習を繰り返す頻度についても説明しました。
1日ですべてのフラッシュカードを暗記しようとしたりせず、長い期間をかけて勉強するようにしましょう。
本章の残りの部分では、そのための練習方法について、もう少し見ていくことにします。
特に、記憶を強化するための2つのテクニック、「想起練習(積極的に何かを思い出そうとする)」と「推敲(新しい知識を既存の記憶と積極的に結び付ける)」を取り上げます。
ここまでの解説で、フラッシュカードの両面を単に読むだけでよいとはいっていないことに気付いたでしょうか。
これまでで述べたのは、文法を覚えるための「プロンプト」が書かれた面を読むようにということでした。
それは、積極的に思い出そうとすることで、記憶がより強固になるという研究結果が出ているからです。
たとえ完全な答えを知らなくても、頻繁に思い出そうとした記憶は、簡単に見付かるようになるのです。
ここからは、この発見についてより深く見ていくことで、プログラミングの学習に応用できるようにします。
3.4.1 情報を記憶する2つの形態 p50
記憶の仕方によってどのように記憶が強化されるのかについて踏み込む前に、まずこの間題をより深く理解する必要があります。
皆さんは、記憶は脳に蓄積されるかされないかの2つの状態しかないと思うかもしれませんが、実際にはもう少し複雑です。
カリフォルニア大学の心理学教授であるロバート・ビョーク(Robert Bjork)とエリザベス・ビョーク(Elizabeth Bjork)は、長期記憶から情報を取り出す際に、貯蔵強度と検索強度という2つの異なるメカニズムがあることを発見しました。
●貯蔵強度
貯蔵強度とは、特定の何かが長期記憶にどれだけきちんと保持されているかを示しています。
その内容を勉強すればするほど、その記憶は強くなり、忘れることができないも同然になっていきます。
4×3が12であることを忘れるなんて、想像できるでしょうか。
しかし、脳に記憶された情報すべてが、九九表のように簡単に思い出せるわけではないのです。
●検索強度
検索強度とは、特定の何かを思い出すのがいかに簡単かを示しています。
名前、曲、電話番号、JavaScriptのfilter()関数の文法など、知っているはずなのに、なかなか思い出せない、答えは喉まででかかっているのに、そこにたどり着けない、そんな経験があるのではないでしょうか。
このような情報は、貯蔵強度は高く、ようやく思い出したときには、それまで思い出せなかったことが信じられない気分になりますが、検索強度が低いために、思い出すのに時間がかかるのです。
一般に、貯蔵強度は増加する一方であり、最近の研究では、人は決して記憶を完全に忘れることはないとされています*5。
しかし、年月が経つにつれて検索強度は低下していきます。
ある情報を繰り返し学習することで、その情報の貯蔵強度は強化されます。
また、自分が知っている事実を繰り返し思い出そうとすると、特に勉強し直したりしなくても、検索強度は向上します。
3.4.3 情報を覚えることで記憶が強化される p51
前節の演習で、長期記憶に情報を保存するだけでは不十分であることがわかりました。
それに加えて、その情報を簡単に取り出せるようにする必要があるのです。
人生における多くのことと同様に、情報の取り出しも、何度も繰り返し練習すれば簡単にできるようになります。
文法などは本当に「覚えよう」としない限り、必要なときに思い出すのは難しいものです。
何かを積極的に覚えようとすることで記憶が強化されることは、アリストテレスの時代から知られている手法です。
まず紹介する記憶を思い出す練習に関する研究は、フィリップ・ボスウッド・バラード(Philip Boswood Ballard)という学校の教師が行ったもので、彼は1913年に「忘却と追憶」(Obliviscence and Reminiscence)という論文を発表しています。
バラードは『ヘスペラスの残骸』という、スキッパーという男が自身の自惚れゆえに彼の娘を死なせてしまうというストーリーの物語詩から16行を抜き出して、学生たちのグループに覚えてもらいました。
そして、学生たちがそれをどれくらい思い出すことができるかを調べている際に、あるおもしろいことに気が付いたのです。
まず彼は、学生たちに「後でもう一度同じテストをする」ということを伝えずに、2日後に再び覚えた詩を思い出してもらいました。
学生たちは後でまたテストを行わなければならないことは知らなかったので、その詩について、それ以上学ぶことはしていませんでした。
しかし2回目のテストでは、生徒たちは平均して10%多く、思い出すことができたのです。
さらに2日後には、学生たちはさらに多くの内容を思い出せたのです。
この結果に疑問を持ったバラードは、同様の実験を何度か繰り返しましたが、毎回同じような結果になりました。
これは、特に追加で学習を行わなくても、情報を積極的に思い出そうとするだけで、学習した内容をより多く記憶することができるようになることを意味しています。
さて、ここまでで、忘却曲線と記憶を思い出す練習の効果について理解できたと思います。
そして、なぜ知らない文法をその都度調べることがよくないのかという理由についても、よりはっきりわかったはずです。
調べるのはとても簡単で、よくある作業なので、脳はその文法を覚える必要がないと感じてしまうのです。
その結果、その文法に関する検索強度は低いままになってしまいます。
文法を覚えてないということは、もちろん悪循環を招きます。
覚えていないから、毎回調べなければなりません。
そして、覚えようとせずに調べ続けるから、プログラミングの概念の検素強度が向上せず、いつまでも調べ続けなければならなくなってしまうのです。
したがって、次に何かを検索によって調べようと思ったときは、積極的に文法を覚えてみようとするとよいでしょう。
その時点ではうまくいかなくても、思い出そうとする行為で記憶が強化され、次にまた同じ文法を使おうとした際に役立つかもしれません。
それがうまくいかないようなら、フラッシュカードを作って、より能動的に練習してみるとよいでしょう。
3.4.4 能動的に考えることで、記憶を強化する p52
前節では、記憶を検索することによって情報を積極的に記憶しようとすることで、その情報をより記憶に定着させることができることを学びました。
また、長期間にわたって情報を記憶する練習を行うことが最も効果的であることもわかったはずです。
しかし、記憶を強化する方法には、もう1つあります。
その方法とは、積極的に情報を考え、振り返ることです。
学んだばかりの情報について考えるプロセスのことを精緻化と呼びます。
精緻化は、複雑なプログラミングの概念を学習する際に特に効果的です。
精緻化の過程と、それを使って新しいプログラミングの概念をより効果的に学ぶ方法について掘り下げていく前に、脳の中の記憶の仕組みについて詳しく見ておく必要があります。
●スキーマ
脳内の記憶は、他の記憶や事実との関係性を持った、ネットワークを形成した形で保存されていることがわかりました。
このように思考とその関係が頭の中で整理されたものをスキーマと呼びます。
新しい情報を学んだとき、あなたの脳内では、長期記憶に保持する前に、その情報を脳の中のスキーマに当てはめようとします。
既存のスキーマにうまく適合する情報は、記憶しやすくなります。
たとえば、「5、12、91、54、102、87の数字を覚えてください」といわれて、その後で「そのうちの3つを選んだら、それに応じて素敵な賞品を差し上げます」といわれたとしましょう。
これは難しい作業です。
なぜなら、これは情報をつなげる「フック」がないからです。
この数字のリストは「素敵な商品をもらうために覚えていること」という新しいスキーマに記憶されます。
しかし、覚える数字が、1、3、15、127、63、31だったらどうでしょうか。
そのほうが簡単かもしれません。
少し考えれば、これらの数字が「二進数表現に変換すると1のみで構成されせる数字」という分類に当てはまることがわかると思います。
このような数字は覚えやすいだけではなく、感覚的に覚えられるので、数字を覚えようという気になるかもしれません。
これらの数字における最大のビットの数を知っていれば、問題を解くのに役立つことはすぐにわかるでしょう。
ワーキングメモリが情報を処理するとき、関連する事実や記憶を長期記憶で検索することを思い出してください。
記憶が互いに結び付いていると、記憶を見付けることも容易になります。
つまり、検索強度は、他の記憶と関連する記憶に対して高くなるわけです。
何らかの記憶を保存する際に、その記憶は既存のスキーマに適応するように変化することさえあります。
1930年代、英国の心理学者フレデリック・バートレット(Frederic Bartlett)は、『幽霊の戦争』と呼ばれるネイティブアメリカンの短い物語を被験者に覚えてもらい、数週間から数ヶ月後にその物語を思い出してもらう実験を行いました*6。
その中でバートレットは、被験者が物語を自分の信念や知識に合わせて変化させていることを、彼らの説明から見て取ったのです。
たとえば、一部の参加者は、彼らが無関係と思った細かい部分を省略しました。
また、弓を銃に置き換えるなど、より「西洋的」な、自分たちの文化に沿った物語に変えてしまった被験者もいました。
この実験から、人はただ言葉や事実だけを記憶しているのではなく、自分の記憶や信念に合うように記憶を修正していることがわかったのです。
記憶が保存されると同時に変化してしまうということには、欠点もあります。
同じ状況に置かれた2人の人が、その後にまったく異なる記憶をする可能性があるからです。
なぜなら、それぞれの人の考えや知識が、どのようにその記憶を保存するかに影響を与えるからです。
しかし一方で、記憶が変化することを利用して、既知の情報と追加された情報を一緒に保存しておくことで、記憶の保存をしやすくなるという利点もあります。
●精緻化による新しいプログラミング概念の学習
本章で既に見たように、記憶は、検索強度(情報を思い出すことの容易さ)が十分でない場合に忘れられることがあります。
バートレットの実験は、情報が長期記憶に初めて保持されるときでさえ、記憶の細部が変化したり、忘れられたりすることがあることを示しています。
たとえば、「ジェームズ・モンローは5代目の米国大統領だった」と教えられたとします。
すると、モンローが元大統領であることは覚えていても、5代目であることは記憶する前に忘れてしまうかもしれません。
その数字を覚えられなかったとしたら、その理由はたくさん考えられます。
たとえば、無関係だと思ったとか、複雑過ぎたとか、気が散っていたなどです。
記憶される量に影響を与える要因はたくさんあり、あなたの感情の状態も含まれます。
たとえば、今日たった数分で修正した簡単なバグよりも、1年前に一晩中机に向かっていたバグのほうが記憶に残っている可能性が高いのです。
自分の感情の状態をコントロールすることはできませんが、新しい記憶をできるだけ多く保存するためにできることはたくさんあります。
記憶の初期の符号化を強化するためにできることの1つに、「精緻化」と呼ばれるものがあります。
精緻化とは、覚えたいことを考え、それを既存の記憶と関連付け、新しい記憶を長期記憶にすでに保存されているスキーマに適合させることをいいます。
バラードの研究において、生徒たちが時間をかけて詩の言葉を覚えていくことができた理由の1つにも、精緻化があったかもしれません。
詩を繰り返し思い出す間に、生徒たちは足りない単語を補い、その都度、記憶を呼び起こすことができたのです。
また、詩の一部と他の記憶とを結び付けることもできたようです。
新しい情報をよりよく記憶したい場合には、その情報を明示的に精緻化していくことが有効です。
精緻化により、関連する記憶のネットワークが強化され、新しい記憶がより多くのつながりを持つようになって、それを取り出すことが容易になるのです。
Pythonのリスト内包表記のような新しいプログラミングの概念を学んでいるときを想像してみましょう。
リスト内包表記は、既存のリストを基に新しいリストを作成する方法です。
たとえば、すでにnumbersというリストに格納されている数字をそれぞれ二乗した要素を持つ新しいリストを作りたい場合、このリスト内包表記を使うことで、次のように書くことができます。
squares = [x*x for x in numbers]
この概念を初めて学んでいると思ってみてください。
この概念をよりうまく覚えたいのであれば、関連する概念を思い描いて意図的に精緻化することが非常に有効です。
たとえば、他のプログラミング言語での関連した概念、Pythonや他のプログラミング言語での別の書き方、この概念が他のパラダイムとどのように関連しているかなどを考えてみるとよいでしょう。
5.1 「変数の役割」フレームワーク p80
コードの背景を推論するにあたり、変数が中心的な役割を果たすことは間違いないでしょう。
変数がどのような情報を保持しているかを理解することは、コードについての理解を促し、コードを修正するための重要な鍵となります。
コード内の変数が何を表しているのかを理解できなければ、コードについて考えるための難易度はずっと高くなってしまいます。
つまり、変数に適切な名前を付けることは、読んでいるコードをより深く理解するための道標の役割を果たすということです。
東フィンランド大学のヨルマサヤニエミ(Jorma Sajaniemi)教授は、変数が理解しにくいのは、ほとんどのプログラマーが変数と関連付けるよいスキーマを長期記憶の中に持っていないためだと述べています。
サヤニエミ教授は、プログラマーが「変数」や「整数」のように範囲の広過ぎるチャンクや、「number_of_customers」といった特定の変数名のような小さ過ぎるチャンクを使う傾向があると主張しています。
しかし、プログラマーは、本来的にはその中間に位置する粒度のチャンクを使うべきです。
サヤニエミ教授は、このことを元に「変数の役割」フレームワークを設計しました。
5.1.2 ほぼすべての変数をカバーできる11の役割 p81
前節の例で示したように、変数が果たす役割は非常に似ています。
多くのプログラマーは、「ステッパー」や「最も重要な値の保持者」となる変数を利用しています。
実のところ、サヤニエミ教授は、ほとんどすべての変数は、たった11個の役割に分類できると主張しています。
・固定値
初期化された後、値が変化しない変数は「固定値」の役割に分類できます。
利用しているプログラミング言語が、変更できない変数を仕様として用意しているなら、それは定数として扱うことができ、そうでないなら、初期化された後、変更されない変数を利用することになります。
固定値となる変数の例としては、円周率などの数学的な定数、ファイルやデータベースから読み込んだデータなどが挙げられます。
・ステッパー
ループ処理を行う際に、ループのたびに値が変更(用意された値のリストをステップ)されていく変数があります。
これを「ステッパー」と呼びます。
ステッパーが取る値は、ループが開始されるタイミングで予測することができます。
ステッパーは、forループで利用されるときに標準的に使われる」のような変数の場合もありますが、二分探索を行う際の「size = size / 2」のように、繰り返しごとに探索する配列のサイズを半分にするといった、より複雑なステッパーが利用される場合もあります。
・フラグ*1
何かが発生したことを示したり、何かの情報が含まれていることなどを表す変数です。
is_setやis_available、is_errorなどの名前がよく利用されます。
フラグは真偽値であることが一般的ですが、整数値や文字列が使われる場合もあります。
・ウォーカー
ウォーカーは、ステッパーと同様にデータ構造を走査するために利用されますが、データ構造を走査する方法が異なっています。
ステッパーは常に、あらかじめわかっている値の一覧に対して反復処理を行います。
たとえば、「for i in range(0, n)」で表されるPythonのforループの変数iなどが、それに当たります。
それに対して「ウォーカー」は、ループ処理を開始する前には、どのように走査を行うのかが未知のケースで利用されます。
プログラミング言語の仕様によって、ウォーカーは、ポインタ変数であったり、整数のインデックスであったりします。
ウォーカーは二分探索のようにリストを走査することもできますが、スタックや木構造などのデータ構造を走査する場合のほうが一般的です。
ウォーカーの例として、リンクリストを走査して新しい要素を追加する位置を探す変数や二分木の検索インデックスなどが挙げられます。
・直近の値の保持者
一連の値を順に処理していく際に、もっとも最新の値を保持する変数を「直近の値の保持者」と呼びます。
たとえば、直近にファイルから読み込んだ行(line = file.readline())や、ステッパーで最後に参照された配列要素のコピー(element = list[i])などが、これに当たります。
・最も重要な値の保持者
ある特定の値を探すために、値の一覧に対して反復処理を行うのはごく一般的なことです。
そして、目的となる値、あるいはこれまでに見付かった中で最も適切な値を保持する変数を「最も重要な値の保持者」と呼びます。
最小値、最大値、あるいはある条件を満たす最初の値を保持する変数などが典型的な例でしょう。
・収集者
データを集めて、1つの変数に集約させているとき、その変数を「収集者(ギャザラー)」と呼びます。
次のように0から始まりループの中で値をまとめていくような変数が、収集者です。
sum = 0
for i in range(list):
sum += list[i]
こうした値は関数型言語やある種の関数的な側面を持つ言語では、functional_total = sum(list)のように、直接計算することも可能です。
・コンテナ
「コンテナ」とは、複数の要素を内包し、追加や削除が可能なデータ構造のことです。
コンテナの例としては、リスト、配列、スタック、ツリーなどが挙げられます。
・フォロワー
アルゴリズムによっては、前の値や次の値を保持して後から参照する必要な場合があります。
このような値を保持する役割を持つ変数は「フォロワー」と呼ばれ、常に他の変数とセットで利用されます。
フォロワー変数の例としては、リンクリストを走査する際に前の要素を指すポインタや、二分探索における下位インデックスなどが挙げられるでしょう。
・オーガナイザー
処理を進めるために変数を何らかの方法で変換する必要がでてくるのはよくあることです。
たとえば、言語によっては、文字列を文字配列に変換しないと、文字列内の個々の文字にアクセスできない場合があります。
また、あるリストを並べ直して保存したい場合もあるでしょう。
こうした際に、「オーガナイザー」を利用します。
オーガナイザーは値を並べ替えたり、異なる形式で保存するためだけに使われる変数のことです。
オーガナイザーは、一般的にはテンポラリ変数でもあります。
・テンポラリ
テンポラリ変数は、短期間だけ使われる変数で、tempやtという名前をよく利用します。
これらの変数は、変数の値を入れ替えたり、メソッドや関数内で何度も使われる計算結果を保持するために使われたりします。
6.2 メンタルモデル p113
これまで、実際に手を動かして、明示的に作成されたモデルを見てきました。
状態表、依存関係グラフ、実体関係ダイアグラムなどは、紙やホワイトボード上で扱うようなモデルです。
このようなモデルは、他者とコミュニケーションをはかるときや、問題を深く考えるときにも便利です。
しかし、これらのモデル以外にも、問題を考えるときに頭の中で利用し、実際に手を動かして何らかの作業をすることを必要としないモデルもあります。
これをメンタルモデルと呼びます。
前節では、問題を解決するためにどのようなデータの表現方法を利用するかが問題に対する考え方にも影響を与えることを学びました。
メンタルモデルも同様に、問題を考えることを助けてくれるものも、そうでないものもあります。
この節では、メンタルモデルとは何か、そして問題を解決する際にメンタルモデルをどのように活用すればよいかについて見ていくことにします。
メンタルモデルをコードに対して利用する例として、まずは木構造のトラバースについて考えてみることにします。
木構造といっても、もちろん、コードやコンピュータの中に実際に木が生えているわけではありません。
メモリ上に値が保持されており、それを木のような構造であると見なして、そう呼んでいるだけです。
このモデルを使うことで、コードの意味をよく理解できるようになります。
なぜなら、「ある要素を参照している要素の集合」と考えるよりも、「あるノードの子ノード」と考えるほうがわかりやすいからです。
メンタルモデルという言葉は、スコットランドの哲学者ケネス・クレイク(Kenneth Craik)が1943年に出版した書籍『The Nature of Explanation(説明の本質)』の中で初めて使われました。
クレイクは、メンタルモデルを自然界の現象の心理的な「スケールモデル」であると説明しています。
そして、クレイクによれば、人は自分の周りの世界を予測し、推論し、説明するために、メンタルモデルを利用しています。
メンタルモデルの定義として著者が最も好んでよく使うのは「メンタルモデルは、目の前の問題について推論するために、ワーキングメモリの中で概念を抽象化するものである」という説明です。
コンピュータを操作しているとき、私たちはさまざまな種類のメンタルモデルを作成しています。
たとえば、ファイルシステムについて考えるとき、フォルダ内にファイルがまとまっている構造を頭に思い浮かべるでしょう。
よく考えてみれば、ハードディスクの中には実際にはファイルやフォルダなどというものは存在せず、0と1で情報が記録されているだけに過ぎません。
しかし、その0と1の集合体を理解するにあたって、私たちはファイルやフォルダという構造を用いて頭の中を整理しているのです。
私たちは、コードについて考えるときにもメンタルモデルを使用します。
プログラミングを考えるときに使うメンタルモデルの例としては「特定のコード行が実行される」という考え方があります。
この考え方は、JavaやCなどのコンパイル言語においても特に違和感はないでしょう。
しかし、実行されるのはその行に対応して生成されたバイトコードであり、JavaやCのコード行そのものではありません。
したがって、コードが実行されるという考え方は、プログラムの実行方法を正しく、あるいは完全に表現しているわけではありません。
とはいえ、プログラムについて考える際には役立つモデルなのです。
このモデルは、私たちを混乱させる場合もあります。
たとえば、高度に最適化されたコードをデバッガで実行したとしましょう。
コンパイラによってコードが最適化されたことで、デバッガがソースコードから期待されるのとは異なる動きをしてしまうケースなどが、それに該当します。
メンタルモデルは長期記憶とワーキングメモリの両方に存在する p121
メンタルモデルは、ワーキングメモリで利用されるという意見と、長期記憶に保持されるという意見は、どちらも正しいとされています。
この2つの意見はお互いに矛盾しているように見えるかもしれませんが、本章で見てきたように、どちらの意見にも価値があり、実際に両者はうまく補完し合っているといえるでしょう。
1990年代の研究によると、長期記憶に保持されたメンタルモデルがワーキングメモリでのメンタルモデルの構築に影響を与えるという、そのどちらの意見もある程度正しいことが示されています*2。
6.3 想定マシン p122
前節では、メンタルモデル、つまり、ある問題について考えるときに脳内で形成される表現を見てきました。
メンタルモデルは汎用的なものであり、さまざまなビジネス領域において用いられるものです。
プログラミング言語の研究では、それに加えて、想定マシンの概念も使われています。
メンタルモデルとは、世の中のあらゆるものをモデル化したものですが、想定マシンはコンピュータがコードを実行する方法について考えるときに使うモデルです。
より正確には、想定マシンは、コンピュータが何をしているかを考えるために使うコンピュータの抽象的な表現のことを指します。
プログラムやプログラミング言語がどのように動作するかを理解しようとしたとき、ほとんどの場合、物理的なコンピュータの動作に関する詳細を気にする必要はありません。
ビットがどのように電気的に保存されるのかを気にする必要はまったくないわけです。
そんなことよりも、2つの値を入れ替えたり、リストの中で最大の要素を見付けたりといった、より抽象度の高いレベルでのプログラミング言語の挙動に集中するほうが有益です。
実際の物理的な機械と、より抽象的なレベルで機械が行う処理の違いを区別するために、「想定マシン」という言葉を使います。
たとえば、JavaやPythonの想定マシンは、参照の概念を必要としますが、メモリアドレスの概念は必要ありません。
メモリアドレスは、JavaやPythonでプログラムを書く際には意識する必要のない、言語実装に隠蔽されたものであると考えられます。
想定マシンは、このようにすべての概念が揃ってなくても、特定のプログラミング言語の実行に関係する機能を正しく抽象化したものです。
したがって、想定マシンは、不完全であったり、複数が矛盾したまま存在することができるメンタルモデルとは異なっています。
想定マシンとメンタルモデルの違いを理解するために筆者が見付けた最もわかりやす説明は、「想定マシンは、コンピュータがどのように動作するかの説明である」というものです。
その想定マシンを内面化し簡単に使えるようしたものが、メンタルモデルだといえます。
そして、プログラミング言語を学べば学ぶほど、あなたのメンタルモデルは想定マシンに近づいていくことになります。
6.3.1 想定マシンとは何か p122
想定マシンという言葉は少し暗号めいているので、実際の例やプログラミングの際の利用方法を見る前に、この用語自体をもう少し解説しておきましょう。
まず最初に、想定マシンは「マシン」、つまり、私たちが自分の意志で操作できるものを表しているということを押さえておきましょう。
これが、たとえば物理や化学などにおけるメンタルモデルと大きく異なる重要なポイントです。
科学的な実験によって周囲の世界を理解することは可能ですが、実験できないもの、少なくとも安全に実験ができないものもたくさんあります。
たとえば、電子や放射線の挙動についてのメンタルモデルを構築する場合、自宅や職場において、安全に学習できる実験装置を用意することは難しいでしょう。
一方、プログラミングの場合は、機械を操作することはいつでも可能です。
想定マシンは、コードを実行するマシンに対する正しい理解を得るためにデザインされたものです。
想定マシンの「想定(notional)」という言葉は、オックスフォード英語辞典によると「提案、推定、理論として存在する、またはそれに基づいている現実には存在しない」という意味です。
コンピュータがどのように動作しているかを考えるとき、その詳細な動作すべてを知りたいわけではありません。
私たちが知りたいのは、その動作の仮説、あるいは理想化されたバージョンであることがほとんどです。
たとえば、変数xに12という値を代入することを考える際に、その値が格納されているメモリアドレスやxとそのメモリアドレスを結ぶポインタについて知りたいとは思っていないことがほとんどで、単に現在の値を持つどこかに格納された実体が存在するということを認識できさえすれば十分なはずです。
想定マシンとは、コンピュータの機能を、その時々に必要な抽象度で理解するための抽象化された存在といえます。
6.3.2 想定マシンの実例 p123
想定マシンのアイデアは、サセックス大学の教授であるベン・デュ・ブーレイ(Bendu Boulay)が、1970年代にLOGOの研究をしているときに思いついたものです。
LOGOはシーモア・パパート(Seymour Papert)とシンシア・ソロモン(Cynthia Solomon)によって設計された教育用プログラミング言語で、タートルという、線を引く機能を持ち、コードを書くことで操作できる実体を導入した最初の言語です。
LOGOという名前は、ギリシャ語で言葉や思考を意味する「ロゴス」に由来しています。
デュブーレイ教授は、子供たちや教師にLOGOを教えるための戦略を説明する際に、初めて「想定マシン」という言葉を使いました。
彼は「プログラミング言語の構造によって暗示されるコンピュータの理想化されたモデル」と想定マシンを表現しました。
デュブーレイ教授の説明には、手書きの図なども含まれていましたが、主に比喩を活用したものになっていました。
たとえば、デュ・ブーレイ教授は、言語実行モデルの比喩として工場労働者という言葉を使っています。
工場労働者は、パラメータ値を聞く耳、出力を話す口、コードに記述された動作を実行する手を持っており、コマンドや関数を実行できます。
このように、彼はプログラミングの概念をまずはシンプルに表現した上で、組み込みコマンド、ユーザー定義プロシージャや関数、サブプロシージャ、再帰などの概念を徐々に追加し、LOGO言語全体を説明できるように構築していきました。
想定マシンは、コードを実行する機械の仕組みを説明するためのものなので、機械と同じような性質を持ち合わせています。
たとえば、物理的な機械と同じように、想定マシンには「状態」という概念が存在しています。
変数を箱に見立てた場合、この仮想的な箱は、空になっている場合も、値が「入っている」場合もあり得ます。
また、想定マシンには、ハードウェアとそれほど結び付いていない場合もあり得ます。
コードを読み書きする際、実行される機械については抽象的な表現を使うことは珍しくありません。
プログラミング言語で計算の仕組みを考えるとき、コンピュータの動作を数学者の動作になぞらえることがよくあります。
次のJavaのコードに例に考えてみましょう。
double celsius = 10;
double fahrenheit = (9.0 / 5.0) * celsius + 32;
このコードを読み解くにあたって、おそらく、まずは頭の中で2行目の変数celsiusを10に置き換えるはずです。
続いて、演算子の優先順位を示す括弧を頭の中で付けるかもしれません。
double fahrenheit = ((9.0 / 5.0) * 10) + 32;
暗算を行うためにコードを脳内で組み替えることは、計算を行うための完璧なモデルとなりますが、実際に機械が行う計算を完全に再現しているわけではなく、機械の処理方法はまったく異なっています。
機械は、式の評価のためにスタックを利用するでしょう。
そして、機械は式を逆ポーランド記法に変換し、まず9.0 / 5.0の計算結果をスタックに積みます。
そして、さらにそれを取り出して10倍し、続く計算のために再びスタックに積むでしょう。
これは、想定マシンの動きが正しくなくても有用であることのよい例といえます。
私たちは、これを「代入型想定マシン」と呼ぶことにします。
この想定マシンは、スタックを利用するモデルよりも、ほとんどのプログラマーのメンタルモデルに近いものになっています。
6.4 想定マシンと言葉 p125
機械がどのように動作するかを読み解くためだけではなく、コードについて議論するためにも、想定マシンが利用されることがあります。
たとえば、変数に値が格納されている物理的な「箱」が存在しているわけでもないのに、変数に値が「格納されているという言い方をします。
これは、変数というものを、値の入った箱のようなものだと考えていることを意味しています。
プログラミングに関して利用する言葉には、このような想定マシンの存在を示唆し、ある種のメンタルモデルに導くようなものが数多くあります。
たとえば、ファイルが「開いている」「閉じている」という言い方をしますが、より正確にいえば、ファイルの読み取りが許可されているか、禁止されているかを意味しています。
また、「ポインタ」という言葉を使いますが、これはポインタがある値を「指す(ポイントする)」ことを意味し、関数がスタックに値を積み、呼び出し元(この考え方もメンタルモデルです)がその値を使えるようにすることを「返す」といいます。
このように、想定マシンは、物事がどのように動作するのかを説明するために多用されており、コードについて話すときに使う言語や、プログラミング言語そのものに入り込んでいます。
ポインタの概念は多くのプログラミング言語に存在し、多くのIDEでは特定の関数の「呼び出し元」の場所を調べることができるようになっています。
6.5 想定マシンとスキーマ p129
想定マシンには問題点もありますが、一般的にいって、プログラミングの際の試行方法としてうまく機能しています。
その理由は、本書ですでに取り上げたいくつかのトピックと関わりがあります。
有用な想定マシンは、プログラミングの概念を、人々がすでに強いスキーマを形成している日常の概念に関連付けるのです。
6.5.1 なぜスキーマが重要なのか p129
スキーマとは、長期記憶が情報を保持するための方法のことです。
たとえば、「箱」という概念は、多くの人が簡単に連想できるもののはずです。
箱の中に何かを入れる中身を見るために箱を開ける、そしてそれを取り出すといった動作は、多くの人が慣れ親しんでいるものだからです。
したがって、変数を箱と考えることは、余計な認知的負荷を生じさせることがありません。
もし、「変数は一輪車のようなものだ」といったとしたら、どうでしょうか。
多くの人は一輪車で何ができるのか、どういう操作をするのかということに対して強いメンタルモデルを持っていないはずなので、あまり変数を理解する役には立ってくれないでしょう。
もちろん、人々がどういうことに慣れ親しんでいるのかについては、世代や文化など、さまざまな状況によって異なってきます。
したがって、何かを説明しようとするときには、相手がよく知っているであろうものを選んで使うことが重要です。
たとえば、インドの農村で子どもたちにコンピュータの機能について説明しようとするときに、コンピュータを象に、プログラマーを象の調教師に見立てて説明した教育者もいました。
それらは、子供たちが馴染み深い存在だったからです。
6.5.2 想定マシンは意味論的なものか
想定マシンを定義する方法は、コンピュータやプログラムの意味論の定義を思い起こさせるかもしれません。
意味論はコンピュータサイエンスの分野の1つで、構文(syntax)と呼ばれるプログラムの見た目ではなく、その意味(semantics)について研究するというものです。
もしかしたら、想定マシンは、単にその意味論的な表現なのではないかと思うかもしれません。
しかし、意味論は、コンピュータの働きを数式で表し、数学的な精度をもって形式化することを目的としています。
つまり、意味論では、想定マシンのように細部を抽象化するようなことはせず、細部を正確に、完璧に定義することを目的としているのです。
したがって、想定マシンは単なる意味論とは異なっているのです。
7.1 2つ目のプログラミング言語を学ぶのは、最初の言語を学ぶよりも、なぜ簡単なのか p132
前章では、長期記憶に格納されたキーワードとメンタルモデルがコードの理解に役立つことを学びました。
何かを学習したとき、その知識が別の領域でも役に立つことがあります。
これは転移(transfer)と呼ばれています。
転移は、すでに知っている情報が新しいことをするのに役立つときに起こります。
たとえば、チェッカーの遊び方を知っていれば、それと似たルールを持つチェスを学ぶのが簡単になります。
同様に、Javaをすでに知っていれば、変数、ループ、クラス、メソッドなどの基本的なプログラミングの概念をすでに知っていることになるので、Pythonを学ぶのは難しくないはずです。
また、デバッガやプロファイラの使い方など、Javaのプログラミング中に身に付けたスキルのいくつかは、次に別のプログラミング言語を学ぶときに役に立つ可能性があります。
長期記憶に蓄積されているプログラミングに関する知識は、新しいプログラミングの概念を学習する際に、2つの方法で活用されます。
まず、プログラミング(あるいは他のトピックでも同様ですが)に関してすでに多くのことを知っている場合、同じトピックについてより多くを学ぶことは非常に容易になります。
これは学習中の転移(transferduringlearning)と呼ばれます。
第2章で学んだように、新しい情報に遭遇すると、その情報は感覚記憶から短期記憶に移行し、さらにワーキングメモリに入力されて処理されます。
このプロセスを図7.1に示しました。
そして、新しいプログラミングの概念について考え始めることでワーキングメモリが活性化すると、長期記憶も活性化して関連情報の検索を開始します。
図7.1に示すように、長期記憶の検索が行われると、その結果、新しく学習した情報に関連する情報が見付かる可能性があります。
長期記憶の中に何らかの関連情報、たとえば、手続き記憶、スキーマ、プラン、エピソード記憶などが見付かると、それもワーキングメモリに送られます。
たとえば、すでにJavaについての知識があり、Pythonのメソッドについて学んでいるとき、Javaのメソッドについての記憶を利用するかもしれません。
その記憶を使うことで、実際にはPythonのメソッドはJavaのメソッドと少し違う動きをするにもかかわらず、より早く理解できるようになるのです。
第3章では、新しい概念を学ぶときに行う精緻化というものについて取り上げました。
精緻化とは、新しい情報をすでに知っている事柄と明示的に関連付けることを意味します。
精緻化が学習に役立つ理由は、長期記憶で関連情報を具体的に検索することで、タスクの実行に役立つ関連情報が見付かる可能性が高くなり、学習中の転移に効果的だからです。
長期記憶に格納された知識が学習に役立つ2つ目の方法は、学習の転移(transfer of learning)と呼ばれるものです。
これは、すでに知っている知識がこれまでよく知らなかった状況に適用できるときに発生します。
認知科学について人々が「転移」という言葉を使うときのほとんどは、この「学習の転移」を意味しています。
学習の転移は、まったく意識していないときに起こっているケースもあります。
たとえば、新しいズボンを買ったとき、ボタンの留め方を改めて考える必要はありません。
そのズボンも、ボタンも、それ自体はあなたにとっては初めてであったとしても、どう扱えばいいかはすでに知っているわけです。
同じように、新しいノートパソコンを買ったとき、そのノートパソコン自体は新しいものであっても、キーボードをどう操作すればよいかは、考えなくてもわかるでしょう。
一方で、新しいプログラミング言語を学ぶときなどに、意図的に行われる学習の転移もあります。
たとえば、Pythonを知っている人がJavaScriptを学ぶ場合、「Pythonではループの本文をインデントする必要があるけれど、JavaScriptでも同じだろうか」とはっきりと意識して考えるはずです。
学習の転移は、学習中の転移とよく似たものです。
なぜなら、どちらの場合も、脳は適用すべき関連する情報を長期記憶で検索するからです。
7.2.1 概念変化で誤認識をデバッグする p141
誤認識とは、間違った考えを確信を持って信じてしまっている状態です。
その確信のために、誤認識している相手に、その考えを改めてもらうのは難しいものです。
その誤認識を解くには、多くの場合は間違いを指摘するだけでは不十分で、誤った考え方を新しい考え方に置き換える必要があります。
つまり、初心者のプログラマーに「変数は変更できるものだ」と教えるだけでは不十分で、変数という概念を新たに理解させる必要があるのです。
すでに知っているプログラミング言語に基づいて形成されてしまった誤認識を、新しく学ぶ言語に適したメンタルモデルに置き換えるプロセスを概念変化(conceptual change)と呼びます。
概念変化とは、既存の考え方が新たな知識によって根本的に変更され、置き換えられ、一体化したものです。
概念変化と他の種類の学習との違いは、概念変化が既存のスキーマに新しい知識を追加するのではなく、既存の知識を変化させるものであるという点です。
長期記憶に記憶されている、すでに学習した知識を変更する必要があるため、概念変化の学習は通常の学習よりも困難です。
そのため、誤認識は非常に長い期間、残ることがあります。
なぜその考え方が間違っているのかという情報を提示されるだけでは誤認識を解くことができない、あるいは誤認識を完全には解き切れない場合がほとんどなのです。
それゆえ、新しいプログラミング言語を学ぶ際には、それ以前のプログラミング言語に関する既存の知識を「アンラーニング」することに多くのエネルギーを費やす必要があるのです。
たとえば、Javaをすでに知っている人がPythonを学ぶ場合、変数の型を必ず定義しなければならないなど、いくつかの文法知識をアンラーニングしなければなりません。
また、変数の型に依存してコード上の判断を行うなど、Javaでは通用した慣習のいくつかも覚え直さなければなりません。
Pythonが動的型付け言語であるという単純な事実を理解するのは難しくなくても、プログラミングをしながら型について考えることを学ぶには、概念変化が必要なため、時間がかかる可能性があるのです。
8.5.1 名前の雛形 p167
フェイテルソン氏は、開発者に変数名を選択させる調査において、開発者が他の開発者と同じ変数名を選ぶことはほとんどなくても、他の開発者が選んだ名前を理解することは可能であることを発見しました。
彼が行った実験において、ある名前が選ばれた際に、ほとんどの開発者がその名前の意味を理解できていたからです。
フェイテルソン氏は、このような現象が起こるのは開発者が名前の雛形(name mold)を使っていたからだと述べています。
名前の雛形とは、変数名を決める際に利用される典型的なパターンのことを意味します。
たとえば、ある人が1ヵ月に受け取ることができる最大の手当(benefits)を表す名前が必要だった場合、表8.4に示すような名前が選ばれました。
ここでは名前を正規化しているので、maxは、実際にはmaxとmaximumの可能性があり、benefitはbenefitsになる可能性があります。
この表は、選ばれた回数の多い順で並べてあります。
このリストを見ると、フェイテルソン氏の研究で2人の開発者が同じ変数名を選ぶ確率が非常に低かった理由がよくわかります。
開発者は、さまざまな雛形を利用していたため、多種多様な変数名が利用される結果になったのです。
これらの名前は概念的には同じ意味を表していますが、その書き方にはさまざまバリエーションがあります。
フェイテルソン氏の実験に参加した被験者は、同じコードで作業している人たちではありません。
しかし、共同で同じコードを開発している人たちの間であっても、こうした雛形の違いが発生している可能性はあります。
認知的負荷と長期記憶の特性について現在までに学んだことを踏まえると、同じコードベースにおいて異なる雛形が複数存在することは、あまりよいことだとはいえないでしょう。
認知的負荷の面では、変数名中の単語(この場合は「benefit」)がどういう概念を指しているのかを考えることと、そうした単語が変数中のどのような場所に位置しているかを調べることは、大きな認知的負荷を発生させます。
変数名がどういう意味なのかを理解することと、それがどういう概念を表しているのかを理解することは別物です。
本章では、キャメルケースやスネークケースといった変数の書き方が訓練によってより早く認識できるようになることを紹介しました。
名前の雛形について、同様の研究はこれまで行われていませんが、おそらく同じ雛形を使った変数を多く読むと、同様に特定の雛形で書かれた変数をより容易に認識できるようになると思われます。
第2に、変数名が類似している場合、同じ型を使うことで長期記憶から関連情報を見付けやすくなる可能性が高くなります。
たとえば、max_benefit_amountという変数がコード中にあったとします。
同じコード中に以前に最大利息額を計算するためのmax_interest_amountという変数があったとすれば、それを関連付けることができ、理解しやすい可能性があります。
最大利息額を計算する変数名がinterest_maximumなどの異なる雛形を利用していたら、長期記憶はそれらを関連付けることに苦労するかも知れません。
類似した雛形を利用することで、長期記憶とワーキングメモリをより効率的に働かせることができるので、1つのコードベース中では、使われる雛形の数をなるべく少なくするのが好ましい状態です。
そのためには、プロジェクトを始めるときに変数の雛形について合意を得るとよいでしょう。
既存のコードベースで雛形を統一していく場合は、コード中の既存の変数名のリストを作成することから始めて、すでに使用されている雛形を確認し、今後どういう方針にするのかを決めるとよいでしょう。
アンラーニング p196
潜在記憶は既知のタスクを素早く実行するのに役立つことがわかりましたが、潜在記憶を持つことはよいことばかりとは限りません。
第7章で「負の転移」という考え方を説明したことを思い出してください。
これは、何かの知識が、他のことを学ぶ上で障害となりうるという話でした。
同様に、潜在記憶を多く持っている場合に、柔軟性が損なわれる可能性があるのです。
たとえば、Qwertyキーボードのタッチタイピングを一度覚えてしまうと、Dvorakキーボードの使い方を覚えるのは、Qwertyを覚えていない人よりも難しくなってしまいます。
これは、直感的に「こうすればいい」と思ってしまう記憶が大量に残ってしまっているからです。
また、最初に学んだ言語とはまったく異なる構文を持つプログラミング言語を2番目の言語として学んだことがある人は、潜在記憶から逃れる難しさを経験したことがあるかもしれません。
たとえば、C#やJavaからPythonに移行した場合、しばらくの間はブロックや関数の周りを無意識に中括弧で囲んでしまうことがあります。
私自身、数年前にC#からPythonに移行しましたが、リストを反復処理する際にforではなくforeachと、いまだによく入力してしまいます。
これは、C#のプログラミング時に構築した潜在記憶が今も強く残っているためです。
10.3.2 自動化するとなぜプログラミングが速くなるのか p200
テクニック(懐疑的な人はトリックというかもしれませんが)の引き出しをたくさん作ることで、さまざまな状況に対応できるテクニックの永遠に成長し続けるツールボックスともいえるものを形作ることができます。
アメリカの心理学者ゴードン・ローガン(Gordon Logan)は、自動化は、長期記憶のエピソード記憶部分から、記憶を取り出すことによって行われると主張しています。
長期記憶には毎日の生活に関する一般的な記憶も保存されています。
方程式を解いたり、文字を読んだりといったタスクを実行すると、新しい記憶が形成されます。
そのタスクに関する記憶をクラスに見立てると、そのインスタンスであるといえます。
そう考えると、すべての記憶は「因数分解に関する記憶」といったように、より抽象的なクラスのインスタンスであると考えることができます。
この考え方は、インスタンス理論(instance theory)と呼ばれています。
インスタンス理論では、前に経験したのと同じようなタスクに直面すると、そのタスクについて何かを推論する(そのタスクに関するインスタンス記憶が足りていない人は推論をするかもしれませんが)のではなく、かつてどのようにそのタスクを実行したのかを思い出し、同じ方法を適用します。
ローガンによると、タスクの実行が完全にエピソード記憶の情報のみによって行われ、一切の推論を行わなくなったときに、完全な自動化が達成されることになります。
自動化されたタスクの実行では、そのタスクについて明示的に考えるよりも、記憶から検索するほうがずっと高速であり、ほとんど意識することなく実行できるため、自動化されていないタスクの実行よりも素早く、苦労なく実行できます。
また、考えながらタスクを実行したときには、完了後に再度確認するようなこともやりたくなりますが、完全に自動化されたタスクであれば、そんな必要を感じることもありません。
プログラミングや問題解決に必要なスキルの多くについて、あなたはすでに自律的な段階に達していると思われます。
forループの記述、リストへのインデックス付け、クラスの作成などは、すでに自動化されていることでしょう。
そのため、これらのタスクはプログラミング中の認知的負荷を増加させることはありません。
しかし、経験やスキルレベルによっては、実行に苦労するタスクもあるでしょう。
前述したように、筆者自身、Pythonを学び始めた頃は、forループの書き方に苦戦しました。
フラッシュカードを使って練習もしていましたし、もちろんforの構文が覚えられないわけではありません。
しかし、forをキーボードで入力しようとすると、指が勝手にforではなくforeachと打ち込んでしまうことがよくありました。
筆者の潜在記憶は、配線を組み替える必要があったのです。
潜在記憶を呼び覚ますテクニックを紹介する前に、自分の現在のスキルをチェックして、どこが改善できるかを知っておくとよいでしょう。
11.2 中断されるプログラマー p215
近年、多くのプログラマーは、仕切りのないオープンな空間のオフィスで仕事をしています。
こういう空間は、集中を妨げ、思考に割り込みを発生させる要素がたくさんあります。
このような割り込みは、私たちの脳と生産性にどのような影響を与えるのでしょうか。
現在オランダのデルフト工科大学に所属しているリニ・バン・ゾーリンゲン(Rini van Solingen)教授は、1990年代半ばにすでにプログラマーの割り込みについて研究を行っていました。
バン・ゾーリンゲン教授が2つの異なる組織で調査を行ったところ、どちらの組織でも驚くほど同じような結果が得られました。
そこで働く人たちは、頻繁に割り込みが発生する状況にあり一度割り込みが発生すると、15~20分は割り込まれた作業にかかってしまうことがわかったのです。
開発者の時間の約20%は、そうした割り込みの作業に費やされていたのです。
そしてSlackなどのメッセージングアプリの利用が増えた現在では、割り込みはより頻繁に起こるようになっているでしょう*1。
割り込みについては、最近も研究が進んでいます。
第3章に登場したクリス・パーニン教授も、86人のプログラマーの1万回のプログラミングセッションを記録して、割り込みの研究を行っています。
この研究で、パーニン教授はバン・ゾーリンゲン教授の割り込みがよくあることだという発見が正しいことを確認しました。
パーニン教授の研究によると*2、平均的なプログラマーが2時間のコーディングセッション中に一度も邪魔されないのは、1日に1回だけだったとのことです。
開発者自身も、そうした割り込みによる作業の中断が問題であることに同意しています。
マイクロソフトの調査によると、62%の開発者が割り込みによる中断から元の作業へ復帰する手間が、深刻な問題だと考えていることが明らかになっています*3
11.2.2 割り込みが発生すると、その後どうなるのか p217
パーニンは、割り込みが発生して作業が中断してしまうと、その後どうなるのかを調べ、当然のことながら、割り込みによる中断は生産性を著しく低下させることを突き止めました。
作業が中断された後、コードの編集を開始できるまでには約25分かかることがわかったのです。
メソッドの編集中に中断された場合、プログラマーが1分以内に作業を再開できたケースは、調査した全体のうちのたった10%に過ぎませんでした。
コードの編集作業に戻るために、人々はどんなことをしているのでしょうか。
パーニンの実験の結果から、ワーキングメモリは、プログラマーが作業しているコードに関する重要な情報を中断によって失っていることがわかります。
この研究では、プログラマーは、コードに関するコンテクストを再構築するために、意識的な努力を必要することがわかっています。
また、被験者たちは後で戻ってくるために、目印を残すこともよく行いました。
たとえば、適当な文字を挿入してコンパイルエラーが起こるようにしておくといったことです。
パーニンは、このことをロードブロックリマインダと呼んでいます。
これは、作業の途中で続きをやるのを忘れてしまわないように、何をやっていたのかを確実に思い出せるようにしておくことを意味します。
また、最後の手段として、現在のソースコードとmasterブランチの間のdiffをとる被験者もいましたが、その方法は実際の変更場所を知るのが少し面倒かもしれません。
11.2.5 マルチタスクに関するいくつかの考察 p223
ここまで割り込みについて説明してきましたが、その中で、みなさんはマルチタスクという概念を思い出したかもしれません。
割り込みによる中断は、本当に悪いことなのでしょうか。
私たちの脳は、マルチコアプロセッサのように同時に複数のことを実行できないのでしょうか。
・マルチタスクと自動化
残念なことに、人は認知的負荷の高い作業をマルチタスクでこなすことはできません。
それには、非常に多くの証拠があります。
とはいっても、それだけでは納得しないかもしれません。
あなたは、もしかしたら音楽を聴きながら本書を読んでいるかもしれませんし、ランニングや編み物をしながら本書をオーディオブックで聴いているかもしれません。
それなのに、どうして人は同時に2つのことができないと言い切れるのでしょうか。
情報を記憶する際の「認知的」「連合的」「自律的」という3つの段階を思い出せば、その理由が理解できるはずです。
つまり、自律的段階に達していない2つ以上のタスクは、同時にこなすことができないのです。
母国語で文章を読むこと自体は、意識的に学んだりする必要のないことなので、編み物などの他の自律的段階にある作業を行いながらでも行うことができます。
しかし、これまでに知らなかった情報がたくさん含まれている難しい内容を説明している文章を読んでいるときには、集中力を高めるために手を止めたり音楽を小さくしたいと考えるのではないでしょうか。
つまり、あなたの脳が「今はマルチタスクは無理です」といっているのです。
車を駐車する際に、ラジオの音を小さくするのも、これと同じ理由です。
この説明を聞いてもまだ、自分の脳が信じられないのであれば、マルチタスクが思ったほどうまく機能しないことを示す科学的知見を見てみましょう。
・マルチタスクに関する研究
現在、マサチューセッツ総合病院医療専門職研究所の定量的手法の教授であるアニー・ベス・フォックス(Annie Beth Fox)は、2009年にインスタントメッセンジャーを使いながら文章を読んでいた学生と、文章だけに集中していた学生の比較を行いました※12どちらのグループも同じように文章を理解はしていましたが、インスタントメッセンジャーを使っていたグループは、文章を読むのにも、その後にそれに関する質問に答える際にも、約50%多くの時間を必要としました。
先に発言を紹介した、オランダの心理学者ポール・キルシュナーは、2010年に約200人の学生を対象に、Facebookの使い方に関する調査を行いました。
その結果、Facebookのヘビーユーザーの学生も、そうでない学生も、同じように勉強をしていましたが、ヘビーユーザーの成績平均は著しく低くなっていました。
特に、メッセージを受信したら即座に返信するタイプの学生には、その傾向が強く見られました。
ただし、興味深いのは、マルチタスクをする人自身は、それが非常に生産的な行為だと感じていることが多いという点です*13。
学生があるタスクを実行しながら、メッセンジャーを使ってパートナーとなる被験者とコミュニケーションをとるという実験では、学生自身は自分のパフォーマンスに満足していたものの、パートナーとなる被験者は、パフォーマンスについて遥かに低い評価を下していました*14。
このことは、Slackでチャットしながらプログラミングをすることは、仕事をきちんと終わらせる上での最適な方法ではない可能性を示していません。
型システムはエラーを防ぐ p228
型システムがエラーを防ぐというのは、本当なのでしょうか。
ドイツの研究者シュテファン・ハーネンベルグ(Stefan Hanenberg)氏は、JavaとGroovyをさまざまな角度から比較した実験を行い、型システムはプログラマーがより早くエラーを発見して修正するのに役立つことを実証しました。
ハーネンベルグ氏の実験では、コンパイラがエラーを指摘した場所は、動的型付けの言語で書かれたコードにおいて、実行時にクラッシュする場所と同じであることが多かったのです。
そしてもちろん、コードを実行するほうがコンパイルよりも時間がかかるので、実行時のエラーに頼ると開発速度は遅くなってしまいます。
ハーネンベルグ氏は、動的型付けの言語で書かれたコードでもエラーを減らすために、IDEのサポートを活用したりドキュメントを充実させたりとさまざまな方法を試しましたが、それでもなお、プログラマーがバグを発見するのにかかる時間やその精度は、静的型付けの言語のほうが動的型付けの言語よりも優れていました。
13.1 オンボーディングプロセスにおける問題点 p244
シニアな開発者になると、プロジェクトチームやオープンソースプロジェクトへの新人のオンボーディングを手伝わなければならない立場になった経験があるでしょう。
多くの開発者は、教えたり指導したりといった訓練を受けているとは限らないので、オンボーディングは双方にとってストレスの溜まるものになる場合も少なくありません。
本章では、オンボーディングプロセスで、新人の脳の中でどういうことが起こっているのか、そして、それをうまく管理するにはどうすればよいのかを掘り下げていきます。
筆者がこれまで目撃したオンボーディングプロセスは、多かれ少なかれ、次のような感じでした。
・シニアな開発者は、新人にたくさんの新しい情報を一度に投げつけてしまいます。
その結果、情報量が多過ぎて処理しきれず、新人の脳に高い認知的負荷がかかります。
シニア開発者は、たとえば、コードが対象とするビジネス領域、開発ワークフロー、コードベースの内容などを一気に紹介してしまいがちです。
・紹介が終わると、シニア開発者は、新人に質問をしたりタスクを与えたりします。
シニア開発者は、小さなバグを治したり、小さな機能開発をするなどのタスクを与えますが、シニア開発者本人は、それを非常に簡単なタスクだと考えがちです。
・ところが、新人は、そのビジネス領域やプログラミング言語における関連するチャンクの不足、あるいは関連するスキルが完全に自動化されていないがゆえに、高い認知的負荷を引き起こし、うまくオンボーディングできない状態になってしまいます。
このシニア開発者と新人のやりとりは、一体何が問題だったのでしょうか。
この場合の最も大きな問題は、シニア開発者が新人に同時に多くを学ぶことを求めていることです。
そのために、新人のワーキングメモリの容量に過剰な負荷がかかってしまい、うまくオンボーディングができなくなってしまっています。
ここで、本書でこれまで学んだ重要な概念について思い出してみましょう。
第3章では、「認知的負荷」、つまり与えられた問題に対して、脳がどのようにがんばるのかということを取り上げました。
認知的負荷が大き過ぎると、効率的な思考が阻害されるということにも触れました。
このことから、新人のワーキングメモリが過負荷に陥ると、新しいコードベースの理解が効果的に進まず、新しい情報も記憶できにくくなってしまうことがわかります。
それが原因となって、双方にフラストレーションが溜まり、間違った思い込みが発生してしまうケースを、筆者は何度も目にしてきました。
チームリーダーは新人のことをあまり頭がよくないと感じ、新人はこのプロジェクトはとても難しいと感じてしまうかもしれません。
これは、これから一緒に働いていく上で、あまりよいスタート地点とはいえないでしょう。
シニアになればなるほど、新人への説明やトレーニングを効果的に行えなくなる理由の1つに「専門知識の呪い(curse of expertise)」があります。
これは、何かのスキルや知識を十分に習得してしまうと、その習得がいかに大変だったのかを忘れてしまうということを意味します。
この呪いにかかると、その習得がそんなに難しいものに感じられず、新人が同時に処理できる新しい物事の数を過大評価してしまうようになってしまうのです。
ここ数カ月の間に、どこかで何かについて「それほど難しくない」とか「実はとても簡単だ」「些細なことだ」などと、誰かに向かっていったことがあるかもしれません。
しかし実際には、それらの多くは、習得するまでに実はかなりの時間を要したものだったのではないでしょうか。
「わあ、簡単だなあ!」と思ったら、専門知識の呪いに陥っている瞬間かもしれないのです。
オンボーディングを失敗しない秘訣は、あなたが簡単だと思うことであっても、学ぶ側にとっては、必ずしも簡単ではないかもしれないと認識することです。
13.2.2 概念を具体的に見るか抽象的に見るかの違い p250
ここまで、初級プログラマーと熟達のプログラマーでは、行動や思考が異なるということを学んできました。
研究によれば、熟達者は何らかの概念について語る際にも、非常に一般的で抽象的な用語を使い、初心者とは異なる方法を用いることがわかっています。
Pythonの可変長引数について語る際にも、熟達者なら「受け取る引数の数が可変の関数」といった説明をするかもしれません。
しかし、そうした場合、すべての引数にアクセスするにはどうしたらよいのか、各引数の名前はどうなるのか、引数の数に制限はあるのかといったさまざまな具体的な疑問は不明なままになってしまいます。
一方、初心者の場合、抽象的な形式と具体的な形式の両方の説明から学ぶことができます。
理論的には、初心者の理解のプロセスは、オーストラリアの科学者カール・マトン(Karl Maton)が定義した概念である、意味波(semantic wave)に従うとされています*2。
意味波に従うと、まず初心者は一般的な概念を理解する必要があります。
一般的な概念とは、それが何のために使われるのか、なぜそれを知る必要があるのかというようなことです。
たとえば、可変長引数を持つ関数が便利なのは、関数の中で必要なだけの引数を自由に使うことができるからです。
初心者が一般的な概念として、それが何をするものなのかを理解した後は、意味波のカーブに沿って抽象度の度合いは低いほうへと進んでいきます。
これはアンパッキング(unpacking)として知られるプロセスです。
このようにして、その概念の詳細を学ぶ準備が整っていきます。
例としては、Pythonでは「*」が可変長引数を示すのに使われること、Pythonは複数の引数をリストとして実装しているので、実際には複数の引数が変数として存在するのではなく、関数のすべての引数を要素として含むリスト変数を受け取れることなどが挙げられるでしょう。
最後に、初心者の理解は具体的な内容から離れて抽象的なレベルまで戻り、概念が一般的にどのように機能するかを知って、しっかり腹落ちする必要があります。
これをリパッキング(repacking)と呼びます。
リパッキングには、例えば「C++は可変長引数関数をサポートしているが、Erlangはサポートしていない」というように、すでに持っていた知識とのネットワークを長期記憶に構築する作業も含まれます。
初心者が熟達者から新しい概念を学ぶ際に陥りやすいアンチパターンは、図13.3に示すような3つがあります。
最初のアンチパターンは、ハイフラットライン(high flatlining)と呼ば初心者が抽象的な概念しか理解していない、つまり熟達者がそこしか教えていない場合に起こります。
Pythonの初心者がPythonに可変長引数関数があることや、なぜそれが便利であるかを学んでいたとしても、実際の構文を知らなければ、まだまだ学ぶことはたくさんある状態だといえるでしょう。
2つ目のアンチパターンはローフラットライン(low flatlining)、つまりハイフラットラインの逆です。
熟達者の中には何かを説明する際に、その概念は一体どういったもので、なぜそれが便利なのかということを説明せずに、初心者に詳細な情報を大量に与えてしまう人がいます。
そういう人は、いきなり「可変長引数は引数の定義を『*』で始めれば定義できて、すべての引数をリストで受け取れます」という説明をしてしまいますが、いつ可変長引数を使うかわからないのに、そういったことを初心者に伝えてもあまり意味をなさないでしょう。
3つ目、最後のアンチパターンは、抽象的な考え方から始まり、意味波に従って具体的なものへと降りていくもので、そこまでは問題はありません。
しかし、このアンチパターンでは、具体的な詳細を理解した後、熟達者は初心者に意味を再度抽象化させることを忘れてしまった場合です。
つまり、熟達者が初心者にその概念についての「Why」(なぜ重要なのか)と「How」(どう扱うのか)は教えているものの、リパッキング、つまり、その新しい知識を長期記憶に統合するチャンスを与えなかったときに起こります。
リパッキングは、その新しい概念と、過去に知っている知識の間の共通点を明確に質問として尋ねることなどで後押しすることができます。
これを下降専用エレベーター(downward escalators)と呼びます。