リーダブルコード要約
リーダブルコードの要約
本を読んでEvernoteに書きためていたので、このブログにも残しておきます。
1. 理解しやすいコード
美しいコードを書くにはたった1つの原則を守ること。鍵となる考えは、「コードは理解しやすくなければいけない」ということ。
読みやすさの基本定理
鍵となる考えは「コードは他の人が最短時間で理解できるように書かなければいけない。」
小さいことは絶対にいいこと?
コードが短くした方が良い。だが、「理解するまでにかかる時間」を短くする方が大切。
これから紹介する規則や原則よりも、この「読みやすさの基本定理」を最優先で考える。
リファクタリングするか迷ったときは?
「このコードは理解しやすいだろうか?」と自問自答してみて、理解しやすいコードになっていたら、リファクタリングは不要。
2.名前に情報詰め込む
名前をつけるときは、それが変数でも関数でもクラスでも、同じ原則を当てはめられる。
それは「名前に情報を詰め込む」ということ。
明確な単語を選ぶ
- getPageよりも、fetchPageやdownloadPageのほうが明確。
- sizeよりもheightやnumNodesやmemoryBytesのほうが明確。
- stopでもいいが、動作に合わせてkillとかpauseのほうがいいかも。
- その他、考えたほうがいい名前は、tmp、retval
名前に情報を追加する
- 16進数のIDなら、idではなくhex_idに。
- 時間やバイト数のように計測できるものであれば、変数名に単位を入れる。
- その他重要な属性を追加する。passwordがプレインテキストならplaintext_passwordとか。
名前の長さ
スコープが小さければ短い名前でもいい。例えば↓の例だと、mという変数名には情報がほぼ含まれていないが、コードを理解するのに必要な情報がすぐ近くにあるため問題ない。
if(debug){ map<String, int> m; LookUpNamesNumbers(&m); Print(m); }
だが、mがクラスのメンバ変数だったり、グローバル変数だと問題あり。mの型や目的がよくわからなくなるから。
単語の頭文字と省略形
クラス名をBackEndManagerではなく、BEManagerと略したら、新しいチームメイトはおそらく意味を理解できない。
理解できるなら問題ないが、理解できないならダメ。
evaluationをeval、documentをdoc、stringをstrとするのはおそらく理解できるのでこれならOK。
3. 誤解されない名前にする
名前が「他の意味と間違えられることはないだろうか?」を何度も自問自答すること。
filterの例
results = DB.all_objects.filter(“year <= 2011”)
このresultsには、year <= 2011のオブジェクトか、year <= 2011を満たさないオブジェクトが格納されるのかわからない。
その理由は、filter が曖昧な言葉だから。
このfilterという名前を修正するならば、フィルタが「選択する」の意味ならselectがいいし、「除外する」のであればexcludeがよい。
限界値を含めるときはmin、maxを使う
限界値を明確にするには、名前の前に maxやminをつける。
MAX_ITEMS_IN_CART = 10 とか。これがCART_TOO_BIG_LIMITだとわかりにくい。
- if(shopping_cart.num_items() > MAX_ITEMS_IN_CART){ Error(“カート内の商品数が多すぎます”) }
- if(shopping_cart.num_items() > CART_TOO_BIG_LIMIT){ Error(“カート内の商品数が多すぎます”) }
上の例で1. だとすぐ理解できるが、2. だと “>” が正しいのか ”>=“ とすべきなのかが一発で判断できない。
範囲を指定するとき
以下のような単語を使って、範囲を明確にする。
- first、last
- begin、end
ブール値の名前
ブール値の変数やブール値を返す関数の名前をつけるときは、trueとfalseの意味を明確にする。
- 頭文字には、is・has・can・shouldをつけると分かりやすい。
- 肯定形にする。つまり、disable_ssl = false; とするより、use_ssl = true; のほうがすんなりわかる。声に出して読みやすい。それに短くてすむ。
ユーザの期待に合わせる
多くのプログラマはgetで始まるメソッドはメンバを返すだけの軽量なアクセサという規約に慣れている。 だから、大量データをイテレートして何かを計算する関数がgetXXXという名前だと、プログラマはコストが高いことを知らずにそれを呼んでしまう。 コストの高さが事前にわかるように、computeXXXとすべき。
4. ソースコードのレイアウトの美しさ
3つの原則を守ること。
原則(1). 読み手がなれているパターンと一貫性のあるレイアウトを使う。
- ある場所でA、B、Cのように並んでいるものを、他の場所でB、C、Aと並べてはいけない。意味のある順番を選び、常にその順番を守る。
原則(2). 似ているコードが似ているように見せる。
- 一貫性のある改行位置を意識する。コードの「シルエット」がおなじになるように。
- ポイントは「縦の線をまっすぐにする」ように空白や改行を使って整列させる。
原則(3). 関連するコードをまとめてブロックにする。
- ヘルパーメソッドを使って、重複を排除。DRYにする。
- 変数を複数並べて書くときは、意味の似ている変数をグループ化して書く。グループとグループの間には空行を入れる。
// ハンドラ void viewProfile(HttpRequest* request); void saveProfile(・・・ void FindFriends(・・・ //DBのヘルパ void openDatabase(String location, String user); void closeDatabase(String location);
- 同様に、関数内の各処理でも、意味のある単位ごとの段落にグルーピングして、グループ毎にコメントを書く。
void suggestNewFriends(User user){ //ユーザの友達のメールアドレスを取得する friends = user.frienss(); friendsEmails =… //まだ友達になっていないユーザを探す ・・・ //まだ友達になっていないユーザを表示する ・・・
5. コメントすべきこと
鍵となる考えは、「書き手の意図を読みてに知らせること」
コメントすべきでないことは?
コメントは、それを読む分だけコードを読む時間がなくなる。
画面も占領する。
つまり、コメントにはそれだけの価値をもたせるべき。
コードからすぐに分かることをコメントに書かない。(ポイントは「すぐに」分かることを書かない)
コメントがコードに対して新しい情報を提供しておらず、コードを見ればどう動くか分かる場合でも、コードを理解するよりコメントを読むほうが早く理解できるなら、コメントを書くべき。
ひどい名前はコメントをせずに名前を変える
関数名はいろんなところで使われるので、優れたコメントよりも関数名を分かりやすくするほうが大切。
優れたコード > 酷いコード + 優れたコメント
自分の考えを記録する
優れたコメントは自分の考えを記録するもの。
その関数がなぜ作られたのか、なぜ他のやり方ではなくこういうコードなのか、など。
- コードが汚い理由を書くのもOK。「//このクラスは汚くなってきている。サブクラスXXXXを使って整理したほうがいいかもしれない」など。
- コードが汚いことを認め、誰かに修正を促している。コメントがなければ誰も近づかないかもしれない。
- コードの欠陥にコメントをつける
- 定数にコメントをつける。定数の定義には、なぜその値を持っているのかという「背景」が存在する場合が多い。
NUM_THREAD = 8; //値は「>= 2* num_processors」で十分
- このように書けば、定数の決め方が分かる(1だと小さすぎて、50だと大きすぎる)
const int MAX_RSS_SUBSCRIPTIONS = 1000; //合理的な限界値。人間はこんなに読めない。
- このように書けば、1000という数値が適切だとわかる。
image_quality = 0.72; //0.72ならユーザはファイルサイズと品質の面で妥協できる。
- このように書くのもよい。一見不要なコメントだが、その値になった理由が分かる。
読み手の立場で考える
プロジェクトのことを熟知していない人が分かるように考える。
- 質問されそうなことは何か?を想像してコメントを書く
- 他の人がハマりそうな罠を告知してあげる
void sendEmail(String to, String subject, String body);
- この関数の実装では外部メールサービスに接続していて、その接続に1秒以上かかる。そのことを知らない人が、HTTPリクエストの処理中にこの関数を誤って呼び出すかもしれない。だから以下のように実装の詳細をコメントで書いてあげてもよい。
//メールを送信する外部サービスを呼び出している(1分でタイムアウト) void sendEmail(String to, String subject, String body);
ライターズブロック(行き詰まってしまって文章が書けないこと)を乗り越える
プログラマはコメントをうまく書くのは大変だと思っているので、コメントを書きたがらない人が多い。そういうときは、思ったことをそのまま書けばよい。
たとえば、「やばい!これはリストに重複があったら面倒なことになる...」と思ったら、
//やばい!これはリストに重複があったら面倒なことになる...
と書けばよい。
言い回しを変えれば、
- 「やばい」→「注意:」
- 「これ」→「入力を処理するコード」
- 「面倒なことになる」→「実装が難しくなる」
として、
// 注意:このコードはリストの重複を処理できません(実装が難しいので)
とする。
- コメントを書く作業の3手順
- (1) 頭のなかにあるコメントをとにかく書き出す
- (2) コメント読んで(どちらかといえば)改善が必要なものを見つける
- (3) 改善する
6.コメントは正確で簡潔に
- 曖昧な代名詞は避ける。「それ」とか「これ」とか。→名詞を代名詞に代入してみる。
- コメントを正確にすることと簡潔にすることは両立させやすい。
- 「//これまでクロールしたURLかどうかによって優先度を変える」 → 「//これまでにクロールしていないURLの優先度を高くする」
- 正確に記述する(実装に適したコメントを書く)
- 「//このファイルに含まれる行数を返す」と書いたら、””は0行なのか1行なのか? ”hello\n”は1行なのか2行なのか?といった疑問が出る。
- 正確に書くのであれば「//このファイルに含まれる改行文字(’\n’)を数える」と書くほうがよい。
- 実例をそのまま言葉で書いてしまってもよい。
- →「//実例:strip(“abba/a/ba”, “ab”) は”/a/“を返す」のように。
- コードの意図を書くときは、プログラムの動作を「高レベル」から説明する。
- 「//listを逆順にイテレートする」のように、プログラムの実装をそのまま説明するより、「//値段の高い順に表示する」と、プログラマがコードを書いたときに考えていたことに近くなるので良い。
- 情報密度の高い言葉を使う。
- これは当たり前。長い文章より、短くて情報量が多い文章を選ぶべき。
7. 制御フローを読みやすくする
鍵となる考えは「条件やループなどの制御フローはできるだけ「自然」にする。
コードの読み手が立ち止まったり読み返したりしないように書く。」
条件式の引数や並び順
if(length >= 10)
または if(10<= length)
だと、前者のほうが読みやすい。
while(bytes_received < bytes_expected)
または whilt(bytes_expected > bytes_received)
だと、前者の方が読みやすい。
→ 左側は「調査対象」の式。変化するもの。 右側は「比較対象」の式。あまり変化しないもの。
この指針は英文法と合致している。
「もし君が18歳以上ならば」というのは自然だが、「もし18年が君の年齢以下ならば」というのは不自然。
if( your_age <= 18 )
は自然だが、 if(18 >= your_age)
は不自然。
if/elseブロックの並び順
- なるべく条件は否定形よりも肯定形を使う。(ただし関心事が最初から否定形なら、否定形のまま扱ってよい)
- 単純な条件を先に書く。ifとelseが同じ画面に表示されるので見やすい。
- 関心を引く条件や目立つ条件を先に書く。
- 文字列に”hello”を含む場合とそうじゃない場合に分けて処理する場合、一番の関心は”hello”という条件。
- だから
if(string.contains(“hello”)){ ・・・} else{・・・}
とする。
- だから
- 否定形の条件でも、関心や注意を引く場合がある。そういう場合は否定形で扱って良い。
- 文字列に”hello”を含む場合とそうじゃない場合に分けて処理する場合、一番の関心は”hello”という条件。
三項演算子
鍵となる考えは「行数を短くするよりも、他の人が理解するのにかかる時間を短くする」
三項演算子を使う前に、if文を使うことを考える。
三項演算子を使って読みやすくなるのは、単純な2つの値から1つ選ぶようなものの場合だけ。
三項演算子が読みやすくて簡潔な例は「 time_str += (hour >= 12) ? “pm”: “am”;」
→ これをわざわざif文で書くと冗長に感じる。
三項演算子でダメな例は、return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);
のような複雑なもの。これは if文を使ったほうが自然だし分かりやすくなる。
do/while は避ける
do/while文の特殊なポイントは、コードブロックを再実行する条件が下にあること。コードは上から下へ読むので、すこし不自然。
→ whileにすれば、コードブロックを読む前に繰り返し条件が分かるので読みやすくなる。
do/whileはwhileで書き直せることが多い。
だが、コードを重複させてまでdo/whileを消すのはよくない。つまり、
do{ xxx(); }while(条件);
これを、
xxx(); while(条件){ xxx(); //2回目 }
と書くのはよくない。
関数から早く返す
関数で複数のreturnを使うのはダメと思っている人がいるが、それは間違い。早く返すのはいいこと。
クリーンアップコードを確実に実行したいときは、Javaでいうtry-finallyやC++でいうデストラクタを使うべし。
ネストは浅くする
ネストが深いと、読みては「精神的スタック」に条件をプッシュしなくてはならない。
if(user_result == SUCCESS){ reply.writeErrors(“”); }else{ reply.writeErrors(user_result); }
これに新しくコードを追加しようとして、↓のようになってしまうことが多くある。
if(user_result == SUCCESS){ if(permission_result != SUCCESS){ reply.writeErrors(“Error reading permissions”); reply.done(); return; } reply.writeErrors(“”); ・・・
このコード追加は間違っていない。だが、初見の人にとっては追いづらくなる。
→ 鍵となる考えは「変更するときはコードを新鮮な目で見る。一歩下がって全体を見る。」
早めに返してネストを削除する。
→失敗ケースをできるだけ早く返す。Fail Fastという考え方。
if(user_result != SUCCESS){ reply.writeErrors(user_result); reply.done(); return; } if(permission_result != SUCCESS){ reply.writeErrors(“Error reading permissions”); reply.done(); return; } reply.writeErrors(“”); reply.done();
これでネストの深さレベルが2から1になる。
また、ループ内のネストを削除する場合は、continueを使うこと。
8.巨大な式を分割する
鍵となる考え方は「巨大な式は飲み込みやすい大きさに分割する」
説明変数を使う
説明変数=式を表す変数。
if(line.split(“:”)[0].strip() == “root”){・・・}
↓
userName = line.split(“;”)[0].strip(); if(userName == “root”){・・・}
要約変数を使う
要約変数とは、大きなコードの塊を小さな名前に置き換えて管理や把握を簡単にする変数。
if(reques.user.id == document.owner_id){ //ユーザはこの文書を編集できる } ・・・ if(reques.user.id != document.owner_id){ //文書は読み取り専用 }
このコードのreques.user.id == document.owner_id
は、それほど大きくないが、変数が5つも入っているので考えるのに少し時間がかかる。
このコードが言いたいのは「ユーザは文書を所持しているか?」なので、要約変数を追加すると、
final boolean user_owns_document = (reques.user.id == document.owner_id); if(user_owns_document){ //ユーザはこの文書を編集できる } if(!user_owns_document){ //ユーザはこの文書を編集できる }
ド・モルガンの法則を使う
- not (a || b || c) <=> (not a) && (not b) && (not c)
- not (a && b && c) <=> (not a) || (not b) || (not c)
9.変数と読みやすさ
- 変数が多いと変数の追跡が難しくなる
- 変数のスコープが大きいと、スコープを把握する時間が長くなる
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる
変数を削除する
コードが読みやすくならない変数(役に立たない変数)なら削除したほうがよい。
役に立たない変数とは、
- 複雑な式を分割していない
- より明確になっていない
- 一度しか使っていないので、重複の削除になっていない
例:
now = datetime.datetime.now(); root_message.last_view_time = now;
→このnowは役に立たない変数。
- 中間結果を保持するだけの変数を削除する
- 制御フロー変数(プログラムの実行を制御するだけで実際のプログラムに関係のあるデータが含まれていない変数)を削除する
while( /*条件*/ && !done){ ・・・ if(・・・){ done = true; continue; } }
これの doneは制御フロー変数。↓のようにかける。
while(/*条件*/){ ・・・ if(・・・){ break; } }
もしbreakが使えないようなネストが何段階もあるループは、ループの内部もしくはループ全体を新しい関数にしてしまうのがよい。
変数のスコープを縮める
鍵となる考えは「変数のことが見えるコード行数をできるだけ減らす」。
その理由は、一度に考えなければいけない変数を減らせるから。
* グローバル変数は避ける。
* アクセスできるスコープはなるべく狭める(Javaのアクセサなど)。これにより変数が見える範囲をへらす。
* 大きなクラスを小さなクラスに分割する。ただし、分割後のクラスが互いに参照しあっていたら意味はない。
変数は一度だけ書き込む
「生きている」変数が多いとコードが理解しづらい。
それよりも理解しづらいのは変数が絶えず変更され続けること。値の追跡難易度が格段に上がってしまう。
だから、定数(C++のconstや、Javaのfinal)が使えるような場面では必ず定数を使う。
→ 永続的に変更されない変数は扱いやすい。イミュータブルはトラブルになる傾向が少ない(by ジェームズ・ゴスリン)
定数ではない変数は、変更する箇所をできるだけ少なくする。
10. 無関係の下位問題を抽出する
エンジニアリングとは、大きな問題を小さな問題に分割し、それぞれの解決策を組み立てること。この原則をコードに当てはめる。 1. 関数やコードブロックを見て「このコードの高レベルの目的は何か?」と自問する。 2. コードの各行に対して「高レベルの目標に直接効果があるのか?」あるいは、無関係の下位問題を解決しているのか?」を自問する。 3. 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする。
findClosestLocation()の例
「与えられた地点から最も近い場所を見つける」ことがこのコードの目的
var findClosestLocation = function(lat, lng, array){ var closest; var closest_dist = Number.MAX_VALUE; for(var i=0; i<array.length; i+=1){ //2地点をラジアンに変換する var lat_rad = radians(lat); var lng_rad = radians(lng); var lat2_rad = radians(array[i].latitude); var lng2_rad = radians(array[i].longitude); //球面三角法の第二余弦定理を使う var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) + Math.cos(lat_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng_rad)); if(dist < closest_dist){ closest = arrat[i]; closest_dist = dist; } } return cosest; };
ループ内のコードは無関係の下位問題を扱っている。
→ 「2つの地点(緯度経度)の球面距離を算出する」である。
そもそもこのコードの目的は、「与えられた地点から最も近い場所を見つける」こと。
「2つの地点(緯度経度)の球面距離を算出する」は、この高レベルの目的とは独立している。
したがって、この部分を新しい関数 spherical_distance() に抽出する。
var spherical_distance = function(lat1, lng1, lat2, lng2){ var lat_rad = radians(lat); var lng_rad = radians(lng); var lat2_rad = radians(array[i].latitude); var lng2_rad = radians(array[i].longitude); //球面三角法の第二余弦定理を使う var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) + Math.cos(lat_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng_rad)); };
として、
var findClosestLocation = function(lat, lng, array){ var closest; var closest_dist = Number.MAX_VALUE; for(var i=0; i<array.length; i+=1){ var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longitude); if(dist < closest_dist){ closest = arrat[i]; closest_dist = dist; } } return cosest; };
難しそうな幾何学の計算に心を奪われることなく、高レベルの目標に集中できるようになった。
既存のインタフェースを簡潔にする
きれいなインタフェースを提供するライブラリは美しい。
引数は少なく、事前設定も必要なく、面倒なことをしなくても使えるライブラリ。
インターフェースがきれいだと、コードが優雅に見える。簡潔で、しかも強力になる。
インタフェースがきれいじゃなくても、自分でラップ関数を作れる。
自分でラッパー関数を用意することで、手も足も出ない汚いインタフェースを覆い隠す。
ここまでをまとめると・・・
プロジェクト固有のコードから、汎用的なコードを分離することが望ましい。
ほとんどのコードは汎用化できる。
一般的な問題を解決するライブラリやヘルパー関数を作っていけば、プログラムに固有の小さな核だけが残る。
11.一度にひとつのことを
鍵となる考えは、「コードは1つずつタスクを行うようにしなければならない。」
そのための手順は、
- コードが行っている「タスク」をすべて列挙する。この「タスク」とは粒度が大きいものも小さいものも、ごっちゃでよい。
- タスクをできるだけ異なる関数に分割する。少なくとも異なる領域(段落)に分割する。
12.コードに思いを込める
「おばあちゃんがわかるように説明できなければ、本当に理解したとは言えない」 - アルバート・アインシュタイン
ソースコードは、プログラムの動作を説明する最も大切な手段。だから、コードも「簡単な言葉で」書くべき。
コードを明確にする手順は以下のとおり。
1. コードの動作を簡単な言葉で同僚にもわかるように説明する
2. その説明の中で使っているキーワードやフレーズに注目する。キーワードやフレーズは、下位問題の可能性が高い。⇒関数などに分割できる。
3. その説明に合わせてコードを書く
問題や設計をうまく言葉で説明できないのであれば、何を見落としているか、詳細が明確になっていないということ。
つまり、プログラム(あるいは自分の考え)を言葉にすることで明確な形になる。
13.短いコードを書く
鍵となる考え「最も読みやすいコードは、何も書かれていないコードである」
その機能の実装について悩まないで
プロジェクトでこれから実装しようとしている機能は、実はいらない機能(まったく使われない機能)だったり、アプリケーションを複雑にするものになってしまう。
プログラマは実装にかかる労力を過小評価するもの。
楽観的に工数を見積もったり、将来必要になる保守や文書化などの負担時間を忘れる。
質問と要求の分割
例1:任意のユーザの緯度経度に対して、最も近い店舗を検索するシステムを作ろうとする。
これを100%正しく実装するには、
- 日付変更線をまたいでいるときの処理
- 北極や南極に近いときの処理
- 1マイルあたりの軽度に対応した地球の曲率の調整
などを考慮しないといけない。
これを真面目に作ると、相当量のコードになる。
しかし、実際の問題は「テキサス州にある30軒の店舗だけを扱う」ものだったりする。
この場合は、上記の3つは要求から削除すればいい。
コードを小さく保つ
プロジェクトが進むとともにコードが大きくなる。
それに伴って、それらを結合させる労力が大きくなる。これが宇宙の自然法則。
コードをできるだけ小さく軽量に維持するためにしなければいけないことは、
- 汎用的なユーティリティコードを作り、重複コードを削除する(無関係の下位問題を抽出する)
- 未使用コードや無用の機能を削除する
- プロジェクトをサブプロジェクトに分割する
- コードの「重量」を意識する。軽量で機敏にしておく
身近なライブラリに親しむ
プログラマは既存ライブラリでできることを忘れていたり、そもそも知らないことが多い。
たまには標準ライブラリのすべての関数・モジュール・型の名前を15分かけて読んでみよう!
14.テストと読みやすさ
テストコードを読みやすくするのは、テスト以外のコードを読みやすくするのと同じくらい大切なこと。
鍵となる考え「他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする」
テストコードが読みにくいと、以下のような悲劇が起こる
- 本物のコードを修正するのを恐れる
- 「うへえ、このコードには手を出したくないなあ。テストを変更するなんて悪夢だよ」
- 新しいコードを書いたときにテストを追加しなくなる
1つの機能に複数のテストを作る
コードを検証する「完ぺき」な入力値を1つ作るのではなく、小さなテストを複数作るほうが、簡単で、効果的で読みやすい。
テストに優しい開発
テストしやすいコードには明確なインタフェースがある。
状態や「セットアップ」がない。検証するデータが隠されていない。
⇒ あとでテストコードを書くつもりでコードを書くと、「テストしやすいようにコードを設計するようになる」!!
このようにコードを書くと、良いコードが書ける。
テスト容易性の低いコードの特性とそこから生じる設計の問題
グローバル変数を使っている。 → グローバルの状態をテストごとに初期化する必要がある(そうしないとテスト同士が干渉してしまうため)。
テスト容易性の高いコードの特性とそこから生じる設計の利点
- クラスが小さい。あるいは内部状態を持たない。
- クラスや関数が1つのことをしている
- クラスが他のクラスへの依存度が低い(疎結合)
- 関数は単純で、インタフェースが明確