プログラマー脳を読んで押さえておきたいところをメモ

# はじめに
この記事は、[プログラマー脳](https://www.shuwasystem.co.jp/book/9784798068534.html)を読んで自分用にまとめたメモです。

# プログラムを読むときに起きる混乱と認知プロセス
- 知識不足
長期記憶の問題
　プログラミング言語のキーワードの意味を理解しているか？
　ドメイン知識を記憶しているか？

- 情報不足
短期記憶の問題
　プログラム内に登場する変数がどんな意味を持つか？
　プログラムを流し見しているときに使用している
　記憶できる容量はごくわずか

- 処理能力の不足
ワーキングメモリの問題
　頭の中でプログラムを動かす
　プログラムをトレースしようとしたときに、紙にメモする必要が出てる場合は、ワーキングメモリが枯渇していることを意味する

# 認知プロセスを踏まえて理解しやすいプログラムを書くには
- 馴染みのあるプログラムを読むのは簡単
長期記憶を使用でき、短期記憶をそこまで必要としない。
コードを読む上でそのコードが何をしようとしているかがわかっていると理解しやすい。

- チャンクで短期記憶で記憶できる量を増やす
コードブロックを意味のあるまとまりとして記憶する。

## チャンク化しやすいコードを書くには

- デザインパターンの利用
 デザインパターンを知っていて、それが使用されているコードを読む場合、知っていない場合より理解しやすい。

- コメント
　プログラムを大まかなまとまりとして理解できるようなコメントは有効。
　細かすぎるコメントは逆に理解しずらくなる。
 
- ビーコン
 コードを理解する際に役立つコメントよりも小さなもの。
 意味のある変数名、キーワード、制御構文など。

# プログラミングの文法を覚えるテクニック
## フラッシュカード
言語、フレームワーク、ライブラリの新しい概念に出会った際にカードを作成する。
紙でなくてもデジタルのものでも良い。
- あまり重要でないものは作成しない
- 長期記憶にとどめるには、長い期間をかけて覚える必要がある
- 月に一度フラッシュカードを使って学習する

### フラッシュカードが有効な理由
長期記憶から情報を取り出すメカニズムには2つある。
- 貯蔵強度
記憶した情報がどれだけ長期記憶に保持されているかを表す。
貯蔵強度は繰り返し学習することで強化される。

- 検索強度
特定の情報を思い出すのがどれだけ簡単かを表す。
検索強度は繰り返し思い出そうとすることで強化される。

記憶を強化するには検索強度を強化する必要がある。
検索強度を強化するには、単に情報を覚える（貯蔵強度の強化）のではなく、能動的に思い出そうとすることが重要である。
フラッシュカードはそれに打って付けである。

## 精緻化
複雑なプログラミングの概念を学習する際に、すでに知っている情報に結びつけて記憶すること。
そうすることで定着しやすくなる。
あるプログラミング言語の文法を覚える際に、別のプログラミングの類似した文法に当てはめて覚えるなど。

# ワーキングメモリ使用時の記憶補助方法
## 依存関係グラフの作成
処理の流れを理解しながら読むことができるグラフを作成する。
対象のコードを印刷、またはスクショして以下を実施する。

0. 全ての変数をまるで囲む
0. 同じ、または関連した変数を線で繋ぐ
0. 全てのメソッド、関数の呼び出しをまるで囲む
変数とは別の色を使う。  
0. メソッド、関数呼び出しをその定義場所と線で繋ぐ
一度しか呼び出されていないメソッドはインライン化の余地あり  
0. クラスのインスタンスを全て丸で囲む
変、メソッドとは別の色を使う。  
0. クラス定義とインスタンス間を線で繋ぐ
クラスの定義がコード内に存在する場合はインスタンスと線で繋ぐ。
ない場合はインスタンス同士を線で繋ぐ。

以上の作業が行われたコードは、変数等の関係性が明らかになっているため、ワーキングメモリの負荷を下げる効果がある。

## 状態遷移表の作成
計算内容の途中経過の把握に役立つ。
依存関係グラフが作成済みの場合、作成しやすい。

0. 全ての変数の一覧を作る
0. 表を作成し、全ての変数の列を作成する
0. コードの区切りとなる部分で行を追加する
ループの折り返し、複雑なif文など。
0. コードを頭の中で実行し、それぞれの行に値を書き込む
表の値は全ての変数の値を埋めていくこと。

[自動化で可視化するツール](https://pythontutor.com/)もある。

# コードリーディング時に有効な戦略

## 変数の役割フレームワーク
プログラミングの変数は11の役割で大体カバーできる。
- 固定値
その名の通り。
初期化後、値が変化しない変数。

- ステッパー
ループ処理でループごとに値が変更されていく変数。

- フラグ
何かが発生したことや何かの情報が含まれているかを表す変数。

- ウォーカー
ポインタ変数や整数のインデックス値。
ステッパーとの違いは、ループ処理を開始する前にどのように走査を行うのかが未知のケースであること。
二分技の検索インデックスなど。

- 直近の値の保持者
一連の値を順に処理する際に、最新の値を保持する変数。

- 最も重要な値の保持者
反復処理などで目的となる値を保持する変数。

- 収集者
データを集めて一つの値に集約している変数。

- コンテナ
複数の値を内包し、追加・削除が可能なデータ構造。

- フォロワー
前の値や次の値を保持する変数。

- オーガナイザー
値を並べ替えたり、異なる形式で値を保存するためだけの変数。

- テンポラリ
短期間だけ使用される変数。
変数の値の入れ替えやメソッド内でよく使用される計算結果を保持するために使用される。

### 変数の役割を決定するフローチャート
```mermaid
graph TB
  B{定数か}
  B -->|no|  D{一時的に保持されている変数か}
  B -->|yes| C[固定値]
  D -->|no|  E{何かのチェックをしているか}
  D -->|yes| F[コンテナ, オーガナイザー, テンポラリ]
  E -->|no|  G{繰り返して利用されているか}
　　　　E -->|yes| H[フラグ]
  G -->|yes| I{前の値を保持しているか}
  I -->|何かを数えているか| J[ステッパー]
  I -->|加算しているか| K[収集者]
  I -->|選んで保持しているか| L[最も重要な値の文字者]
  I -->|保持のタイミングが遅延しているか| M[フォロワー]
```

変数の役割にアイコンを割り当てておくと、コードに役割を記載する際にやりやすい。


## 文章理解の戦略を適用する
コードの読解と文章読解は認知的な類似性がある。
脳の数学的な部分ではなく、文章理解時に使用する領域をコード読解時にも使用している。
以下の文章理解時の戦略をコード読解時にも適用できる。

- 過去の知識の活性化
対象のコードの全体をスキャンし、長期記憶の関連情報と照らし合わせる。
コードリーディングする時間に加え、そのコードを理解する時間を確保する。

- 監視
自分が何を読んでいるか、それを理解できるかを把握する。
理解できない部分とそうでない部分をわかるようにしておくのもあり。
そうすることで質問するときにわからない点を明確にできる。

- 重要な行の判断
どの行がプログラムに最も影響を与えるかを判断する。
チームメンバーで行うと重要であると判断する行が異なるので学びを得ることが多い。

- 変数の名前の意味を推論する
変数の名前は2種類に分類できる。
1つはビジネス領域に関係する名前。
もう1つはプログラムの概念に関係する名前。

- 可視化
状態遷移表やコードのトレース以外では、操作テーブルの作成する方法がある。
変数、関数、クラスなどの識別子を全て書き出し、その横に関連する全ての操作内容を書き出す。

- 自問自答
コードに対して、自分で質問を作りそれに答える。
内容は省略。

- コードの要約
読んだ内容を要約する。
自然言語の文章で要約することでコードの理解がより深まる。
自分用のドキュメントとしても使用できる。


# メンタルモデルを利用する
状態遷移表などは実際に手を動かして作成するモデルである。
それに対し、メンタルモデルはワーキングメモリ内で概念を抽象化するもの。
ファイルシステムについて考えるとき、フォルダ内にファイルがまとまっている構造を思い浮かべるなどがこれにあたる。
メンタルモデルが詳細であるほどコードの理解は進む。

## 複雑なコードのメンタルモデルの作り方

0. 局所的なモデルの構築
コードの特定の箇所での状態遷移表などは、一つ一つは小さいがより大きなメンタルモデルを構築するパーツになる可能性がある。

0. コード内の関連する全てのオブジェクトと、オブジェクト間の関係を表に書き出す
紙やデジタルツールを使ってそれぞれの要素の関係を書き表す。

0. 理解しようとしているシステムに対する自問自答を行い、モデルを改良する

## あらかじめメンタルモデルを記憶しておく
長期記憶にデータ構造やデザインパターン、アーキテクチャパターンなどを記憶しておくことで、コード理解時にメンタルモデルに適用して理解の助けになることがある。
記憶の仕方は定番のフラッシュカード。
コードリーディング時にはフラッシュカードと照らし合わせるのもあり。

# 別の言語を学ぶときに既知の言語があることが有効か

良い影響もあれば悪影響もある。
既知の言語と似ている部分は、長期記憶から関連するものを利用して理解しやすくなる。
しかし、似ていることが逆に理解を妨げる要因にもなる。
その一つとして、新たに学ぶ言語の特定の挙動が、既知の言語の挙動と同じであると勘違いしてしまうことが挙げられる。
SQLからJavaScriptとなると似ている部分が少なすぎるため、その現象は起こらない。
知っている言語があるからこそ起きる思い込み(誤認識)が一番怖いねと。

## 新しい言語を学ぶときに誤認識を減らすには
- 言語の学び順が同じ人に気を付ける点を聞いてみる
- 思い込みによる誤認識がありそうな部分はテストを書いて確認する

何かにつけてつくづく思うのが、テストはやはり重要。

# 命名について
- 命名規則はプロダクトの初期段階からほとんど変化しない。
初期段階の命名規則がしっかりしていないとズルズル引きずっていくと。

また、テストの書き方もプロダクトの既存のテストが参考にされるため、変化しにくくなってしまう。
テストの書き方は気をつけなければ・・・

命名自体が認知的負荷が高いため、コーディング後のレビュー時に再確認すると良い。
コーディングと命名の両方を一度に行うのは脳にとって負荷が高い。

`camelCase`と`snake_case`は、`camelCase`のが変数名の理解をしやすくなる。
ただし、元々どちらかのケースをベースに言語を習得している場合は、そのときに使用したものの方が理解はしやすい。
変数名の雛形があると他の人が書いたコードを読む際の認知的負荷が下がる。

## 良い変数名を付ける3ステップ

0. 名前に含めるべき概念の選択
どんな情報を保持し、何に使われるかを理解できるか。
コメントで情報を追加したくなったら、その情報を変数名に加える必要がある。

0. その各概念を表す単語を決める
同義語を許容せず、特定の概念に対して使用する言葉は統一する。

0. その単語の使用して命名する
雛形は自然言語と同じ語順であるとさらに理解しやすい。

# 問題解決を効率化するには

## 問題解決のステップ

0. 問題を理解する
0. 計画を立てる
言語が標準で提供している関数が使用できるか、その関数を使用して問題を解決できるかを調べる。
その情報へのアクセスが難しい場合、計画を立てる難易度は上がる。

0. 計画を実行する
言語仕様についての理解が乏しい場合、利用する関数がどのような処理を行うか理解する必要があるため、ただ関数を利用すれば良いわけでないことがわかった場合、実行計画が簡単ではないことがわかる。

過去に類似した問題を解決した場合、類似する問題を解決するのは簡単。
なぜなら長期記憶から関連した記憶を検索できるから。

## 長期記憶の種類

- 潜在記憶
意識しなくてもできる技能に関する記憶。
自転車に乗る、タッチタイピングなど。

- 顕在記憶
記憶していることを自覚している記憶。
Javaのforループの書き方、Twitterを買収した人がイーロン・マスクであるなど。
顕在記憶はさらに分けることができる。

  - エピソード記憶
一般的に「記憶」と表現されるもの。
数年前に旅行に行った、数時間かけてバグを解消したなど。
意味記憶のように努力しなくても作られる。

  - 意味記憶
意味や概念、事実に関する記憶。
りんごを英語ではappleということ、3×5=15であるなど。
フラッシュカードで記憶したこともこれにあたる。

プログラミングでは、文法を思い出すときに意味記憶、過去の問題解決法を覚えているときはエピソード記憶、バグが起きそうな箇所に意識せずにブレークポイント置く際には潜在記憶が使われている。

より複雑な問題を解決するには、認知的負荷を下げる必要があるため、プログラミングをある程度自動化してできるようになる必要がある。
そのためには潜在記憶を強化する必要がある。

## 潜在記憶が作られる段階

- 認知的段階
目の前のタスクについて説明的に考える必要がある。

- 連合的段階
パターンに反応できるまで繰り返し作業する必要がある。

- 自律的段階
特に意識せずに作業ができる自動化された状態。
この段階に達した作業は認知的負荷が増えることがない。

スキルやタスクについてどの段階にいるかを判断する場合以下の質問が有用。
- そのタスクについてしっかり考えないと行えない。
認知的段階

- あまり考えなくてもできるが、何らかのテクニックや法則を頭の中に描いて実行している。
連合的段階

- そのタスクは別の問題を考えながらこなすことができる。
自律的段階

## 自律的段階まで高めるには
意図的な練習をする必要がある。
プログラミングでは、既存のコードに対して練習したい概念を適用したり、その概念を適用する前と適用後のコードを作り、比較するなどがある。

## ワーキングメモリに負荷がかかりすぎると長期記憶に保存できない

ワーキングメモリは短期記憶程度の情報しか保持できない。
また、ワーキングメモリは、情報を長期記憶に保持することをしており、そのときにかかる負荷を学習関連負荷という。
ワーキングメモリに負荷がかかりすぎると、この負荷に割く領域がなくなってしまうため、問題解決後にその解決方法を忘れてしまうといったことが起こる。

# プログラミングの活動と関連する認知プロセス

- 検索
関連する認知プロセス：短期記憶
コード内を調べ、特定の情報を探す作業。

- 理解
関連する認知プロセス：ワーキングメモリ
コードを読んで機能を理解する作業。

- 転写
関連する認知プロセス：長期記憶
コードを書く作業のみ。
書くコードの計画は事前に作成済みの前提。

- 増強
関連する認知プロセス：3つ全て
検索、理解、転写を組み合わせたもの。
例として新規機能追加があげられる。
コードの追加箇所の検索、理解、実装イメージをコードに落とし込む作業。

- 探索
関連する認知プロセス：3つ全て
コードの完成像が漠然としている状態でコードを書くこと。

## 割り込み対策
プログラミング中に割り込みが入ると元の作業に戻るまで20分程度かかる。
割り込み後に復帰しやすくするために以下の方法がある。

- メンタルモデルの保存
それまでの作業内容をメモ、モデルとして残してから割り込み作業に入る。

- 展望記憶の補助
未来に関係する記憶を展望記憶という。
展望記憶の補助としてTODOコメントを使用する。
ただ、TODOコメントではそのまま残り続ける傾向があるため、プラグインなどで期限付きのTODOコメントを残すと良い。

- 下位目標のラベル付け
問題を小さいステップに分割し、テキストとして記載すること。
作業前にテキストに書き出しておくことで割り込み後にそのテキストを確認して作業の再開までの時間を短縮できる。

上記の方法以外にも集中したいときにコミニュケーションツールの通知を切ることも有効。

# コードの特性とプログラミング活動との関係

- エラーの発生しやすさ
動的型付け言語は、暗黙的な型変換により静的型付け言語よりエラーが発生しやすい。
他の要因としては、コーディング規約の不徹底、曖昧な名前、ドキュメントの不足がある。

- 一貫性
命名に一貫性があるか。
言語的アンチパターンが存在するコードは認知的負荷が高い。

- 拡張性
プログラミングの構成要素がどれだけ多くの記述や長さを必要とするか。
拡張性が低い方が読みやすく、理解しやすい。

- 隠れた依存性
依存関係がどの程度までユーザーに見えるようになっているか。
隠れた依存性のあるコードに対してドキュメントを書くことで関係性がわかりやすくなる。

- 暫定性
コードの記述方法にどれだけ自由度があるか。
型制約や構文によって強制されるほど暫定性は下がる。
紙やメモなどは文法的に間違っていたとしても自分の思考通りにコードを書ける点では暫定性が高い。
暫定性が低い状態でコードディングするのは認知的負荷が高い。

- 粘性
コードがどれくらい変更するのが難しいか。
言語的特徴では、動的の方が静的より変更が容易。
コードを取り巻く環境では、コンパイル〜テスト実行に時間がかかる場合も粘性は高いと言える。

- 段階的評価
部分的な作業をチェックしたり、実行するのがどれだけ簡単か。
暫定性が高いシステムでは不完全なアイデアでもすぐに試すことができる。
段階的評価が可能なシステムでは書きかけのコードでも一部を実行することができる。
暫定性と関連しており、段階的評価がやりづらいシステムでは、暫定性も低い可能性がある。

- 役割表現力
コード中の役割をどれだけ簡単に理解できるか。
引数のない関数に()がついていることでそれが関数であるとわかるため、役割的表現を助けている。
また、シンタックスハイライトも各要素がどのような役割を果たしているかをわかりやすくしている。
言語的アンチパターンは役割表現力を低くしてしまう。

- マッピングの近接度
コードの内容が対象のビジネス領域にどれだけ近いか。
ドメイン駆動設計はマッピングの近接度を高くしようとする試み。

- ハードな心的操作
言語的な制約が多いほどハードな心的操作を要求する。
上記に関しては、エラーの減少にもつながるため、一概に悪いとは言えない。
システムがハードな心的操作を要求する場合、多くのことを記憶しておかないと使えないなどがある。
マッピングの近接度が低い場合も長期記憶に記憶に記憶する必要があるため、ハードな心的操作を要求する。

- 副次的表記
コード中に正式な仕様として決められていない余計な意味をコードに追加する可能性。
コード中のコメントや名前付きパラメータはこれに当たる。

- 抽象化
システム利用者がそのシステムに元からある抽象化と同等の抽象化を行えるか。
プログラミング言語では、関数、クラス、オブジェクトが作成できることがあげられる。

- 視認性
システムのさまざまな部分をどれだけ簡単に見ることができるか。
コードでは、コードが複数のファイルに別れており、関連性がわかりにくい場合、視認性は低い。

## プログラミング活動との関係
|特性|助ける活動|害を及ぼす活動|
|---|---|---|
|エラーの発生しやすさ| |増強
|一貫性|検索、理解|転写
|拡張性|検索|
|隠れた依存性||検索
|暫定性|探索|
|粘性||転写、増強
|段階的評価|探索|
|役割表現力|理解|
|マッピングの近接度|増強|
|ハードな心的操作||転写、増強、探索
|副次的表記|検索|
|抽象化|理解|検索
|視認性||理解

# オンボーディング時の問題点と対策
オンボーディングする側が難易度が低いと思っているタスク（小さなバグ修正）でも、新しい開発者にとっては認知的負荷の高い作業となっていることがある。
例えば、小さなバグ修正でも、新しい開発者は、少なくとも4つの活動を同時行うことを求められる。
実際に作業を行うと、機能を実装する場所や関連情報を探し、ソースコードを理解し、理解を深めるためにコードを探索し、新しい機能でコードを増強する必要がある。
この過程から、新しい開発者にとっては脳にかかる認知的負荷が高いことがわかる。
そして、ワーキングメモリのリソース不足になり、ワーキングメモリが通常行っている「長期記憶への保存」ができなくなり、知識の定着が遅れる。

## オンボーディングの改善
少なくとも各活動の1つと対応するような作業をさせる。
これまでの認知プロセスに関する知識が両者にあるとなお良い。

### 長期記憶のサポート
コード内のビジネス領域と関連する情報についての概念や知識はあらかじめドキュメントにまとめておく。
コードで使用しているライブラリ、フレームワーク、データベース、その他のツールについてもまとまっていると良い。

またコードの理解とは別に、関連するビジネス領域の概念を理解させる。
プログラミングの概念、ビジネス領域の概念をそれぞれフラッシュカードにまとめておくのは既存の開発者にとっても有益。

### 短期記憶のサポート
新しい開発者にいきなりコードを触らせてしまうと前述した通り、認知的負荷が高くなる可能性がある。
それを防ぐためにタスクを細かく分割し、一つのことだけに集中できるようにする必要がある。
- 理解だけのタスクをしてもらう
いきなり実装のタスクを行なってもらうのではなく、理解を目的としたタスクを割り当てる。
既存のクラスの概要をドキュメントに書き出すことや、特定の機能に関わるクラスを書き出してもらうなど。
ドキュメントの記載をすることで理解することに集中できる。

実装から始める場合でも、あらかじめ関連するコードを容易しておくことで、検索の負荷を減らせる。
教える側は、新しい開発者にかかる負荷をなるべく軽減し、長期記憶に記憶できるだけの余白を作ることが大事。
**教える側にこの視点がなく、認知的負荷が高いことで新しい開発者のパフォーマンスが十分に発揮できていないことを、新しい開発者の能力のせいにしてはいけない。**

### ワーキングメモリのサポート
ダイアグラムをあらかじめ作成しておくことで、新しい開発者のワーキングメモリをサポートできる可能性がある。
ただ、タイミングによっては思っていたより機能しないことがあるため（展開が早すぎたなど）、様子を見ながら展開していく。

### コードを一緒に読む
自然言語からコードリーディングに適用できる7つの戦略をオンボーディング時に一緒にやるのもあり。
内容は割愛。

# 終わりに
新しい環境でコードの理解がなかなか進まない理由が明確になった。
コードリーディング時に使えるテクニックは、頭の中の余白を作れるからワーキングメモリだけでやる時よりも理解は早まりそう。
フラッシュカードは簡単にできる割に効果が高いので積極的に取り入れていく。
自分がオンボーディングを受けた時も、短期記憶に対する情報量が多すぎて理解するのに時間がかかったなーと思ったり。