読者です 読者をやめる 読者になる 読者になる

新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:リーダブルコード

[学習記録] リーダブルコード

参考書籍

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

第Ⅰ部;表面上の改善

1章:理解しやすいコード

読みやすさの基本定理
  • コードは他の人が最短時間で理解できるように書かなければいけない。
    • 理解する=変更を加えたりバグを見つけたりできる、という意味
    • 「他の人」には数カ月後の自分自身も含まれている
  • コードは短くした方がいいが、理解に掛かる時間を短くする方が重要
    • 1行で全ての条件分岐を表現するより、2行に分けた方が良い場合もある
    • コメントはコードを長くするが、そのほうが理解しやすい場合もある
  • 理解するのに掛かる時間を最短化することは、その他の条件とは競合しない
    • コードを効率化するとか、設計をうまくやるとか、テストしやすいとか
    • 理解しやすいコードは設計やテストのしやすさに繋がることが多い

2章:名前に情報を詰め込む

明確な単語を選ぶ
  • 「getPage()」はページをどこから取ってくるのか?
    • インターネットからならDownloadPage()の方がわかりやすい
    • ThreadクラスのStop()は?取り消しができないならKill()、ResumeできるならPause()
    • 類語辞典を使って調べよう
汎用的な名前を避ける
  • retval?tmp?foo?
    • retvalには「これは戻り値です」以外の情報がない(戻り値なのは当たり前だ)
    • 2変数の値を入れ替えるのにtmpを使うのは悪くない(他に役割が無いことが明確になる)
    • ループでi, j, kを使うのは悪くないが、イテレータ複数ある場合にusers_iのようにするとバグが見つかりやすい
抽象的な名前よりも具体的な名前を
  • ServerCanStart()よりCanListenOnPort()の方が具体的
    • DISALLOW_EVIL_CONSTRACTORS -> DISALLOW_COPY_AND_ASSIGN
    • ローカルでデバッグする際に使う--run_locallyオプション -> リモートでデバッグしたいときは?
    • --extra_loggingの方が正確。run_locallyにデバッグ以外の機能がある場合は?別のオプションを作るべき
接尾辞や接頭辞を使う
  • 値の単位:ミリ秒(ms)、サイズ(mb)などを変数名につける
    • 重要な属性を追加する:plaintext_password、unescaped_comment、html_utf8
名前の長さを決める
  • newNavigationControllerWrappingViewController....
    • あまりに長い名前を避けるのは当然だが、どのくらいの長さにすればいいのか?
    • スコープが小さければ短くても良い:if文内でしか使わない変数なら、Map mでも分かる
    • 新しいチームメイトが理解できるなら省略しても良い:eval、str、docはわかるが、BEは?
    • 不要な単語は削除:ConvertToString()はToString()でも十分分かる
名前のフォーマットで情報を伝える
  • GoogleC++のフォーマット規約
    • クラス名はキャメルケースで、変数名はスネークケース、など
    • クラスのメンバ変数は最後の文字をアンダースコアに
    • 定数は頭にkを付ける(全部大文字だと#defineマクロと区別が付かない)
  • その他のフォーマット
    • HTMLタグのidの区切り文字はアンダースコアを、classはハイフンを

3章:誤解されない名前

  • 名前が「他の意味に捉えられることはないか?」と自問自答する
filter()
  • results = Database.all_objects.filter("year <= 2011")
    • resultsに入っているのはyearが2011以下のオブジェクト?2011より大きいオブジェクト?
    • filter()が分かりにくい。select()かexclude()にすれば良い
範囲の使い分け
  • 限界値を含めるときはminとmaxを使う
    • CART_TOO_BIG_LIMIT
    • LIMITではその数まで含んでいいのか分からない
    • MAX_ITEMS_IN_CART
  • 範囲の指定にはfirstとlastを使う
    • stop=4は4まで含む?含まない?
    • lastを使えば包含していることが明確になる
  • 包含/排他的関係にはbeginとendを使う
    • 「ちょうど最後の値を超えたところ」を表す簡潔な英語がない
    • beginとendの対はイディオムになっているので、これを使う
ブール値の名前
  • trueとfalseの意味を明確にしなければならない
    • read_password??
    • is・has・can・shouldを頭につけてわかりやすくする
    • 名前を否定形にするのは避ける。disable_xxxよりuse_xxxの方がわかりやすい
ユーザの期待に合わせる
  • 多くの場合、getXxx()はメンバの値を返すだけの軽量アクセサである
    • size()メソッドの計算量がO(n)だと想像できるか?
複数の名前を検討する
  • あるIDの設定を再利用して、変更が必要な情報だけ置き換えて再実験したい場合
    • 「あるID」を表す名前は?:template, reuse, copy, inherit
    • template:「これはTemplateだ」という意味かも。
    • reuse:reuse 100と書かれていたら「これは100回使える」という意味かも。
    • copy:「これは100回コピーされる」という意味かも。copy_experimentではどうか。
    • inherit:継承関係がわかりやすいが、他の実験から継承していることを明確に。
    • 結果、copy_experimentやinherit_from_experiment_idなどではどうか。

4章:美しさ

  • 優れたソースコードは「目にやさしい」ものでなければならない
    • 見た目が美しいコードのほうが使いやすいのは明らかである
    • プログラミングのほとんとの時間はコードを読む時間
  • 以下の3つの原則
    • 読み手が慣れているパターンと一貫性のあるレイアウトを使う
    • 似ているコードは似ているように見せる
    • 関連するコードをまとめてブロックにする
一貫性のある簡潔な改行位置
public static final TopConnectionSimulator wifi = new TcpConnectionSimulator(
    500, /* kbps */
    80, /* millisecs latency */
    200, /* jitter */
    1 /* packet loss */ );

public static final TopConnectionSimulator t3_fiber = 
    new TcpConnectionSimulator(
        45000, /* kbps */
        400, /* millisecs latency */
        250, /* jitter */
        5 /* packet loss */ );

コードのシルエットを揃える必要がある(ついでにコメントも)

public static final TopConnectionSimulator wifi = 
    new TcpConnectionSimulator(
        500,     /* kbps */
        80,      /* millisecs latency */
        200,     /* jitter */
        1        /* packet loss */ );

public static final TopConnectionSimulator t3_fiber = 
    new TcpConnectionSimulator(
        45000,   /* kbps */
        400,     /* millisecs latency */
        250,     /* jitter */
        5        /* packet loss */ );

もっと簡潔に書く方法がある。

// TcpConnectionSimulator(throughput, latency, jitter, packet_loss)
//                            [kbps]      [ms]   [ms]    [percent]  

public static final TopConnectionSimulator wifi = 
    new TcpConnectionSimulator(500,       80,  200,  1);

public static final TopConnectionSimulator t3_fiber = 
    new TcpConnectionSimulator(45000,    400,  250,  5);
縦の列を揃える
CheckFullName("Doug Adams" , "Mr. Douglas Adams", "");
CheckFullName("No Such Guy" , ""                , "no match found");
一貫性と意味のある並び
  • どの順番で書いてもよいコードが有る場合、意味のある順番に並べると良い
    • 対応するHTMLフォームのフィールドと同じ順にする、重要度の順、アルファベット順
    • どの並び順にするにせよ、一連のコードでは同じ並び順を使う
宣言をブロックにまとめる
class FronterdServer {
  public:
    FrontedServer();
    ~FrontedServer();

    // ハンドラ
    void ViewProfile(HttpRequest* request);
    void SaveProfile(HttpRequest* requrst);

    // ユーティリティ
    string ExtractQueryParam(HttpRequest* request, string param);
    
    // データベースのヘルパ
    void OpenDatabase(string location, string user);
};
コードを段落に分割する
  • 文章を段落に分割する理由
    • 似ている考えをグループに分けて、他の考えと分ける
    • 視覚的な踏み石の提供
    • 段落単位で移動できるようになる
  • コード間に間を空け、コメントを追加する
個人的な好みと一貫性
  • 最終的には個人の好みになってしまうこともある
    • 2つのスタイルが混ざらないように。一貫性の方が大事

5章:コメントすべきことを知る

  • コメントの目的は、書き手の意図を読み手に知らせることである
    • コードの「動作」を説明することではない
    • コードを書いているときに頭の中にある大切な情報を書き出す
コメントするべきでは「ない」こと
  • コメントを読むとその分コードを読む時間を使う。画面を占領する
    • コメントにはそれだけの価値を持たせるべき
    • コードから「すぐに」わかることをコメントに書かない
// Accountクラスの定義
class Account {
  public:
    // コンストラクタ
    Account();

    // profitに新しい値を設定する
    void SetProfit(double profit);
};

「すぐに」が大事。良く読めばわかるようなコードでは、コメントを読んだ方が早い場合もある

// 2番目の"*"以降を全て削除する
name = '*'.join(line.split('*'[:2])
  • コメントのためのコメントをしない
    • 関数宣言と同じコメント(名前を引数をそのまま日本語にして書いただけ)
    • もっと大切なことを書く。例えば、見つからなかったときにNULLを返すとか。
  • ひどい名前はコメントをつけずに名前を変える
    • CleanReply():Replyに対してRequestで記述した制限を課す。項目数やバイト数など。
    • 「clean」の意味を説明するだけになってしまう。関数名をEnforce...に変える方がよい。
    • DeleteRegistry():レジストリーキーのハンドルを解放する。実際のレジストリは変更しない
    • レジストリを削除!?となるので、やはりReleaseRegistryHandle()にした方が良い
    • 通常、コードの読みにくさを補う補助的なコメントは不要である
    • 優れたコードは、ひどいコード+コメントに優る
自分の考えを記録する
  • 監督のコメンタリーを入れる
    • コメントにはコードに対する大切な考えを記録しなければいけない
    • 例えば、以下のように
// このデータだとハッシュテーブルよりバイナリツリーの方が40%速かった

// ヒューリスティックだと単語が漏れることがあるが仕方がない。

// このクラスは汚くなってきている。サブクラスを作って整理した方がいいかも
  • コードの欠陥にコメントを付ける
    • コードは絶えず進化しているので、欠陥を生む運命にある
    • 欠陥を文章化することを恥ずかしがってはいけない
// TODO: もっと高速なアルゴリズムを使う

// TODO: JPEG以外のフォーマットに対応する
記法 典型的な意味
TODO: あとで手を付ける
FIXME: 既知の不具合
HACK: あまり綺麗じゃない解決策
XXX: 危険!大きな問題がある
  • 定数にコメントをつける
    • 定数には、その定数が何をするのか、なんのためにあるのかという「背景」がある
NUM_THREAD = 8 # 値は「>= 2 * num_processors」で十分
// 合理的な限界値。人間はこんなに読めない
const int MAX_RSS_SUBSCRIPTIONS = 1000;
image_quality = 0.72; // 0.72ならユーザはファイルサイズと品質の面で妥協できる。
    • こんなコメントは要らない?たいていの場合、こういったコメントは役に立つ
    • 名前が明確でコメントが不要な定数もある(SECONDS_PER_DAY)が、コメントで改善できる定数は多い
読み手の立場になって考える
  • 質問されそうなことを想像する
    • 他人のコードを読んで「えっ?これって何?」となったところにコメントを付ける
void Clear() {
    // ベクタのメモリを解放する(STL swap 技法で検索してみよう)
    vector<float>().swap(data);
}
  • ハマりそうな罠を告知する
    • このコードを見て驚くことは何だろうか?どのように間違ってる使う可能性があるか?
    • // メールを送信する外部サービスを呼び出す(1分でタイムアウト
    • // 実行時間はO(タグ数*タグ深さの平均)なので、ネストの深さに気をつける
  • 全体像のコメント
    • 新しいチームメンバにとって、最も難しいのは全体像の理解
    • クラスの連携、データの流れ…システムの設計者はこういったことについてコメントを書かない事が多い
    • 新しくチームに参加した人に教えるつもりでコメントに書く
    • 「これはビジネスロジックとデータベースをつなぐグルーコードです。アプリケーションから直接使ってはいけない」
    • 「このクラスは一見複雑だが、単なるキャッシュです」
  • 要約コメント
    • 関数の内部で全体像についてコメントすることもいい考えである
    • for文の前に 「顧客が自分で購入した商品を検索する」
    • あるブロックの前に「このユーザのロックを獲得する」
ライターズブロックを乗り越える
  • プログラマの多くはコメントをうまく書くのは大変だと思っている
    • ライダーズブロック(文書を書くのに行き詰まること)を乗り越えるには、とりあえず書くこと
    • 書いてみて、改善すればよい
    • 「ヤバい。これはリストに重複があったら面倒なことになる」
    • 改善すると…「注意:このコードはリストの重複を処理できません(実装が難しいので)」

6章:コメントは正確で簡潔に

  • 前章では何をコメントに書くべきかを説明した
    • 本章では、どうすれば正確で簡潔に書けるかを説明する
    • コメントは領域に対する情報の比率が高くなければいけない。
コメントを簡潔にしておく
// intはCategoryType。
// pairの最初はscore
// 2つめはweight
typedef hash_map<int, pair<float, float> > ScoreMap;

これなら一行で説明できるのでは?

// CategoryType -> (score, weight)
...
あいまいな代名詞を避ける
  • 「データをキャッシュに入れる。ただし、先に「その」サイズをチェックする。」
    • 「その」はデータ?キャッシュ?
    • 「データをキャッシュに入れる。ただし、先にデータのサイズをチェックする。」
    • あるいは、文書全体を書き換えて「それ」を明確にする
    • 「データが十分に小さければ、それをキャッシュに入れる。」
歯切れの悪い文章を磨く
  • コメントを正確にすることと簡潔にすることは両立することが多い
    • 「これまでにクロールしたURLかどうかによって優先度を変える」
    • 「これまでにクロールしていないURLの優先度を高くする」
    • 後者の方が単純だし短いし直接的であり、より情報が多い!
関数の動作を正確に記述する
  • ファイルの行数を数える関数
    • 「このファイルに含まれる行数を返す。」
    • あまり正確ではない。空のファイルは?"hello\n"は?
    • 「このファイルに含まれる改行文字('\n')を数える。」
入出力のコーナーケースに実例を使う
  • 慎重に選んだ入出力の実例をコメントに書いておけば、一番わかりやすい
    • 「srcの先頭や末尾にある'chars'を除去する」
    • charsは文字列?文字集合? srcの末尾に複数のcharsがあったら??
    • 「実例:Strip(abba/a/ba", "ab")は"/a/"を返す」
    • 全ての機能を見せる実例でないと意味がないことに注意。
コードの意図を書く
  • コードの動作をそのまま伝えるだけでは情報がない
    • 「listを逆順にいてレートする」 for (list...)
    • 「値段の高い順に表示する」と書くことでより高レベルな説明になる
名前付き引数コメント
  • よくわからない引数の説明には、引数にコメントを付けることが有用
    • Pythonのような言語なら引数を名前付きで渡せる
    • そうではない言語なら、インラインコメントを使う
Connect(timeout = 10, use_enctyption = false)
Connect(/* timeout_ms */ = 10, /* use_enctyption = */ false);
情報密度の高い言葉を使う
  • 同じ問題や解決策が何度も繰り返し登場する場合、こうしたパターンを説明する表現がある
    • 「このクラスには大量のメンバがあり、同じ情報がDBにも登録されているが、速度の面からここに保存しておき、…」
    • 「このクラスの役割は、データベースのキャッシュ層である。」と書けば良い。