プログラマー脳
コードをよりよく読むために
コーディング中の混乱を紐解く
- 知識不足=長期記憶の問題: そもそも知らない
- 情報不足=短期記憶の問題: ソースコード上で提供されている情報が不足している
- 処理能力不足=ワーキングメモリの問題: 脳内でトレースする量が多い
コードを速読する
convention に従った命名: カウンタ変数が i
とか、swap に使うための一時変数が temp
とか
context があるかないか: チェスの盤面を記憶するとき、実践にありそうな盤面なら熟練プレイヤーはよく覚えることができ、平均的プレイヤーはそれに比べると劣る。 でたらめな盤面だとどちらも大差ない結果になる。
プログラムでの実験:
プログラミング言語の文法を素早く習得する方法
忘却曲線、他の隣接概念との関連付け、とかその辺のよくある話。
複雑なコードの読み方
認知的負荷の種類
- 課題内在的負荷: その問題自体がどのぐらい複雑化
- 課題外在的負荷: その問題の妨げとなる外部要因
- コードのスタイルとか、関数型とかの慣れないパラダイムとか。
- 認知的リファクタリング: 自分が読みやすいスタイル・パラダイムに書き下してから考える。
- 学習関連負荷: 考えたことを長期記憶に保持する際に引き起こされる認知的負荷
コードについて考える
コードの深い理解に到達する
変数の役割
- 固定値: 所謂 constants。
public static final
な定数。円周率とか。 - ステッパー: loop のときの
i
とか。 - フラグ: 何かが発生したことを示したり、何かの情報が含まれていることを表したりする変数。
is_set
is_available
is_error
とか。 - ウォーカー: iterator とか。
- 直近の値の保持者: range-based for とかで使う変数。
for(auto ele : a)
のele
みたいな。 - 最も重要な値の保持者: 結果を保持する変数。最大値、最小値、条件を満たす最初の値、などなど。
- 収集者: 合計値などを格納する変数。
- コンテナ: リスト、配列、スタック、木などのデータ構造を保持する変数。
- フォロワー: 一部のアルゴリズムが必要とする、直前の値などを保持する変数。next dp とかで使う
dp
とかndp
とかはこれ。 - オーガナイザー: あるデータを別の形式に一時的に変換して保持する変数。文字列をいったん文字配列にしたい、とか。
-
テンポラリ:
temp
と名付けられるような使い捨ての変数。swap とかで使う。 - 「文章の理解」と「計画の理解」: 何がどう書かれているかという構文理解とコード作者が何を考えてそうしたかという計画理解の 2 面からコードを理解する。
プログラミングに関する問題をよりうまく解決するには
長期記憶においてソースコードのメンタルモデルの形成を行う
- ソースコードそのものに関する知識というよりかは、ソースコードの設計に関する知識を長期記憶として獲得する。
- データ構造: グラフ、リストなど
- デザインパターン: GoF など
- アーキテクチャパターン: MVC など
- ダイアグラム: シーケンス図など
- モデリングツール: UML 図など
想定マシン: 変数に「格納する」というが、変数という箱のようなものが実体として存在するわけではない。が、比喩として優れているからこのように表現する。 時間が「流れる」という表現と同じ。このように既知のものに例えて理解すると長期記憶として自然に獲得することができる。
誤認識:思考に潜むバグ
既知の事実を他のことを理解する際に使ってもいいけど、それが本当に本質的に一致しているのか、それともたまたまそのときは偶然似ているようなものに見えただけなのか、には注意が必要。
比喩で理解するのはいいけど、いつまでも比喩レベルの理解じゃ困りますよ、といういつものやつ。
よりよいコードを書くために
よりよい命名を行う方法
- 名前に含めるべき概念を選択する
- それらの各概念を表す単語を選ぶ
- それらの単語を使って命名を行う
汚いコードとそれによる認知的負荷を避けるための2つのフレームワーク
- 構造的アンチパターン: 「リファクタリング」で語られる code smell の話
- 概念的アンチパターン: 命名が悪いというやつ
- 命名よりも多くのことをやる・ものを持つ
- 命名未満のことしかやらない・ものしか持っていない
- 命名と全く異なることをやっている・ものを持っている
複雑な問題をより上手に解決するために
調べなければわからないことを減らす。反射でできることを増やす。
ショートカットとかをうまく利用する。
他人のソースコードとそのコードの目的を学ぶ。
などなど。
コーディングにおける共同作業
コードを書くという行為
割り込みからの復帰を助ける行為
- メンタルモデルを保存する
- UML に設計を起こしたり、ソースコード内に考えていたことをメモしたり
- 展望記憶
- やることを忘れないように TODO を書く
- 下位目標のラベル付け
- 何をやるべきかをリスト化してメモしておく
フローライトやチャットツールのステータス設定でそもそも割り込みが発生しないようにする
より大きなシステムの設計と改善
CDCB: コードベースの認知特性
- エラーの発生しやすさ: 型安全か、堅牢性が高いか、など
- 一貫性: 命名やスタイル
- 拡散性: 同じものを実装する際に、どれだけ長いコードを書かなければならないか、という指標
- 隠れた依存関係:
- 暫定性: どれだけ試行錯誤をしやすい環境か
- 粘性: どれだけコードを変更しやすいか。たとえば、動的型付けの方が静的型付けより変更しやすい(壊れやすさはまた別)。コンパイルやテストに時間がかかるような場合も粘性が高い。
- 段階的評価: デバッグしながら色々ソースコードを書き換えてみて動作を確認することができるか、みたいな指標。暫定性と近い概念。
- 役割表現力: 括弧がついてたらそれは関数呼び出しである、とか、syntax highlight によってコードの構成要素の種類が一目でわかる、とか。
- マッピングの近接度: ソースコードがビジネス領域とどれだけ近い言語で書かれているか、みたいな話。プログラムを DSL のように扱ってドメイン知識を落とし込むといいよね、という話。
- ハードな心的操作: プログラムを読み書きするのがプログラマにとってどれだけ負荷がかかるものか、という指標。低ければいいというわけではなく、例えば堅牢な型システムは負荷は高いがミスを防いでくれる。
- 副次的表記: ソースコードにつけられたコメントなど。プログラムの動作自体には不要だが、プログラムの理解を助けるもの。
- 抽象化:
- 視認性: ぱっと見でデータがわかりやすいかどうか、とか。例えば JSON とかで indent によって見やすく整えられているか、とか。
新しい開発者のオンボーディング
意味波 (semantic wave): 概念について抽象的で一般的なレベルの理解をする -> 具体的にはどのようにすればいいのかを学ぶ -> 異なる文脈で応用するために抽象化する
関連するアンチパターン
- high flatlining: 抽象的な概念しか学ばず、具体的にどうしたらよいかがわからない状態。
- low flatlining: 具体的な手法しか学ばず、その概念は一体どういったもので、なぜそれが必要で、どういった場合に使うべきなのか、ということがわからない状態。
- downwards escalator: 抽象的概念を学び、具体的な手法も学んだが、その後抽象化をしておらず応用が利かない状態。
オンボーディングの際に行う活動
- 探索: コードベースを見渡して、その全体像を把握する
- 検索: 特定のインターフェースを実装しているクラスを探す
- 転写: 新人に作業の具体的な手順について明確な計画を与える
- 理解: 特定のメソッドを自然言語で要約するなど、コードの内容を理解する
- 増強: 既存クラスへの機能追加(計画の作成も含む)
これらを並行してやってもらうのではなく、一つずつ指示して分解して実施してもらう。