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

新人SEの学習記録

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

学習記録:リーダブルコード、今後の予定

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

第Ⅲ部:コードの再構成

10章:無関係の下位問題を抽出する

  • 無関係の下位問題を抽出する、とは
    • 関数やコードブロックを見て、このコードの高レベルの目標は何か自問する
    • コードの各行に対して、高レベルの目標に効果があるか、無関係の下位問題を解決しているのか自問する
    • 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする
    • 無関係の下位問題を積極的に探しだすのがポイント
純粋なユーティリティコード
  • 文字列操作、ファイルの読み書きといったプログラムの核となる基本的なタスク
    • プログラミング言語の組み込みライブラリとして実装されている
    • しかし、たまに自分でこの溝を埋めなければいけない場合がある
    • 例えば、C++にはファイルの中身を全て読み込む方法が用意されていない
その他の汎用コード
  • JavaScriptデバッグ
    • alert()でメッセージボックスをポップアップさせることが多い
    • Ajaxでデータをサーバに送信するコード
ajax_post({
    url: 'http://example.com',
    data: data,
    on_success: function (response_data) {
        var str = "{\n";
        for (var key in response_data) {
            str += " " + key + " = " + response_data[key] + "\n";
        }
        alert(str + "}");
    }
});
  • 上記コードの高レベルの目標は「Ajaxでサーバを呼び出してレスポンスを処理」
    • しかし、コードの大部分は「ディクショナリをキレイに印字する」という無関係の下位問題を解決している
    • このコードを抽出して、format_pretty(obj)のような関数にする
汎用コードをたくさん作る
  • 汎用コードはプロジェクトから完全に切り離されている
    • 開発もテストも理解も楽、複数のプロジェクトで利用できる
    • 完全に切り離せないようなプロジェクトに特化した機能も、なるべく汎用的な機能にする
既存のインタフェースを簡潔にする
  • キレイなインタフェースを提供…引数が少なく、事前設定が要らず、面倒なことをしなくても使えるライブラリ
    • インタフェースがキレイじゃなくても、自分でラップ関数を作ることができる
    • JavaScriptでブラウザのクッキーを扱うのはすごく残念な感じ
    • name1=value1; ... で書かれている文字列から検索したり、有効期限を過去にして削除したり…
// max_resultsという名前のクッキーを読み込むコード
var max_results;
var cookies = document.coolie.split(';');
for (var i = 0; i < cookies.length; i++) {
    var c = cookies[i];
    c = c.replace(/^[ ]+/, '');  // 先頭の空白文字を削除
    if (c.indexOf("max_results=") === 0)
        max_results = Number(c.substring(12, c.length));
}

// 関数get_cookieを作っておけば良い
var max_results = Number(get_cookie("max_results"));
必要に応じてインタフェースを整える
  • プログラムの多くはその他のコードを支援するグルーコード
    • 例えば、ユーザの機密情報を含んだディクショナリをURLに使いたい
    • 機密情報なのでCipherクラスで暗号化したい
    • Cipherクラスは文字列を受け取るし、返すのはURLセーフな文字列ではない
    • やりたいことは単純だが、多くのグルーコードが必要>下位問題は抽出して別関数にする
やりすぎ
  • 目標は「無関係の下位問題を積極的に見つけて抽出する」こと
    • 積極的に、とはいったがやり過ぎなこともある
    • 新しい関数を追加するときは、それによる読みにくさのコストを相殺できるような関数である必要がある

11章:一度に1つのことを

  • コードは一つずつタスクを行うようにしなければいけない。
    • 関数は一度に一つのことを行うべきだ、関数に限った話ではない
    • 関数の中で論理的な区分に分けるなど。
    • 1)コードが行っているタスクを全て列挙する
    • 2)タスクをできるだけ異なる関数に分割する。少なくとも異なる領域に。
タスクは小さくできる
  • Up/Downの投票スコアを表示するJavaScript
    • Upならスコアを+1、Downなら-1する
    • コードはもう少し複雑で、old_voteとnew_voteを持ち、数値にパースする
    • 既にDownに投票しており(old_voteがDown)、Upに再投票(new_voteがUp)したらスコアは+2する
    • if文の入れ子で実現すると分かりにくい
  • やっていることは1)数値へのパース、2)スコアの更新
    • 2つのタスクを別々に解決すれば読みやすくなる
var score = get_score();

// vote_valuteの戻り値は'Up'なら1, 'Down'なら-1
score -= vote_value(old_vote);
score += vote_value(new_vote);

set_score(score);
オブジェクトから値を抽出する
  • location_infoから値を取り出して「都市, 国」に整形する
    • location_infoには市/町名、都市/群、州/地域、国名の4つが入っている
    • ただし、どれもが必須項目でない(値が入っていない可能性がある)
    • 値が入っていないことを考慮し、以下の対応策をとる
    • 1)都市名には市>都市>州の順で探し、値がなければ「何でもない場所」を入れる
    • 2)国名が入っていない場合、「地球」を入れる
var place = location_info["市名"];
if (!place) {
    place = location_info["都市名”];
}
if (!place) {
    place = location_info["州名"];
}
if (!place) {
    place = "何でもない場所";
}

// 国名を追加するコード…
  • 「一度の1つのタスク」を適用
    • 1)location_infoから値を抽出
    • 2)都市の優先順に調べる。
    • 3)国を取得。
    • 4)placeを更新
// 1)
var town = location_info["市名"];
var city = location_info["都市名”];
var state = location_info["州名"];
var country = location_info["国"];

// 2)
var first_half = "何でもない場所";
if (state) {
    first_half = state;
}
if (city) {
    first_half = city;
}

//以下略。。。
  • 言語の特性を活かしてもっと簡潔にも書ける
first_half = town || city || state || "なんでもない場所";
second_half = county || "地球";

12章:コードに思いを込める

  • コードをより明確にする簡単な手順
    1. コードの動作を簡単な言葉で同僚にも分かるように説明する
    2. その説明の中で使っているキーワードやフレーズに注目する
    3. その説明に併せてコードを書く
ロジックを明確に説明する
  • ユーザにページを閲覧する権限があるか確認し、なければエラーページに戻す
    • エラーページに戻すのは「管理者でないとき」「文書があり、その文書の所有者でないとき」
    • このままプログラムを書くと…
    • if(文書あり) { if (文書の所有者でない) 戻す } else { if (管理者でない) 戻す }
    • わかりにくい!
  • 「権限があるとき」を見てみると
    • 「管理者のとき」「文書があり、その文書の所有者のとき」
    • こちらでプログラムを書くと…
    • 一見、空のif文が二つもあり変だが、こっちの方がわかりやすい(否定も少ない)
if (is_admin_request()) {
    // 権限あり(何もしない)
} elseif( $document && ($document['username'] == $_SESSION['username'])) {
    // 権限あり(何もしない)
} else {
    return not_authorized();
}

13章:短いコードを書く

  • 最も読みやすいコードは、何も書かれていないコードだ。
その機能の実装について悩まないで ーきっと必要ないから
  • プロジェクトに欠かせない機能を過剰に見積ってしまいがち
    • その結果、多くの機能が、完成しないか、全く使われないか、アプリケーションを複雑にするものに
    • 実装にかかる時間を楽観的に見積ったり、将来必要になる保守や文書化の負担を忘れたりする
質問と要求の分割
  • 全てのプログラムが、高速かつ100%正しく、あらゆる入力をうまく処理する必要はない
    • 要求を詳しく調べれば、問題をもっと簡単にできることもある
  • 例)店舗検索システム
    • 要求:任意のユーザの緯度経度に対して、最も近い店舗を検索する
    • これを100%正しく実装するには…
    • 1)日付変更線をまたいでいる時の処理
    • 2)北極や南極に近い時の処理
    • 3)1マイルあたりの緯度に対応した地球の曲率の処理…
    • 実際の要求は「テキサス州のユーザのために、テキサスで最も近い店舗を検索する」
    • これなら全ての店舗との距離を計算すればよい
コードを小さく保つ
  • プロジェクトが成長すると、それらを結びつける複雑さはもっと早い速度で成長する
    • コードをできるだけ小さく軽量に維持するしかない
    • 汎用的なユーティリティコードを作って重複を削除する
    • 未使用のコードや無用の機能を削除する
    • プロジェクトをサブプロジェクトに分割する
    • コードの重量を意識する
身近なライブラリに親しむ
  • たまには標準ライブラリの全関数・モジュール・型の名前を15分掛けて読んでみよう
    • どんなことができそうか感じ取るだけでOK
    • ライブラリの機能を知っておき、実際に活用することが重要