1章 理解しやすいコード
コードを改善するための原則や技法は「コードは理解しやすくなければならない」というテーマから生じている。
コードを短く簡潔にまとめることも大切なことだが、それよりも「このコードは理解しやすいだろうか?」と考えることが大事である。
2章 名前に情報を詰め込む
コードにおける命名で鍵となる考えは「名前に情報を詰め込む。」ことである。以下に具体的な方法について紹介する。
2.1 明確な単語を選ぶ
- 汎用的な単語は避ける。(get, stop)
- これだけだと一体何をget、stopするのか分からない。
- 気取った言い回しより明確で正確な単語を選ぶ。
- getと似たような単語にfetchやdownloadなどがあるが、結局何をfetchするのかというのが伝わらない。
- 明確で正確な単語を選ぶ。
2.2 汎用的な名前を使う場面を考える
- 2つの変数を入れ替えるときの一時的な変数(tmp)
- イテレータやループのときに用いる変数(i, j)
汎用的な名前を使うときはそれ相応の理由を考える。
2.3 抽象的な名前より具体的な名前を使う
2.4 名前に情報を追加する
- 単位(ミリ秒であればtime_ms、メガバイトのサイズの情報であればsize_mb)
- 属性(暗号化する前のパスワードであればplaintext_password, htmlの文字コードをUTF-8に変えた場合はhtml_utf8)
2.5 名前の長さ
- いい名前を選ぶときに「長い名前を避ける」という暗黙的な制約がある。
- スコープが短ければ短い名前でも良い。
- 逆にクラスのメンバ変数やグローバル変数の場合は十分な情報を詰め込んだ明確な名前が必要である。
- テキストエディタの「単語補完」機能を使う。
- プログラマの世界で省略するのが普通の単語は省略しても構わない。(Stringならstr、documentならdoc)
- プロジェクト固有の名称は省略しない。
- 不要な単語を投げ捨てる。(ConvertToString→ToString)
2.6 名前のフォーマットで情報を伝える
- クラスのメンバ変数やローカル変数など変数の種類の違いによって命名の仕方を変える(アンダーバー、大文字)
- プログラミング言語特有のフォーマット規約(Javascriptだとコンストラクタは大文字、通常の関数は小文字からスタート)
3章 誤解されない名前
鍵となる考えは「他の意味と間違えられることはないか」。
3.1 filter
results = Database.all_objects.filter("year <= 2011")
この場合filterが2011年より昔の情報を「選択」するのか「除外」するのかいまいち分からない。
「選択」であればselect、「除外」であればexcludeを用いる。
この他にもClipといった単語なども曖昧になる。
3.2 限界値を含めるときはminとmaxを用いる。
「CART_TOO_BIG_LIMIT」
この単語はCARTの大きさなのか長さなのかが曖昧である。
「MAX_ITEMS_IN_CART」
とすればカートの中のアイテムの最大数だと分かる。
3.3 リストの範囲
- 終端を範囲に含める場合(first, last)
- 含めない場合(begin, end)
3.4 ブール値の名前
「read_password」
この単語は2つの意味が内包されている。
- パスワードをこれから読み取る必要がある。
- パスワードを既に読み取っている。
この場合、
- need_password
- user_is_authenticated
などとしたほうが分かりやすい。
ブール値の変数名の付け方のコツとして
- is・has・can・shouldなどをつけてわかりやすくする。
- 肯定形にする。
3.5 ユーザの期待に合わせる
- get
- getは値を返すだけの「軽量アクセサ」であるという規約に慣れている。
- なのでO(n)の計算量がかかるものや、大量のデータを取ってきて平均を計算する。といったことはすべきではない。
これはsizeなどにも同じことが言える。
3.6 まとめ
最善の名前は誤解されない名前である。
4章 美しさ
優れたコードは「目に優しい」ものでなければならない。そのための3つの原則が以下
- 読み手が慣れているパターンと一貫性のあるレイアウトを使う。
- 似ているコードは似ているように見せる。
- 関連するコードをまとめてブロックにする。
4.1 なぜ美しさが必要か
見た目が美しいコードは読みやすく、使いやすい。
4.2 対応
- 適切な位置で改行を行う(複数の似たメソッドがあった時に、ひとつだけ改行位置をずらすことはしない)
- メソッドを使って整列させる(重複の排除ができる)
- 引数が3つある同じメソッドを5つ並べるときなどに「,」の位置などを揃える。
- コードの並びを意味のある並びにする。(例えば変数定義の部分など)
- アルファベット順
- HTMLのフォームの順序に揃える
- 重要度順
- クラスの中に多くのメソッドがある場合、論理的なグループに分ける。
- コードを段落に分ける。
- コードの中で同じような表現は同じように表現する。(一貫性をもたせる)
5章 コメントすべきことを知る
コメントの目的は、「書き手の意図を読み手に知らせること」である。
「コードの動作を説明する。」は目的のごく一部である。
コードを書いているときの頭の情報をいつ書き出せば良いのかということについて、ここで紹介する。
5.1 コメントするべきでは「ない」ことを知る。
- コードからすぐに分かることはコメントに書かない。
- 「〇〇クラスについて」、「コンストラクタ」など
- コメントをひどい(分かりにくい)名前の埋め合わせに使うべきではない。
- 優れたコード>ひどいコード+優れたコメント
5.2 自分の考えを記録する。
- コードに対する大切な考えを記録する。
- 「ヒューリスティックだと単語が漏れることがあるが仕方ない。100%は難しい。」
- 「このデータだとハッシュテーブルよりバイナリツリーのほうが40%速かった。」
- コードの欠陥にコメントをつける
- 「このクラスは汚くなっている。」
- 「JPEG以外のフォーマットに対応する。」
- よく使う記法(TODO:あとで手を付ける、FIXME:既知の不具合があるコード、HACK:あまりきれいじゃない解決策、XXX:危険!大きな問題がある)
- 定数にコメントをつける
- 値の決め方の基準を書く
- 値は2以上で十分
- 合理的な限界値 など
- 値の決め方の基準を書く
5.3 読み手の立場になって考える
- 質問されそうなことを想像してコメントをつける
- どのように間違えて使う可能性があるか、嵌りそうな罠は何かを考える
- 実行時間はO(タグ数×タグの深さの平均)なのでネストの深さに気をつける。
- 「全体像」の理解を手助けしてくれるコメントを書く
- これはビジネスロジックとデータベースをつなぐグルーコードです。アプリケーションから直接使ってはいけません。
- 関数の内部でも「全体像」に対するコメントを書く
5.4 ライダーズブロックを乗り越える
- とにかく自分の考えを書き出してみる
- コメントを読んで改善が必要なものを見つける。
- 改善を施す。
6章 コメントは正確で簡潔に
ここではコメントを正確で簡潔に書く方法について説明。鍵となる考えは「コメントは領域に対する情報の比率を高くすること。」
対応
- 「それ」や「これ」といった代名詞を避ける。
- もしくは「それ」の指す内容が自明になるように文章を書き換える。
- 「〜したかどうかによって動作を変える」といった曖昧な記述は避け、できるだけ正確なコメントをする。
- 関数の動作はできるだけ正確に記述する。
- 「ファイルの行数を数える」→「ファイルに含まれる改行文字{'\n'}を数える」
- 入出力のコーナーケースに実例を使う。
- できるだけ起こりうる質問に対応できる実例を書く。(簡単な実例は不要)
- コードの意図は高レベルで記述する。
- 「listを逆順にイテレートする」ではなく「値段の高い順に表示する」
- 意図が伝わりにくい引数にはコメントをつける。
- 多くの意味が詰め込まれた言葉や表現を使って、コメントを簡潔に保つ。
7章 制御フローを読みやすくする
鍵となる考えは「条件やループなどの制御フローはできるだけ自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く。
7.1 条件式の引数の並び順
「調査対象」の式。変化する。 | 「比較対象」の式。あまり変化しない。 |
---|---|
length | 10 |
bytes_received | bytes_expected |
7.2 if-elseブロックの並び順
- 条件は否定形より肯定系を使う。
- 単純な条件を先に書く。
- 関心を引く条件や目立つ条件を先に書く。
否定形だが、関心をひきたい条件などもある可能性があるが、それは都度柔軟に対応。
7.3 三項演算子
- 使うことで冗長な表現を避けられるのであれば積極的に使う。
- 鍵となる考えは「行数の短さより他人が理解するのにかかる時間を短くする。」
7.4 do/whileループを避ける
- 条件が下にあるのでコードを二回読むことになる。
- whileループでそもそも置き換えられる。
- do の内部にcontinue文を記述すると挙動が分かりづらい。
等の理由で避けるべき。
7.5 関数から早く返す
7.6 悪名高きgo to
C言語においてgo to文はスバゲティコードの原因になるので使用を避ける。
7.7 ネストを浅くする
- ネストの深い構造はいくつかの状態を覚えて置かなければならず、自分の立ち位置が分かりづらくなる。
- 「失敗ケース」をできるだけ早く関数から返してネストを削除する。
- ループ内でcontinue文を使うことでループ内部でも「早めに返す」ことでネストを浅くする。
7.8 実行の流れを追えるか
スレッド、シグナル/割り込みハンドラ、例外、関数ポインタと無名関数、仮想メソッドといった構成要素はコードが読みやすくなったり、冗長性が低くなる一方で理解しにくくなるのでコード全体に占める割合を高くしないようにすることが大切。
8章 巨大な式を分割する
鍵となる考え「巨大な式は飲み込みやすい大きさに分割する。」
対応
- 説明変数を用いる
- 式を表す変数のこと
- 要約変数を用いる
- 複雑な条件式が何をしているのかを説明する変数のこと
- ド・モルガンの法則を用いる
- 論理式をより簡単なものに置き換えられる可能性がある
- 短絡評価は簡潔に使える場合のみ用いる。
- 「頭が良い」コードに気をつける。あとでコードを読む人が分からなくなる。
- 複雑なロジックをより簡潔にできるよう工夫する。
- 巨大な「文」を説明変数によって簡潔にする。
- また、マクロを使って簡潔にする。
9章 変数と読みやすさ
変数を適当に使うとプログラムが理解しにくくなる。変数に関する問題として以下の3つの問題がある。
- 変数が多いと変数を追跡するのが難しくなる。
- 変数のスコープが大きいとスコープを把握する時間が長くなる。
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる。
対応
- 無駄な変数は削除する。
- 一度しか使われていない。
- 変数の利用によってコードの意味が明確にならない。
- 変数のスコープを縮める。
- 鍵となる考え「変数のことが見えるコード行数をできるだけ減らす。」
- 一部しか使っていない変数はスコープを落として使う。
- いくつかの言語例
- Javascript:変数の定義にvarをつけないとグローバル変数になるので常にvarキーワードをつける。
- Python、Javascript:ブロックで定義された変数は関数全体に漏れ出る。
- その変数を使っている場所の「最も近い共通の祖先」で変数定義を行う。
- 変数を操作する場所を減らす。
- できるだけ不変にする。
- 鍵となる考え「変数を操作する場所が増えると、現在地の判断が難しくなる。」
10章 無関係の下位問題を抽出する
高レベルの目的を達成するためにあまり関係がない問題を積極的に抽出せよということ。
(例)
- 与えられた地点から最も近い場所を見つける
- 与えられた複数地点を全て検証する
- 2つの地点の球面距離の算出(無関係)
- Javascriptでブラウザのクッキーを取得して表示する。
- document.cookieでクッキーを取得。
- クッキーに関わる巨大な文字列を自分でパース。(無関係)
- ブラウザに表示
プロジェクト固有のコードから汎用的なコードを切り離すことが大切。ただしやりすぎて小さな関数を作りすぎると、逆に読みにくくなるので注意する。
11章 一度に1つのことを
鍵となる考え「コードは1つずつタスクを行うようにしなければならない。」
以下に「1度に1つのタスクをする」ために使っている手順を紹介する。
- コードが行っている「タスク」をすべて列挙する。小さなことから曖昧なものまで様々
- タスクをできるだけ異なる関数に分割する。少なくとも異なる領域に分割する。
12章 コードに思いを込める
プログラムのコードは読み手が理解しやすいように「簡単な言葉」で書くべきということ。
対応
- コードの動作を簡単な言葉で同僚にもわかるように説明する。
- その説明のなかで使っているキーワードやフレーズに注目する。
- その説明に合わせてコードを書く。
- 自分が実装している機能のロジックを書き出してみる。
- 何をしようとしているのかを書き出してみる。
- 汚いロジックを小さな問題に分割する。
- 小さな問題でも同様のことをする。
13章 短いコードを書く
鍵となる考え「最も読みやすいコードは、何も書かれていないコードだ。」
対応
- プロジェクトに欠かせない機能は何かを考え、過剰な機能の実装をしない。
- プログラマは実装にかかる労力を過小評価しがちで、保守や文書化などの「負担」時間を忘れる。
- コードを小さく保つ。
- 汎用的な「ユーティリティ」コードを作って、重複を排除する。
- 未使用のコードや無用の機能を削除する。
- プロジェクトをサブプロジェクトに分割する。
- コードの「重量」を意識する。軽量で軽微にしておく。
- ライブラリの再利用を行う。
- たまに標準ライブラリの関数やモジュールを見ておくと良い。
- コーディングよりUNIXツールボックスを使う。
14章 テストと読みやすさ
鍵となる考えは「他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする。」
テストの一般的な設計原則として「大切ではない詳細はユーザから隠し、大切な詳細は目立つようにする。」
対応
- テストが何をしようとしているのかを簡単な言葉で説明する。
- エラーメッセージを読みやすくする。
- assertメソッドを用いる。
- 手作りのエラーメッセージを用いる。
- テストの意味のある入力値を選択する。
- コードを完全にテストする最も単純な入力値の組み合わせを選択しなければならない。
- テストには最もきれいで単純な値を選ぶ。
- 「完璧な」入力値を1つ作るのではなく小さなテストを複数作る。
- テストに適切な名前をつける。
テスト容易性
- テスト容易性の低いコード
- グローバル変数を用いている。
- 多くの外部コンポーネントに依存している。
- コードが非決定的な動作をする。
- テスト容易性が高いコード
- クラスが小さい。あるいは内部状態を持たない。
- クラスや関数が1つのことをしている。
- クラスは他のクラスにあまり依存しない。高度に疎結合化されている。
- 関数は単純でインターフェースが明確。
テストのやり過ぎには注意
- テストのために本物のコードの読みやすさを犠牲にしてしまう。
- テストのカバレッジを100%にしないと気がすまない。
- テストがプロダクト開発の邪魔になる。
- あくまでテストはプロダクト開発の一部
15章 「分/時間カウンタ」を設計・実装する
この章ではウェブサーバの直近1分間と直近1時間の転送バイト数を把握するクラスをどのように実装するかをこれまでの手法を踏まえた上で実装していくことが書かれており、これまでの総復習である。
どのようにして実装が変遷するのかを事細かに書くのは気がはばかられるためポイントだけ箇条書きで述べる。
- 分かりやすい名前をつける。
- 分かりやすいコメントをつける。
- 共通化できる部分をまとめる。
- 分カウンタと時間カウンタは時間のスパンが異なるだけで実装内容はほとんど同じ
- 外側の視点を得る。
- 自分のコードが「ユーザフレンドリ」かを確認する優れた手段である。
- 不要なデータは削除する。
- 直近1時間の転送バイト数を把握するのにそれより過去のデータはいらない。
- 余分なリソースを使わない。
- 直近1分間のデータは直近1時間のデータでも再利用できる。
- 1時間のデータを取得するときは1分の誤差を取得できればいい。
- そのうち何秒に取得したかというデータはいらない。メモリの無駄遣い
- 柔軟性の高い設計
- 1時間と1分のリソース取得には対応できるが例えば直近10分の転送バイト数を取得したいときにすぐに実装可能な設計にする。
- 1度に1つのことをする。
最後に
読みやすいコードを書くための3つのステップを軽くまとめる。
- 実際にやる。
- 読む側の視点に立ってコードを書く。
- 他人のレビューをもらう。
- 当たり前にする。
- 読みやすいコードを書くことを意識し続ける。
- 他の人が書いたコードで読みにくいコードを指摘する。
- コードで伝える。
- 読みやすいコードとは何かを読みやすいコードで伝える。
- 仲間にフィードバックする。