より良くプログラムを書くためには
目次
はじめに
プログラマー脳という書籍を社内でおすすめされたので読んでみた。
自分がこれまで言語化していなかった事柄が認知科学に基づきうまく説明されており示唆に富むものだったので自分なりに拡張し、その内容をまとめた。
プログラミングにおける混乱
プログラムを読み書きする行為をプログラミングというが、この行為において初学者やプロジェクト新規参画者を混乱させる要因は以下のものがある。
- 知識不足: 馴染みのないプログラミング言語で書かれたコードは、そもそも読むことが困難である
- 情報不足: 関数名などからその処理内容を推論することが困難である
- 処理能力の不足: コード中の処理が把握しづらいなどの理由で脳内のリソースが奪われ、読むことが困難になる
要因の名称はプログラマー脳から拝借したもの(だが、名称と説明に若干の違和感もある)。
知識不足は、あるプログラミング言語の理解が十分でないため読むことが困難であることを示している。
情報不足は、要件やコード全体を把握できていない場合に起こりうる困難さを示している。
処理能力の不足は、ワーキングメモリの消費が激しいことによる困難さを示している。
なぜ混乱が起きるのか
まず、脳のメカニズムとして短期記憶と長期記憶がある。
短期記憶とは、プログラミング中に得ている事柄をに一時的に留めたもの。容量は小さい。
長期記憶とは、いわゆる経験値であり何も見なくとも活用できる記憶である。容量は大きい。
熟練者は長期記憶を活用し推測でき、短期記憶がいっぱいにならないことに加え、
短期記憶と長期記憶を結びつける(これをチャンクという)ことで効率的に考えることができる。
一方、初学者の場合は長期記憶をほとんど活用できないため、短期記憶やワーキングメモリの消費が激しく混乱しやすい。
なお、プログラマー脳では短期記憶とワーキングメモリは別概念として定義されており、本文でもこれを踏襲する。
短期記憶は情報を保持するのに対して、ワーキングメモリはコードの流れや計算を処理する領域。
認知的負荷が高いコードへの対処法
ワーキングメモリを著しく消費するコードには、以下の対処法がある。
- リファクタリング: そもそも読みづらいコードになっている場合は、読みやすいように改修する
- リファクタリングの具体的な方法については、マーティン・ファウラー著「リファクタリング」を参照されたい
- 状態遷移図: 状態遷移図または状態遷移表を用いてワーキングメモリの負荷を軽減する
変数の役割フレームワーク
良い変数名をつけることはリファクタリングの1つであるが、このためのプラクティスとして「変数の役割フレームワーク」がある。
これは変数の役割を11個に分類したものであり、
- 固定値: 定数とも
- ステッパー: 再帰やループ処理のために用いる変数。i,j,kが用いられる
- フラグ: 条件判断に用いる変数
- テンポラリ: 一時的な値を保持する変数。tmpなどが用いられる
のように役割ごとに分けられる。
詳細はプログラマー脳を読んでもらいたいが、要約すると「異なる変数名には異なる役割を持たせる」ということ。
命名ルールを定める
プログラマー脳では「2人の開発者が同じ名前を選択する確率は非常に低い」とあり、このことから命名ルールを定めることは有用である。
命名ルールは変数、関数、その他言語機能を対象に定める。
ルールはチーム毎に定めるべきだと思うが、汎用的なルールの例は以下のようなものがある。
- 名称は一貫性を持たせる。例えば表記法を統一したり、make,create,複数形の戻り値は複数の値を返すなど
- 辞書に載っている単語を用いる
- 省略形は利用しない。ただしi,j,kなどの慣習的に知られたものは除く
- 先頭と末尾にアンダースコアを用いない
表記法(キャメルケース、スネークケース)や大文字小文字の扱いについてはプログラミング言語の影響を受ける場合もあるため割愛。
その他、命名ルールの具体例を示した表や雛形を用意しておくと良い。
長期記憶と短期記憶をうまく活用するための方法
長期記憶と短期記憶をうまく活用するための方法もある。
- デザインパターン: デザインパターンに当てはめることでチャンク化し、脳の負荷を軽減する
- ビーコンとコメント: ビーコンとはキーワードのようなもので、例えば木構造を表す用語である「木(tree)」「根(root)」を関数名や変数名に含めることでチャンク化を容易にする。これらをコメントとして残しても良い
- ドキュメント: コードやコメントとして残すのが適さないものは、ドキュメントとしてまとめる
読む練習の大切さを認識する
プログラマーは書く練習はするが、読む練習はあまりしない場合がある。
プログラマーの作業の6割は読むことにあるため、読む練習はとても重要なプラクティスと言える。
具体的には
- 全体把握: コード全体を把握する。関数の詳細な処理などの細かいところは後回し。この時、経験と紐付け過去の知識を活性化させる
- 重要箇所の抜き出し: 処理野中で重要だと判断できる箇所を抜き出す。なお重要かどうかはコンテキストに依存する
- 意味の推論: 変数名や関数名から処理内容を推測する。この時、名称がビジネス要件の意味を持つのか、プログラムの概念としての意味なのかを整理する
- 要約: コード全体の要約を行う
プログラムを書く者は特に意味の推論において、ビジネスロジックを記述する際はビジネス要件に合わせた用語を用いて命名するべきだ。
ビジネス要件に合わせた用語をステークホルダー内で定めたものをユビキタス言語といい、これについてはエリック・エヴァンス著「エリック・エヴァンスのドメイン駆動設計: ソフトウェアの核心にある複雑さに立ち向かう」を参照されたい。
ソフトウェア設計における意思決定を上手に
コードをうまく書くためには、適切なソフトウェア設計が重要である。
ソフトウェア設計のためにはモデルを用いると良い。
モデルはプログラムの意図を伝えやすくする役割と、問題解決のための意思決定を助ける役割がある。
メンタルモデル
目の前の問題を捉えるための概念、またはそれを抽象化したものメンタルモデルという。
メンタルモデルについてはプログラマー脳を読むか、エリック・エヴァンス著「エリック・エヴァンスのドメイン駆動設計: ソフトウェアの核心にある複雑さに立ち向かう」を参照されたい。
メンタルモデルの具体的な例としてデザインパターンや木構造などのデータ構造、コードリーディングを通して得られるビジネスロジックや状態遷移図が挙げられる。
想定マシン
メンタルモデルに加えて、プログラムがコンピュータ上でどのように動作するかを説明するものとして想定マシンがある。
例としてスタックが挙げられる。
なお、想定マシンは意味論ではない(当然)。
既存の知識を活かす
ある知識を別の領域に活かすことを転移という。
近い領域の知識は活かしやすく、遠い知識は結びつけづらいという特性がある。
転移を適切に行うための要因は以下のとおり。
- 習熟度: 取り組む対象のタスクに関する知識が長期記憶に多いほど、コードの理解が早い
- 類似性: 取り組む対象のタスクに類似する知識が長期記憶に多いほど、コーディングがうまくできる
- 重要な性質に関する知識: 取り組む対象のタスクの重要なポイントと長期記憶にある知識を結びつけられると良い
プログラマー脳には上記以外にも、コンテクスト、関連性、感情があるが、すでに列挙した要因に内包されると思われるため省略している。
転移の落とし穴
既存の知識をうまく活かせることもあれば、活かせないこともある。
活かせない場合、それは誤解を生みコードの理解を阻害してしまう場合がある。
転移の落とし穴に立ち向かうには次の方法がある。
- 概念変化: 新しい言語や概念を学ぶ時は、新しいものだと認識する
- ペアプログラミング: ペアプログラミングで誤認識(負の転移)を防止する。テストやドキュメントを書くことも有効
熟練者がやるべきこと
熟練者が自らコーディングすることもあれば、初学者や新規参画者に教える立場になることもある。
教える立場にある者は次のことに注意する。
- 割り込みを発生させない: 割り込みによって開発効率が低下することが知られている
- 準備時間を確保する: プログラミングにはウォームアップという準備時間が必要であることが分かっており、割り込みが発生した場合はこの準備時間を考慮および確保する
- 抽象、具象、両者の結びつけの流れで: まずは全体像やなぜそれをやるのか(抽象)を教え、次のどうやって実現するのか(具象)を教える。最後に具象と抽象を結びつける
- オンボーディングの目的は1つに絞る: 全体像を把握する、構造体やメソッドの詳細を理解するなど、抽象、具象、または両者の結びつけのいずれかのみを目的とする。詰め込みすぎない