前書き・はじめに・本書について・本書の読み方
-
本書の前書きでは島本和彦「アオイホノオ」(小学館)に登場する「面白いと、読みやすいは、違うんだよ?」というセリフを引き合いに出し、読みやすいコードは面白いコードである必要はないこと、しかしながら、読みやすいコードについて解説する本著は面白い本であることを押さえられている。わざわざ面白い本であることを確認した上で始まることに期待させられる。
-
優れたコードとは見た瞬間に何をしているかが伝わってくる。そういうコードは使うのが楽しい。自分のコードもそうあるべきである。この本の目的は読者のコードをよくすることのみである。アーキテクチャやデザインパターンの話はしないと断っている。
-
本書の目的は読みやすいコードを書くこと。中心となる考えは、コードは理解しやすくなければいけないという考え。これはつまり、そのコードを読んで理解するための時間を最短にするということ。
-
気楽に楽しく、1〜2週間で読むことを推奨している。章立ては「難易度」順になっているらしいので読みやすそうである。
第1章 理解しやすいコード
- コードを書く上でいちばん大切な原則とは 鍵となる考え「コードは理解しやすくなければいけない。」 ということ。
-
「優れた」コードって何?
- コードの比較を行っている。
- 「簡潔」なコード、つまり、記述量が少ないコードと、「安心」なコード、つまり効率的なコードはどちらが大切なことだろう?
-
読みやすさの基本定理
- 筆者が辿りついた読みやすさの基準、つまり読みやすさの基本定理は、 鍵となる考え「コードは他の人が最短時間で理解できるように書かなければいけない」 ということ。
- コードを理解するというのは、変更を加えたり、バグを見つけたりできるという意味。
- 他の人や将来の自分が理解できるコードを書くようにしなければならない。
-
小さなことは絶対にいいこと?
- コードは短ければいいってもんじゃない!
- 行を増やしたり、コメントを添えることで理解しやすいこともある。
- 理解するまでにかかる時間を短くする方が大切だ。
-
「理解するまでにかかる時間」は競合する?
- コードの効率化や設計のうまさ、テストのしやすさ等とは競合しない。
- 理解しやすいコードは優れた設計やテストのしやすさにつながることが多い。
- このコードは理解しやすいだろうか?と自問自答することが大切。
-
でもやるんだよ
- 誰だかわからない誰かにとって、そのコードが理解しやすいかをどうかを考えるのは大変。
- でもやることによって、優秀なプログラマになれるはず。
第1部 表面上の改善
- 読みやすさへの第一歩は「表面上の改善」
- コードのすべての行に影響する。
- 適切な命名、優れたコメント、綺麗な空白の使い方など
2章 名前に情報を詰め込む
- 鍵となる考え「名前に情報を詰め込む」
- tmp, size, get などの名前は問題なさそうであっても、情報が含まれていない。
- 明確な単語を選ぶ
- 汎用的な名前を避ける。
- 抽象的な名前よりも具体的な名前を使う。
- 接尾辞や接頭語を使って情報を追加する。
- 名前の長さを決める
- 名前のフォーマットで情報を伝える。
-
明確な単語を選ぶ
- 「名前に情報を詰め込む」には、明確な単語を選ばなければならない。「空虚な」単語は避ける。
- Get -> Fetch, Download など
- Size -> Height, NumNodes, MemoryBytes など
- Stop -> Kill, Resume, Pause など
- 類語辞典を使うとよい。
- send -> deliver, dispatch, route, announce, distribute など
- find -> search, locate, extract, recover
- start -> launch, create, begin, open
- make -> create, set up, build, generate, compose, add, new
- 鍵となる考え「気取った言い回しよりも明確で正確なほうがいい」
- 文字列を分割するメソッドに explode()という命名は独特すぎる。split()の方が自然。
-
tmp や retval などの汎用的な名前を避ける。
- tmp, retval, foo は「空虚」な名前
- エンティティの値や目的を表した名前を選ぼう。
- v の2乗ならば、sum_squares など。バグも見つけやすい。
- アドバイス「retval という名前には情報がない。変数の値を表すような名前を使おう。
- 正真正銘の tmp であり、それでしかないものであるならば、tmp という名前でよい。
- アドバイス「tmp という名前は、生存期間が短くて、一時的な保管が最も大切な変数にだけ使おう。」
- i, j, k, iter などが複数出現するのも解りにくい。club_i, member_i, users_i などがよい。バグも見つけやすい。
- アドバイス「tmp, it, retval のような汎用的な名前を使うときは、それ相応の理由を用意しよう。」
-
抽象的な名前よりも具体的な名前を使う。
- メソッドの動作をそのまま表した命名。
- 任意の TCP/IP ポートをサーバがリッスンできるかを確認するメソッド: ServerCanStart() -> CanListenOnPort()
- DISALLOW_EVIL_CONSTRUCTORS() -> DISALLOW_COPY_AND_ASSIGN(): 印象や感覚によるものではなく、正確な動作を
- --run/locally -> --extra_logging: 使用条件ではなく、動作を
-
名前に情報を追加する
- 名前は短いコメント
- 絶対に知らせないといけない大切な情報は変数名に追加する。
- 16 新数の ID: id -> hex_id
- delay -> delay_ms
- size -> size_mb
- limit -> max_kbps
- angle -> degrees_cw
- 危険や注意を喚起する情報も命名に追加する
- 変数の意味を間違えてしまったときにバグになりそうなところにだけ使うことが大切。
- untrustedUrl, unsafeMessageBody など
- 暗号化前の password -> plaintext_password
- エスケープ前の comment -> unescaped_comment
- UTF-8 に変換した html -> html_utf8
- URL エンコードした data -> data_urlenc
-
名前の長さを決める
- 「長い名前を避ける」という暗黙の了解があるが、長い名前とはどれくらいなのか?
- 変数の名前はそのスコープの範囲の広さに比例するはず。
- 「入力しにくいから長い名前を避ける」というのは間違い。
- 新しいチームメイトが理解できるなら省略形や頭文字もよい。eval, doc, str はよい。BEManager はよくない。
- 不要な単語は捨てて良い。削除しても情報が損なわれないこともある。
- ConvertToString() -> ToString()
- DoServeLoop() -> ServeLoop()
-
名前のフォーマットで情報を伝える
- アンダースコア、ダッシュ、大文字を使って名前に情報を詰め込むこともできる。
- クラス名はキャメルケース: CamelCase
- 変数名は小文字のアンダースコア: lower_snake_case
- その他にも定数、クラスのメンバ変数、コンストラクタなどへの命名規則がプロジェクトや言語によって決まっていることがある。
-
まとめ
- 明確な単語を選ぶ: Get -> Fetch, Download
- 汎用的な名前を避ける: tmp, size, get
- 具体的な名前を使って、物事を詳細に説明する: ServerCanStart() -> CanListenOnPort()
- 変数名に大切な情報を追加する: ミリ秒を表す -> _ms, そのエスケープが必要 -> raw_xx
- スコープの大きな変数には長い名前をつける
- 大文字やアンダースコアなどに意味を含める
3章 誤解されない名前
- 鍵となる考え「名前が他の意味とまちがえられることはないだろうか?と何度も自問自答する。」
-
filter() は選択するのか、除外するのか曖昧。
- 選択する -> select(), 除外する -> exclude() が適切。
-
Clip(text, length) は最後から length 文字を切り取るのか、最大 length 文字まで切り詰めるのか曖昧。
- 最後から length 文字を切り取る -> removeLastChars(text, length)
- 最大 length 文字まで切り詰める -> truncate(text, max_length)
-
限界値を含めるときは min と max を使う
- CART_TOO_BIG_LIMIT は未満なのか以下なのか曖昧
- アドバイス「限界値を明確にするには、名前の前に maxや minをつけよう。
- MAX_ITEMS_IN_CART ならば明確
-
範囲を指定するときは first と last を使う
- print_integer_range(start=2, stop=4) の stop=4 は曖昧。
- start -> first, stop -> last の方が範囲を示すには適切。
-
包含(ほうがん)/排他的範囲には begin と end を使う
- end とは本来、包含/排他的 が曖昧な言葉だが、プログラミングでは排他的範囲を表すものとして使われている。
- begin と end の対はイディオム(慣用的な表現)になっている。
-
ブール値の名前
- read_password = true は、「これから読み取る」のか、「すでに読み取った」のか曖昧。
- need_password, user_is_authenticated などが適切。
- ブール値には is, has, can, should などをつけてブール値であることがわかりやすくする。
- ブール値の命名を否定形にするのは避ける。
-
ユーザの期待に合わせる
- ユーザが先入観をもっていることを想定する。
- get()はメンバの値を返すだけだろうと思われてしまう。: getMean() -> computeMean()
- list.size() はリストの要素数を返すだけと思われてしまう。: list.size() -> list.countSize()
-
複数の名前を検討する
- 設定の継承元である実験の ID: template, reuse, copy -> copy_experiment_id, inherit_from_experiment_id
-
まとめ
- 最善の名前とは、誤解されない名前
- 上下の限界値を決めるとき: max* や min*
- 包含的範囲: first と last
- 包含/排他的範囲: begin と end
- ブール値: is, has, can, should
- get(), size() などはユーザから軽量なメソッドであると期待されてしまう。
4 章 美しさ
- 優れたソースコードは「目に優しい」ものでなければいけない。
- 読み手が慣れているパターンと一貫性のあるレイアウトを使う。
- 似ているコードは似ているように見せる。
- 関連するコードをまとめてブロックにする。
-
なぜ美しさが大切なのか?
- 見た目が美しいコードの方が使いやすいのは明らか
- 読み慣れた構成を使うことで、コードの理解が容易になる。
-
一貫性のある簡潔な改行位置
- 似ているコードは似ているように見せる
- 同じコメントが複数回繰り返されるならば、そのコメントは括って外に出す。
-
メソッドを使った整列
- 長くて不要な改行が入りシルエットや一貫性が損なわれている場合は、ヘルパーメソッドを定義して見栄えをシンプルにする。
- これにより、コードの簡潔化、コードの可読性の向上、コード追加の簡易さの向上などの効果も得られる。
-
縦の線をまっすぐにする。
- 似た構成の行が複数行に渡る場合、スペースを用いることにより共通部分を縦に揃えることで、タイプミスを見つけやすくなったり、構造の理解がしやすくなったりする。
- 似ているコードは似ているように見せる
-
一貫性と意味のある並び
- コードの並び順を HTML 上の対応する要素と同じにする。
- 最重要なものから重要度順に並べる。
- アルファベット順に並べる。
-
宣言をブロックにまとめる
- クラス内の定義を羅列するのではなく、メソッドの種類に応じてブロックに分けるようにすることで見やすくなる。
-
コードを「段階」に分割する
- 一つのメソッドの中身でも、コードを処理の段階ごとに分割することで、コードの理解が容易になる。
-
個人的な好みと一貫性
- 鍵となる考え「一貫性のあるスタイルは正しいスタイルよりも大切だ」
- 時には間違ったスタイルを使っているプロジェクトに出会うこともあるが、そこではプロジェクトの規約に従い、一貫性を保つことが大切。
-
まとめ
- 複数のコードブロックで同じようなことをしていたら、シルエットも同じようなものにする。
- コードの「列」を整列すれば、概要が把握しやすくなる。
- 要素の並び順は意味のあるものにし、全体で一貫性を持たせる。
- 空行を使って大きなブロックを論理的な「段階」に分ける。
5章 コメントすべきことを知る
- 鍵となる考え「コメントの目的は、書き手の意図を読みてに知らせることである。」
- コメントすべきでないことを知るべし。
- コードを書いているときの自分の考えを記録する。
- 読みての立場になって何が必要になるかを考える。
-
コメントするべきでは「ない」こと
- コメントには価値を持たせないと、時間とスペースの無駄でしかない。
- 鍵となる考え「コードからすぐにわかることをコメントに書かない」
- コードを理解するよりも、コメントを読んだ方が早く理解できるなら、そのコメントには価値がある。
- コメントはひどい名前の埋め合わせに使うものではない。名前がひどいなら名前を変えるべき。
- 関数名は「自己文書化」すべき。
- 優れたコード > ひどいコード + 優れたコメント
-
自分の考えを記録する
- 何をコメントすべきか
- 優れたコメントとは「考えを記録する」ためのものである。
- 映画の DVD についている「監督のコメンタリー」つまり、「監督の考え」を記録したもののようなもの。
- コードの欠陥を認め、コメントをつける。
- TODO: あとで手をつける
- FIXME: 基地の不具合があるコード
- HACK: あまり綺麗じゃない解決策
- XXX: 危険!大きな問題がある。
- 定数の適正範囲には意味がある場合がある。ならば、その理由をコメントする。
-
読み手の立場になって考える
- プロジェクトを熟知していない人の立場になって考える。
- 質問されそうなことを想像する。
- なぜ、そのような記述になっているのか?
- ハマりそうな罠を告知する。実行時間の長さ、ハングの可能性など
- プロジェクトの全体像について新入りに説明するであろうことはコメントする。
- 関数の内部の大きな塊に要約コメントをつける。
-
ライターズブロックを乗り越える。
- ライターズブロックとはコメント記述への心理的ハードルのこと
- 気負いせずに、記述していて「ヤバイ!」と思ったことはそのまま書けばよい。
-
まとめ
コメントすべきではないこと
- コードからすぐ抽出できること
- ひどいコードを補うためのコメント
コメントすべきこと
- なぜ、コードが他のやり方ではなく、この方法をとっているのか。
- コードの欠陥を TODO: や FIXME: などでコメントする。
- 定数の値にまつわる背景
読み手の立場になって考える
- コードを読んだ人が「えっ?」となるところにコメント
- ファイルやクラスには全体像の要約コメント
- コードブロックの概要をコメント
6章 コメントは正直で簡潔に
- 鍵となる考え「コメントは領域に対する情報の比率が高くなければいけない」
-
コメントを簡潔にしておく
- 簡潔にし、なるべく行数をかけない。
-
あいまいな代名詞を避ける
- 「それ」や「これ」は読み解くのに手間がかかる。
-
歯切れの悪い文章を磨く
- 単純、短く、直接的に
-
関数の動作を正確に記述する
- 「改行を数える」ではなく、「\n の数を数える」のように、より直接的に動作の核心を記述する。
-
入出力のコーナーケースを実例に使う
- 複数の疑問に答えるような実例をコメントに記述する。
-
コードの意図を書く
- コードの動作をそのまま書くことになんの情報も詰め込まれないこともある。
- プログラマの意図、つまり、そのコードで何をしたいのかをコメントにつけることで、バグの発見にも繋がる。
-
「名前付き引数」コメント
- phthon では引数に値だけでなく、名前を記述することもできる。
- Java では、それができないので、引数の前にインラインコメントで名前を添えておくことでわかりやすくすることもできる。
-
情報密度の高い言葉を使う
- キャッシュ層、正規化、ヒューリイステイック、ブルートフォース、ナイーブソリューションなどの用語を使用する。
-
まとめ
- 複数のものを指す可能性がある「それ」や「これ」などの代名詞を避ける。
- 関数の動作はできるだけ正確に説明する。
- コメントに含める入出力の実例を慎重に選ぶ
- コードの意図は、詳細レベルではなく、高レベルで記述する。
- よくわからない引数にはインラインコメントで名前をつける。
- 多くの意味がつけ込まれた言葉や表現を使って、コメントを簡潔に保つ。
第2部 ループとロジックの単純化
- 複雑なループ、巨大な指揮、膨大な変数を見ると、頭のなかの「精神的な荷物」が増えてしまう。これは「理解しやすい」の正反対。
- 「精神的な荷物」が多いと、バグは見つからない、コードは変更しにくい、コードに触れるのが楽しくなくなる。
7章 制御フローを読みやすくする
- どこをたどって読むのか分かりやすいコードを書く。
- 鍵となる考え「条件やループなどの制御フローはできるだけ自然にする。コードの読み手がたちどまったり読み返したりしないように書く。」
-
条件式の引数の並び順
- 引数の並び順は、左: 調査対象、右: 比較対象。例: if (length >= 10)
- 英語の語順と同じ。
-
if/else ブロックの並び順
- 条件は否定系でなく、肯定系を使う。
- 単純な条件を先に書く。if と else が同じ画面に表示されるので見やすい。
- 関心を惹く条件や目立つ条件を先に書く。
- これらは衝突するときもあるので、自分で判断することも大切。
-
参考演算子
- 鍵となる考え「行数を短くすることよりも、他の人が理解するのにかかる時間を短くする。」
- アドバイス「基本的には if/else を使おう。三項演算子はそれによって簡潔になるときだけ使おう。
- 例えば、単純な2つの値おから一つを選ぶようなものならば、三項演算子にすることで読みやすくなる。
-
do/while ループを避ける。
- do/while ループは、コードの読む順序が通常のコードと逆になるので、読みにくい。
- なるべく while ループで書くようにする。
-
関数から早く返す。
- 関数で複数の return を使うことに遠慮しなくてもいい。
-
悪名高き goto
- goto は、スパゲッティーコードの原因になる。
-
ネストを浅くする
- ネストの深い if 文は後からコードを追加することによって生まれる。
- 鍵となる考え「変更するときにはコードを新鮮な目で見る。一歩下がって全体を見る。
- 早めに返してネストを削除する。
- ネストを削除するには、「失敗」ケースをできるだけ早めに関数から返せばいい。
- ループ内部のネストを削除するためには、continue を使用することもあるが、わかりづらくなることもあるので注意が必要。
-
実行の流れを終えるかい?
- コードを舞台裏で実行する構成要素があるとコードを追うのが難しくなる。
- そのような構成要素は、コードの読み手がたちどまったり読み返したりすることを避けるために、コードの流れを終えるようにする。
- 例えば、関数の終わりに return をつける。
- スレッド: どのコードがいつ実行されるのかよくわからない。
- シグナル/割り込みハンドラ: 他のコードが実行される可能性がある。
- 例外: いろんな関数呼び出しが終了しようとする。
- 関数ポインタと無名関数: コンパイル時に判別できないので、どのコードが実行されるのかわからない。
- 仮想メソッド: object.virtual_method() は道のサブクラスのコードを呼び出す可能性がある。
- コードが読みやすくなったり、冗長性が低くなったりするが、割合を高くすると、バグを見つけるのが難しくなる。
9.まとめ
- 比較を書く時は変化する値を左に、より安定した値を右に配置する。
- if/else 分のブロックは適切に並び変える。肯定系、単純、目立つものを先に処理する。
- 参考演算子、do/while、gotu などを使うとコードが読みにくくなることが多い。
- ネストを浅くする。ガード節が便利。
8章 巨大な式を分割する
- 鍵となる考え「巨大な式は飲み込みやすい大きさに分割する」
-
説明変数
- 式を簡単に分割するには、式を表す変数を「説明変数』という。
-
要約変数
- 変数が5つも入っているなら、要約した変数を使う。「要約変数』という。
-
ド・モルガンの法則を使う
- !をくくると読みやすくなる。複数あると読みづらい。
-
短絡評価の悪用
- 鍵となる考え「頭がいいコードに気をつける。あとで他の人がコードを読むときにわかりにくくなる。」
- 短絡評価は便利な機能だが、可読性を損なう使い方は避ける
-
複雑なロジックと格闘する
- 以上、以下、より大きい、より小さいなどについて扱うコードはわかりづらくなりがち。
- 複雑なコードに対しては、より優雅な手法を見つける。例えば反対から考えてみる。とか。
-
巨大な式を分割する
- 要約変数や説明変数を使うことで以下の恩恵が得られる。
- タイプミスが減る。
- 横幅が縮まる。
- 変更も集約される。
-
式を簡潔にするもう一つの方法
- C++ではマクロを使うのも良い。
-
まとめ
- 巨大な式を分割するために「説明変数」や「要約変数」を使用する。
- ド・モルガンの法則を使って括ったり、複雑な論理条件の反対を考えたりすることも有効。
9 章 変数と読みやすさ
- 変数を適当に使ったときに起こる3つの問題
- 変数が多いと変数をついせtきするのが難しくなる。
- 変数のスコープが大きいとスコープを把握する時間が長くなる。
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる。
-
変数を削除する。
- 役に立たない一時変数を削除する: なくても楽にコードが読めるなら、その変数は削除する。おそらくこれまでの編集の中で生まれた残骸だから。
- 中間結果を削除する: 関数から早く返す、タスクはできるだけ早く完了する、を実践することで、中間結果を保持するためだけの変数を削除できる場合がある。
- 制御フロー変数を削除する: for, while などを続けるかどうかだけを制御するための boolean 変数は break で代替できる場合がある。
-
変数のスコープを縮める。
- 全ての変数の「スコープを縮める」のはいい考え。
- 鍵となる考え「変数うのことが見えるコード行数をできるだけ減らす。
- クラスの中のいくつかのメソッドしか使っていないグローバル変数があるなら、それはローカル変数に格下げしてもいいかもしれない。
- メソッドをできるだけ sattic にすることも有効。
- 大きなクラスを小さなクラスに分割することも有効。
- JavaScript でプライベートな変数を定義するならクロージャを用いればいい。
- Javascript では、var,let,const をつけないとグローバル変数になってしまう。
- Java や C++では、if 文の中で宣言した exampleValur=0 はその中でしか使えないが、JavaScript ではそうではない。そのため、JavaScript の if 文で宣言された変数が他の if 文で使用されることもあるが、これはわかりずらいため、それならば、両 if 文の外で共通した変数を宣言しておいた方がいい。ブロックスコープにしたいのなら、let blockValue=0 とする。
- 変数は最初に全て宣言しておくよりも、使うときに宣言する方が分かりやすい。
-
変数は一度だけ書き込む。
- 変数が絶えず変更され続けると、理解しにくい。
- 変数は一度だけ書き込むことにするとよい。
- 永続的に変更されない変数は扱いやすい。
- Java の final はぜひとも使うべき。
- イミュータブルはトラブルになる傾向が少ない。
- 鍵となる考え「変数を操作する場所が増えると、現在値の判断が難しくなる。
-
最後の例
- while(true)や for(var i=0;true;i++)のようにすることで、変数を一つ減らせる場合がある。
-
まとめ
- 邪魔な変数を削除する。結果をすぐに使って、中間 ß 結果の変数を削除する。
- 変数のスコープをできるだけ小さくする。変数を数行のコードからしか見えない一に移動する。
- 一度だけ書き込む変数を使う。const や final を使う。
第3部 コードの再構成
- コードを大きく変更する技法の紹介。
- プロブラムの主目的と関係のない「無関係の下位問題」を抽出する。
- コードを再構成して、一度に一つのことをやるようにする。
- 最初にコードを言葉で説明する。その説明を元に鵜して綺麗な解決策を作る。
10 章 無関係の下位問題を抽出する
- 関数やコードブロックを見て、このコードの高レベルの目標な何か?と自問する。
- コードの各行に対して「高レベルの目標に直接的に効果があるのか?あるいは、無関係の下位問題を解決しているのか?」を自問する。
- 無関係の下位問題を解決しているコードが相当量あるのであれば、それらを抽出して別の関数にする。
-
入門的な例: findClosestLocation()
- 例えば、「与えられた地点から最も近い場所を見つける」関数の中の「2つの地点の球面距離を算出する」部分は抽出して別の関数にすることができる。
- つまり、大問題とは別の問題を解決している部分は抽出することができるということ。
- 切り分けることで、テストもできるし、再利用もできる。
-
純粋なユーティリティコード
- このライブラリに XYZ()関数があればいいのになと思うなら、自分で書けばいい。複数のプロジェクトで使えるユーティリティコードになる。
-
その他の汎用コード
- コードを抽出して別の関数にすることで、抽出部分の改善が楽になる。
-
汎用コードをたくさん作る
- 無関係の下位問題を抽出して作った関数は基本的で広く適用可能なので、複数のプロジェクトで再利用できる。
- このようなコードを共有できるよう、特別なディレクトリを用意するといい。
-
プロジェクトに特化した機能
- 抽出する下位問題というのは、プロジェクトから完全に独立したものであると、他プロジェクトでも使うことができる。
- しかし、そうでなくても、抽出するメリットはある。コードが読みやすくなる。
-
既存のインターフェースを簡潔にする
- インターフェースとは使い心地のようなもの。
- 既存のライブラリであっても、インターフェースが悪い場合がある。
- 理想とは程遠いインターフェースに妥協することはない。
- 自分でラッパー関数を用意して、使いやすくするといい。
-
必要に応じてインターフェースを整える。
- 異なるシステム、ライブラリ、モジュール、API などを結びつけるために行われるコードの変換、調整、ラッピングなどを行うコードを「グルーコード」という。
- グルー部分はシステムロジックとは関係ない下位問題であるため、抽出するとプログラムの本質的なロジックは簡潔になる。
-
やりすぎ
- 「無関係の下位問題の抽出」を細かく行いすぎると、逆にコードは読みにくくなる。
- コードの抽出はある程度のまとまりで行った方が読みやすい。
-
まとめ
- プロジェクト固有のコードから汎用コードを分離すること。
- 無関係な下位問題を抽出することで、プログラムに固有の小さな核だけが残る。
- 抽出された下位問題もリファクタリングしやすく、より緻密で正確なものになる。再利用もできる。
11 章 一度に1つのことを
- 一度に複数のことをするコードは理解しにくい。
- 鍵となる考え「コードは1つずつタスクを行うようにしなければならない。
- コードが行っている「タスク」を全て列挙する。
- タスクをできるだけ異なる関数に分割する。少なくとも異なる領域に分割する。
-
タスクは小さくできる。
- 「ユーザーの投票結果の変更を反映する」を行う関数の場合、「投票結果を数値にする」と「スコアに反映する」の2つを行っているので分割する。
-
オブジェクトから値を抽出する
- if 文が連続していると、全ての場合分けを注意深く読まなければいけない。ので、以下のように if ではなく||を使うのもよい。
var first_half, second_half;
if (country === "USA") {
first_half = town || city || "Middle-of-Nowhere";
second_half = state || "USA";
} else {
first_half = town || city || state || "Middle-of-Nowhere";
second_half = country || "Planet Earth";
}
return first_half + ", " + second_half;
-
もっと大きな例
- コードの改善のために、まずはコードを領域ごとに「デフラグ」(処理の順番を整頓すること)すること。
- そして、抽出したコードをヘルパー関数とする。
-
まとめ
- 読みにくいコードがあれば、そこで行われているタスクを全て列挙する。
- 別の関数やクラスに分割できるタスクがあるはず。
- それ以外は、関数の論理的な段落になる。
12 章 コードに思いを込める
- おばあちゃんがわかるように説明できなければ、本当に理解したとは言えない(アルバート アインシュタイン)
- 誰かに複雑な考えを伝えるときには、自分よりも知識が少ない人が理解できるような「簡単な言葉」で説明する能力が大切だ。
- つまり、コードも「簡単な言葉で」書くべきなのだ。
- ① コードの動作を簡単な言葉で同僚にもわかるように説明する。
- ② その説明の中で使っているキーワードやフレーズに注目する。
- ③ その説明に合わせてコードを書く。
-
ロジックを明確に説明する。
- つまりどういうことかと考え、ロジックを簡略化する。!が減ったりする。
-
ライブラリを知る。
- 人間がどのように考えるかを重視したコードは読みやすい。
- ライブラリが提供しているメソッドや関数をもっとよく知るといい。
- 簡潔なコードを書くのにかか欠せないのは、ライブラリが何を提供してくれているかを知ることだ。
-
この手法を大きな問題に適用する。
- 解決策を言葉で説明する。
- これからやろうとしていることを簡単な言葉で説明する。
- もっとも汚い部分があるなら、汚い部分を新しい関数に抽出する。
- 汚い部分を抽出した新しい関数も、言葉で説明することで考えを整理し、改善していく。
- 問題に特化した詳細な変数名を用いらなくてもよいのなら、もっと簡潔で汎用的なものに改名する。
-
まとめ
- プログラムを簡単な言葉で説明することで、コードがより自然になっていく。
- 説明で使う単語やフレーズをよく見れば、分割する下位問題がどこにあるかがわかる。
- ラバーダッキング: プログラムのデバックに悩む学生は、部屋の隅に置かれたディベアに向かって最初に説明しなければいけない。驚くことに、問題を声に出して説明するだけで、解決策が見つかる。
- 問題や設計をうまく言葉で説明できないのであれば、何かを見落としているか、詳細が明確になっていないということだ。プログラムを言葉にすることで明確な形になる。
13 章 短いコードを書く
- プログラマが学ぶべき最も大切な技能というのは、コードを書かないときを知ることなのかもしれない。
- 鍵となる考え「最も読みやすいコードは、何も書かれていないコードだ。」
-
その機能の実装について悩まないで -- きっと必要ないから
- プログラマというものはこれから実装するものに興奮し、過剰に見積り、その結果、完成しなかったり、全く使われなかったり、複雑になってしまったりするものだ。
- プログラマというものは、実装にかかる労力を楽観的に見積もったり、考えず、過小評価するものでもある。
-
質問と要求の分割
- すべてのプログラムが、高速で、正しくて、あらゆる入力をうまく処理する必要はない。
- 要求を詳しく調べれば、問題をもっと簡単にできるときもある。
- 例えば、2つの地点を引数にとり、その距離を算出するプログラムを作るとき、それを地球規模で考えるのか、それともローカルな範囲で考えるのかで必要なコードはかなり変わる。
- 最適化しようとするとき、複雑な方法を採用する前に、もっとシンプルな解決策がないかを考えることが大事
- シンプルな解決策が 「90%の効果」 を出せるなら、複雑な仕組みを導入するコストとリスクを考え、最適なバランスを見極めることが重要だ
-
コードを小さく保つ
- プロジェクトの進展とともに、ファイルは増え、内部構造は複雑化する。
- これにより、把握できなくなり、機能の追加が苦痛になり、コーディングが楽しくなくなる。
- ならば、プロジェクトが成長しても、コードをできるだけ小さく軽量に維持するしかない。
- ① 汎用的なユーティリティコードを作って、重複コードを削除する。
- ② 未使用のコードや無用の機能を削除する。
- 補足:園芸家が植物の健全な成長を促すために枝を刈り込むようなもの。案外、未使用のコードがプロジェクト全体に絡みあっていて、削除するとスッキリするものだ。
- ③ プロジェクトをサブプロジェクトに分割する。
- ④ コードの「重量」を意識する。軽量で機敏にしておく。
-
身近なライブラリに親しむ
- プログラマというのは、既存のライブラリで問題を解決できることを知らないことが多い。
- たまには標準ライブラリの全ての関数、モジュール、型の名前を15分かけて読んでみよう。
- どんなことができそうか感じるだけでいい。ふいに思い出すことがあるかもしれない。
- 平均役なソフトウェアエンジニアが、膨大な設計、デバック、修正、文書、最適化、テストの末に出荷するコードは一日平均10行らしい。
- であるならば、ライブラリを使用することは有意義であるに違いない。
-
例: コーディングするよりも Unix ツールボックスを使う。
- 問題を解決するために必ずしもコードを書く必要はなく、Unix のコマンドラインツールを組み合わせれば、簡単に同じことができる
-
まとめ
- コートが増えると「重く」なるし、開発も難しくなる。
- コードを書かないようにするには。
- ① 不必要な機能をプロダクトから削除する。
- ② 最も簡単に問題を解決できるような要求を考える。
- ③ 定期的に全ての API を読んで、標準ライブラリに慣れ親しんでおく。
第 4 部 選抜テーマ
- 効果的で読みやすいテストの書き方について
- 特定用途のデータ構造の設計と実装について
14 章 テストと読みやすさについて
- 効果的なテストを書くための簡単な技法について
-
テストを読みやすくて保守しやすいものにする
- テストコードというのは「本物のコードの動作と使い方を示した非公式的な文書」
- 鍵となる考え「他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする。
- テストコードが大きくて恐ろしいものだと起きること
- 本物のコードを修正するのを忘れる。
- 新しいコードを書いたときにテストを追加しなくなる。
-
このテストのどこがダメなの?
- 例示されたテストコードの8つの問題点について解説していく。
-
テストを読みやすくする
- 大切でない証左はユーザから隠し、大切な詳細は目立つようにするべき。
- テストにとって、どうでもいい処理や要素はヘルパー関数に抽出してしまう。
- 最小のテストをつくる
- 12 章 コードに思いを込めるの技法を使う。このテストが何をしようとしているのかを言葉で説明するのだ。
- どんな状況と入力から、どんな振る舞いと出力が期待されるのか。
- 言語化したものから考えられるテストの本質は1行に収まるはずだ。
- 独自の「ミニ言語」を作る
- 「ミニ言語」 というのは、特定の用途のために簡単な記法を作ることを指す
- 独自のデータフォーマットを定義し、それを解析する方法を用いることで、データをシンプルな表現にしつつ、適切に処理できるようにする。
-
エラーメッセージを読みやすくする
- assert()ではエラーメッセージがわかりづらくなってしまう場合がある。
- もっといい assert()が用意されている。
- 例えば、Boost C++ライブラリの BOOST_CHECK_EQUAL()や Pyton の unittest モジュールの assertEqual()は結果と期待値がどのように違ったのかを示してくれる。
- どの言語にも、役に立つライブラリやフレームワークがきっとあるはず。ライブラリのことを知っておくといい。
- 手作りのエラーメッセージ
- エラーメッセージをもっといい感じにしたい!
- エラーメッセージはできるだけ役に立つようにする。
- 自分好みのエラーメッセージを出力する「手作りのアサート」を用意するのが最善の道かもしれない。
-
テストの適切な入力値を選択する
- テストの適切な入力値を選択するのはとても重要なことだ。
- 入力値とは、そのテストを完全にテストするものでなければならない。
- また、簡単に読めるような単純なものでなければならない。
- 鍵となる考え「コードを完全にテストする元も単純な入力値の組み合わせを選択しなければならない。」
- 入力値を単純にする
- ものすごいマイナス値なら-e100 のようなシンプルなものを使う。
- 鍵となる考え「テストには最も綺麗で単純な値を選ぶ」
- 一つの機能に複数のテスト
- コードを検証する完璧な入力値を一つ作るのではなく、小さなテストを複数作る方が簡単で効果的で読みやすい。
-
テストの機能に名前をつける
- テストの名前を意味のないものにしてはならない。
- テストの内容を表した名前をつけるべきだ。
- 以下のことをすぐ理解できる名前がよい。
- テストするクラス
- テストする関数
- テストする状況やバグ
- Test_SortAndFilter_EmptyList()のように、テストであること、関数名、オプションを表すテストタイトルがおすすめ。
- テスト関数の名前はコメントだと思えばいい。
- テストの名前は説明的な方が役に立つ。
-
このテストのどこがダメだったのか?
-
テストコードの8つの改善点
- テストが何をしているかは1つの文で記述できるはずなのに、どうでもよいことがたくさん書かれている。
- ヘルパー関数に抽出していないため、テストコードを簡単に追加できない。
- エラーメッセージが役に立たない。デバックに使える情報が足りない。
- 一度に全てのことをテストしようとしている。テストは分割した方が読みやすい。
- テストの入力値が単純でない。
- テストの入力値が不完全である。テストすべき値をテストしていない。
- 極端な入力値(エッジケース)のテストしていない。
- テスト関数の名前に意味が込められていない。テスト関数の名前は説明的にする。
-
-
テストに優しい開発
-
テストしやすいようにコードを設計すると、結果的に良いコードになる
-
状態や「セットアップ」がない、検査するデータが隠されていないテストがよい。
-
「テストしやすい設計を意識する」ことで、自然とコードの質が向上する
-
「テストが書きやすい設計」を意識することが大切
- クラスやメソッドを適切に分割する(密結合を避ける)
- グローバル変数や設定ファイルなどの「外部コンポーネント」に依存しすぎない
- テストが書きにくいと感じたら、設計を見直す
-
テスト容易性の低いコードの特性
- グローバル変数を使っている
- 多くの外部コンポーネントに依存している
- コードが非決定的な動作をする
-
テスト容易性の高いコードの特性とそこから生じる設計の利点
- クラスが小さい、あるいは、内部状態を持たない
- クラスや関数が 1 つのことをしている
- クラスは他に依存していない、高度に疎結合化されている
- 関数は単純でインターフェースが明確である
-
-
やりすぎ
- テストに集中しすぎてしまうこともある。
- テストのために本物のコードの読みやすさを犠牲にしてしまう。
- テストのカバレッジを 100%にしないと気がすまない。
- テストがプロダクト開発の邪魔になる。
- テストに集中しすぎてしまうこともある。
-
まとめ
- テストを改善するポイント
- テストのトップレベルはできるだけ簡潔にする。入出力のテストはコード1行で記述できる。
- テストが失敗したら、バグの発見や修正がしやすいようなエラーメッセージを表示する。
- テストに有効な最も単純な入力値を使う。
- テスト関数に説明的な名前をつけて、何をテストしているのかを明らかにする。Test*<関数名>*<状況>のような名前にする。
- テストを改善するポイント
15 章 「分/時間カウンタ」を設計・実装する
-
問題点
- ウェブサーバーの直近1分間と直近1時間の転送バイト数を把握するためのクラスの問題点を改善するにあたって、以下の点について考える。
- 名前の改善: get で始まる関数は軽力アクセサであると勘違いされるので避ける。
- Count()は動詞と名詞があるので、紛らわしい。Add()など間違われないものを選ぶ。
- コメントを改善する
- コードを見てわかるコメントは書かない。
- 誤解のない明確な説明を書く。
- 関数名やコメントを第3者に確認してもらい、誤解がないか確認するのもよい。第3者は将来の自分でもある。
- MinuteCount()と HourCount()の動作は似ているので、共通部分は抽出した方がいい。
- for ループの条件にループの目的と終了条件の 2 つが混在すると分かりずらいので、終了条件は for ループ内の if 文に分けるなどしてシンプルにする。
- 複雑でめんどくさい汚れ仕事はヘルパーメソッドに押し付ける。
- 求められる精度を正確に見極める。必要以上の精度を求めない。
- 内部に時計があるのではなく、引数として時刻を渡すことで、テストしやすくなる。バグが少なくなる。時計を持つクラスを限定できる。
- 一度に一つのことをする。そのためにクラスを分ける。
- パフォーマンスが高く、設計に柔軟性があり、読みやすいなら、コードは多くなってもいい。
- 50 行の読みにくいコードよりも、100 行の読みやすいコードの方がいい。
- ウェブサーバーの直近1分間と直近1時間の転送バイト数を把握するためのクラスの問題点を改善するにあたって、以下の点について考える。