はじめに
今回は、より良いコードを書くためのシンプルで実践的なテクニックが学べる「リーダブルコード」という書籍を読んで、得た知識を整理するための備忘録になります。
目次
- 名前に情報を埋め込む
- 誤解されない名前
- 美しさ
- コメントするべきことを知る
- コメントは正確で簡潔に
- 制御フローを読みやすくする
- 巨大な式を分割する
- 変数と読みやすさ
- 無関係な下位問題を抽出する
- 一度に一つのことを
- コードに思いを込める
- 短いコードを書く
- テストと読みやすさ
- 「分/時間カウンタ」を設計・実装する
名前に情報を埋め込む
名前を付けるときは、それが変数であっても、関数であっても、クラスであっても、同じ原則を当てはめることができる。
[鍵となる考え]
名前に情報を埋め込む
気取った言い回しよりも明確で正確な方がいい
-
明確な単語を選ぶ
例えば、Getではなく状況に応じてFetchやDownloadなどを使う。 -
汎用的な名前を避ける
tmpやretvalなど。ただし明確な理由があれば話は別。 -
抽象的な名前よりも具体的な名前を使う
ServerCanStart()よりもCanListenOnPort()の方が明確。 -
変数名に大切な情報を追加する(接尾辞や接頭辞を使う)
ミリ秒を表す変数名には、後ろに_msをつける。これからエスケープが必要な変数名には、前にraw_をつける。 -
スコープの大きな変数には長い名前をつける
スコープが数画面に及ぶ変数に1~2文字の短い暗号めいた名前を付けてはいけない。
短い名前はスコープが数行の変数に付けるべき。 -
名前のフォーマットで情報を伝える(大文字やアンダースコアなどに意味を含める
例えば、クラスのメンバ変数にアンダースコアをつけて、ローカル変数と区別する。
誤解されない名前
積極的に「誤解」されそうな名前を探していく。
そうすれば変更が必要なあいまいな名前が見つかるだろう。
[鍵となる考え]
名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する
-
英語の単語はfilter・lenght・limitのようにプログラミングに使うには意味があいまいなものが多い
⇒filter()では「選択する」のか「除外する」のかわからない
誤解を招く可能性がある場合はselect()やexclude()にする -
限界値を含めるときはminとmaxを使う
-
範囲を指定するときはfirstとlastを使う
-
包含/排他的範囲にはbeginとendを使う
-
ブール値(理論値)に名前を付けるときは、それがブール値だとわかるようにisやhasなどの単語を使う
-
単語に対するユーザの期待に注意する
⇒例えばget()やsize()には軽量なメソッドが期待されている
美しさ
優れたソースコードは「目に優しい」ものでなければいけない。
具体的には以下の3つの原則について。
- 読み手が慣れているパターンと一貫性のあるレイアウトを使う
- 似ているコードは似ているように見せる
- 関連するコードをまとめてブロックにする
[鍵となる考え]
一貫性のあるスタイルは「正しい」スタイルよりも大切だ。
-
複数のコードブロックで同じようなことをしていたらシルエットも同じようなものになる
-
コードの「列」を整列すれば、概要が把握しやすくなる
-
ある場所でA・B・Cのように並んでいたものを、他の場所でB・C・Aのように並べてはいけない
-
空行を使って大きなブロックを理論的な「段落」に分ける
コメントするべきことを知る
[鍵となる考え]
コメントの目的は、書き手の意図を読み手に知らせることである
コードからすぐわかることをコメントに書かない
〇コメントすべきでは「ない」こと
- コードからすぐに抽出できること
- ひどいコード(例えば、ひどい名前の関数)を補う「補助的なコメント」。
この場合、コメントを書くのではなくコードを修正する
〇記録すべき自分の考え
- なぜコードが他のやり方ではなくこうなっているのか
- コードの欠陥をTODO:やXXX:などの記法を使って示す
- 定数の値にまつわる「背景」
〇読み手の立場になって考える
- コードを読んだ人が「えっ?」と思うところを予想してコメントをつける
- 平均的な読み手が驚くような動作は文書化しておく
- ファイルやクラスには「全体像」のコメントを書く
- 読み手が細部に捕らわれないように、コードブロックにコメントをつけて概要をまとめる
コメントは正確で簡潔に
コメントを書くのであれば、正確に書くべきだ(できるだけ明確で詳細に)。
またコメントには画面の領域を取られるし、読むのに時間もかかるので、簡単なものでなければいけない。
[鍵となる考え]
コメントは領域に対する情報の比率が高くなければいけない。
-
複数のものを指す可能性がある「それ」や「これ」などの代名詞を避ける
-
関数の動作はできるだけ正確に説明する
-
コメントに含める入出力の実例を慎重に選ぶ
-
コードの意図は詳細レベルではなく、高レベルで記述する
-
よくわからない引数にはインラインのコメントを使う
例:Function(/* arg = */) -
多くの意味が詰め込まれた言葉や表現を使って、コメントを簡潔に保つ
制御フローを読みやすくする
条件やループなどの制御フローがないコードは読みやすい。
[鍵となる考え]
・条件やループなどの制御フローはできるだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く。
・行数を短くするよりも、他の人が理解するのにかかる時間を短くする。
・変更するときにはコードを新鮮な目で見る。一歩下がって全体を見る。
-
比較を書くときには、変化する値を左に、より安定した値を右に配置する。
例:while(bytes_expected > bytes_received)
⇒while(bytes_received < byte_expected) -
if/else文のブロックは適切に並び替える
一般的には、肯定形・単純・目立つものを先に処理する -
三項演算子(?:)・do/whileループ・gotoなどのプログラミング構成要素を使うと、コードが読みにくくなることが多い。
代替となるものが必ずあるので、これらはあまり使わない方がいい。 -
ネストしているとコードを追うのに集中力が必要になる
深いネストを避けるには「直線的なコードを選択する」
また、早めに返してあげると、ネストを削除したりコードをクリーンしたりできる。
特に「ガード節」が便利
巨大な式を分割する
[鍵となる考え]
・巨大な式は飲み込みやすい大きさに分割する
・頭がいいコードに気をつける。後で他の人がコードを読むときにわかりにくくなる
-
説明変数を導入する利点
・巨大な式を分割できる
・簡潔な名前で式を説明することで、コードを文章化できる
・コードの主要な「概念」を読み手が認識しやすくなる -
ド・モルガンの法則を使ってロジックを操作する
-
複雑な論理条件は小さな分に分割することで読みやすくできる
変数と読みやすさ
[鍵となる考え]
・変数のことが見えるコード行数をできるだけ減らす
・変数を操作する場所が増えると、現在地の判断が難しくなる
-
邪魔な変数を削除する
-
変数のスコープをできるだけ小さくする
-
一度だけ書き込む変数を使う
無関係な下位問題を抽出する
プロジェクト固有のコードから汎用コードを分離する
-
関数やコードブロックを見て「このコードの高レベルの目標は何か?」と自答する
-
コードの行数に対して「高レベルの目標に直接的に効果があるか?あるいは、無関係の下位問題を解決しているのか?」と自答する
-
無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする
一度に一つのことを
[鍵となる考え]
コードは一つずつタスクを行うようにしなければいけない
-
読みにくいコードであれば、そこで行われているタスクをすべて列挙する
⇒分割できるタスクがあれば分割していく -
分割できないタスクは、関数の論理的な「段落」となる
-
タスクをどのように分割するかよりも、分割するということが大切
コードに思いを込める
-
ロジックを単純化して、明確な言葉で説明する
-
解決策を言葉で説明する
⇒簡単な言葉で! -
ラバーダッキングも有効な手法
(問題を声に出して説明することで、解決策が見つかる) -
問題や設計をうまく言葉にできないのであれば、何かを見落としているか、詳細が明確になっていないということ
短いコードを書く
[鍵となる考え]
最も読みやすいコードは、何も書かれていないコードだ。
-
不必要な機能をプロダクトから削除する。過剰な機能を持たせない
-
もっとも簡単な問題を解決できるような要求を考える
-
定期的にすべてのAPIを読んで、標準ライブラリに慣れ親しんでおく
テストと読みやすさ
[鍵となる考え]
・他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする
・コードを完全にテストする最も単純な入力値の組み合わせを選択しなければならない
・テストには最もキレイで単純な値を選ぶ
-
テストのトップレベルはできるだけ簡潔にする
-
テストが失敗したらバグの発見や修正がしやすいようなエラーメッセージを表示する
-
テストに有効な最も単純な入力値を使う
-
テスト関数に説明的な名前を付けて、何をテストしているのかを明らかにする
Test1()ではなく、Test_<関数名>_<状況>のような名前にする
「分/時間カウンタ」を設計・実装する
[問題点]
データ構造「分/時間カウンタ」において、ウェブサーバの間近1分間と間近1時間の転送バイト数を把握したい。
課題:速度とメモリ使用量
⇒「ベルトコンベヤー」設計で上記の課題は解決できる。
しかし、柔軟性が乏しく、その他の時間帯を扱うにはかなり手を入れる必要がある
⇒複数の下位問題に分割することですべての問題を解決。
ボトムアップで以下の3つのクラスを作り、それぞれのクラスで下位問題を解決する
-
ConveyorQueue
最大長のあるキュー。「シフト」可能で合計値を保持する -
TrailingBucketCounter
時間経過に伴ってConveyorQueueを移動する
また、1つの(最新の)時間帯のカウントを任意の精度で保持する -
MinuteHourCounter
2つのTrailingBucketCounterを保持する
1つは1分間のカウントで、もう一つは1時間のカウント
詳細の参考サイト:https://msyksphinz.hatenablog.com/entry/2017/11/27/020000