はじめに
こちらは著書「リーダブルコード」の学習記録です。
忙しい人のためにも、重要そうな部分を抜粋してまとめました。
2章 名前に情報を詰め込む。
明確な単語を選ぶ
例えば、「get」はあまり明確な単語ではない
def GetPage(url):
....
このメソッドはどこからページを取ってくるのか?
ローカルキャッシュから?データベースから?
もしインターネットから取ってくるならば、DownloadPage()
の方が明確だ。
tmpなどの汎用的な名前を避ける
エンティティの値や目的を表した名前を選ぼう。
(例)tmp
2つの変数を入れ替える古典的な例です。
if(right < left) {
tmp = right;
right = left;
left = tmp;
}
この場合は、情報の一時的な保管のため、tmpという名前で全く問題ない。tmpという名前で「この変数には他に役割がない」という明確な意味を伝えている。
つまり、他の関数に渡されたり、何度も書き換えられたりはしない。
ただし、以下のtmp、テメーはダメだ。
String tmp = user.name();
tmp += " " + user.phone_number();
tmp += " " + user.email();
...
tmplate.set("user_info", tmp);
この変数にとって一番大切なことは、「一時的な保管」ではない。これをわかりやすくするには、user_info
のような名前に変えたほうが良いだろう。
名前の長さを決める
以下のような識別子は、誰もが嫌がるだろう。
newNavigationControllerWrappingViewControllerForDataSourceOfClass
どこの世界線の呪文詠唱だ。詠唱破棄したくなるじゃないか。
では、どれくらいの長さが適切なのか?それは、変数の使い方によって違ってくる。
スコープが小さければ短い名前でもいい
識別子の「スコープ」(その名前が「見える」コードの行数)が小さければ、多くの情報を詰め込む必要はない。
すべての情報(変数の型・初期値・破棄方法など)が見えるので、変数の名前は短くていい。
if(debug) {
map<string,int> m;
LookUpNamesNumbers(&m);
Print(m);
ただし上記のmがクラスのメンバ変数やグローバル変数だった場合、mの型や目的がわからなくなるだろう。
識別子のスコープが大きければ、名前に十分な情報を詰め込んで明確にする必要がある。
3章 誤解されない名前
filter()
データベースの問い合わせ結果を処理するコードを書いてるとしよう。
results = Database.all_objects.filter("year <= 2011")
このresultsには何が含まれているだろうか?
- 「year <= 2011」のオブジェクト
- 「year <= 2011」ではないオブジェクト
どちらかよくわからないのは、filter
が曖昧な言葉だからだ。これでは「選択」するのか、「除外」するのかわからない。
「選択」するのであれば、select()
にしたほうがいい。「除外」するのであれば、exclude()
にした方がいい。
ブール値の名前
ブール値の変数やブール値を返す関数の名前を選ぶときには、trueとfalseの意味を明確にしなければならない。
以下は危険な例だ。
bool read_password = true;
「read」をどう「読む」かになるけど、これには2つの解釈の仕方がある。
- パスワードをこれから読み取る必要がある
- パスワードをすでに読み取っている
代わりに、need_password
やuser_is_authenticated
などを使った方がいい。
ブール値の変数名は、頭にis・has・can・shouldなどをつけてわかりやすくする場合が多い。
それから、名前は否定形よりも肯定形にした方が声に出して読みやすい。
// 否定形
bool disable_ssl = false;
// 肯定形
bool use_ssl = true;
4章 美しさ
なぜ美しさが大切なのか?
以下のようなコードを使わなければならないとしよう。
class:class StatsKeeper {
public:
// doubleを記録するクラス
void Add(double d); //と素早く統計を出すメソッド
private: int count; /*それまでの個数*/
public:
double Average();
private: double minimum;
list<double>
past_items
;double maximum;
};
これを理解するのには時間がかかると思う。では以下のキレイなバージョンならどうだろう。
class StatsKeeper {
public:
void Add(double d);
double Average();
private:
list<double> past_items
int count;
double minimum;
double maximum;
};
見た目が美しいコードの方が使いやすいのは明らかだ。
一貫性と意味のある並び
コードの並びがコードの正しさに影響を及ぼすことは少ない。
details = request.POST.get('details')
location = request.POST.get('location')
phone = request.POST.get('phone')
email = request.POST.get('email')
url = request.POST.get('url')
であれば、ランダムに並べるのではなく、意味のある順番に並べると良い。
- 対応するHTMLフォームのinputフィールドと同じ並び順にする
- 「最重要」なものから重要度順に並べる
- アルファベット順に並べる
重要なのは、「正しさ」よりも「一貫性」
5章 コメントすべきことを知る
コメントの目的は、書き手の意図を読み手に知らせることである。
コメントするべきでは「ない」こと
コードを読んですぐわかることはコメントに書かない。
// Accountクラスの定義
class Account {
public:
// コンストラクタ
Account();
// profitに新しい値を設定する
void SetProfit(double profit);
// このAccountからprofitを返す
double GetProfit();
};
自分の考えを記録する
それでは、何をコメントすべきなのか?
優れたコメントとは、「考えを記録する」ためのものである。
コードを書いているときに持っている「大切な考え」のことだ。
コードの欠陥にコメントをつける
コードが未完成の時は、例えば以下のように書いておくと良い
// TODO: JPEG以外のフォーマットに対応する
記法 | 典型的な意味 |
---|---|
TODO: | あとで手をつける |
FIXME: | 既知の不具合があるコード |
HACK: | あまりキレイじゃない解決策 |
XXX: | 危険! 大きな問題がある |
大切なのは、これからコードをどうしたいかを自由にコメントに書くこと。コードの品質や状態を知らせたり、改善の方向性を示したりできる。
読み手の立場となって考える
「全体像」のコメント
新しいチームメンバーにとって、最も難しいのは「全体像」の理解である。
データはどのようにシステムを流れているのかなど、システムを設計した人は、コメントを書かないことが多い。
あまりにも密接にシステムに関わり過ぎているからだ。
そこでファイルやクラスには、全体像に関するコメントを書くと良い。
// (例)
// このファイルには、ファイルシステムに関する便利なインタフェースを提供
// するヘルパー関数が含まれています。ファイルのパーミッションなどを扱います。
要約コメント
関数の内部でも、「全体像」についてコメントするのは良い考えだ。
# 顧客が自分で購入した商品を検索する
for customer_id in all_customers:
for sale in all_sales[customer_id].sales:
if sale.recipient == customer_id:
...
しかし、コメントがないとコードの途中で意味がわからなくなる(「all_customers」をイテレートするのは、何のために?)
以下のように関数の処理をまとめれば、詳細を調べなくても関数の概要が把握しやすい。
def GenerateUserReport():
# このユーザーのロックを獲得する
...
# ユーザーの情報をDBから読み込む
...
# 情報をファイルに書き出す
...
# このユーザーのロックを解放する
...
6章 コメントは正確で簡潔に
曖昧な代名詞を避ける
「それ」や「これ」が何を指しているのかよくわからないことがある。以下はその例。
// データをキャッシュに入れる。ただし、先にそのサイズをチェックする。
「その」が指しているのは「データ」かもしれないし「キャッシュ」かもしれない。
紛らわしいのであれば、名詞を代名詞に代入してみるといい。
// データをキャッシュに入れる。ただし、先にデータのサイズをチェックする。
あるいは、文章全体を書き換えて「それ」を明確にすることもできる。
// データが十分に小さければ、それをキャッシュに入れる。
関数の動作を正確に記述する
ファイルの行数を数える関数を書いているとする。
// このファイルに含まれる行数を返す
int Countlines(string filename){ ... }
「行」と言ってもさまざまな意味がある。
- ""(空のファイル)は、0行なのか1行なのか
- "hello"は、0行なのか1行なのか
- "hello¥n"は、1行なのか2行なのか
- "hello¥n world"は、1行なのか2行なのか
最も単純な実装は、改行文字(¥n)を数えるものだ。
この実装に適したコメントは、以下のようになる。
// このファイルに含まれる改行文字('¥n')を数える。
int Countlines(string filename){ ... }
これで改行文字がない場合は、0を返すことがわかる。
コードの意図を書く
以下のようなコードは、コードの動作をそのまま書いてるだけで何も情報を追加していない。
void DisplayProducts(list<Product> products) {
products.sort(CompareProductByPrice);
// listを逆順にイテレートする
for(list<Product>::reverse_iterator it = products.rebigin();
it != products.rend(); ++it;)
DisplayPrice(it->price);
...
}
このコードは直下のコードをそのまま説明しているだけなので、以下のように変えてみる。
// 値段の高い順に表示する
for(list<Product>::reverse_iterator it = products.rebigin(); ...)
このプログラムにはバグがあり、CompareProductByPrice関数が、すでに値段の高い順にソートしている。したがって、このコードは作成者の意図に反したものになっている。
そのため、後者のコメントのほうが優れていることになる。
前者のコメントは技術的には正しい(=逆順にイテレートする)。しかし、後者のコメントのほうが作成者の意図(=値段の高い順に表示する)が、実際のコードと矛盾していることに気づきやすい。
まとめ
- 変数名などには、明確で適切な長さの名前を入れる
- 最善な名前とは、誤解されない名前である
- 一貫性を持つことで、より美しいコードが書ける
- コメントを書くことは、作成者の意図を知らせることにもつながる