どんな本か?
この本の内容を一言でまとめると、いかにして可読性の高い、読みやすいコードを書くかということを書いてある本です。
プログラミングの勉強をしていると、よく目にするのが
- 可読性の高いコードを書くことが重要で~
- 他人が見ても読みやすいコードを書くべき
理解しやすいコードとは?
リーダブルコードでは、わかりやすい、きれいなコードを書く方法を説明する前に「きれいなコードとはどんなコードか?」ということを定義するところから始めています。筆者の考えでは、
他人が最短時間で理解できるコードが良いコードの条件。つまり、この考えこそがこの本の軸となる考え方で、今後の具体的な手法はこの考えを基礎としています。
最短時間で理解できるコードというのは、最も短いコードと等価ではありません。実際に本で紹介されているコードがこちらです。
// 短さに重点を置いたコード | |
assert((!bucket = FindBucket(key))) || !bucket->IsOccupied()); | |
//行数は長いが、より短い時間で理解できるコード | |
bucket = FindBucket(key); | |
if (bucket != NULL) assert(!bucket->IsOccupied()); |
このような技法は、基本的なもので、指摘されなくてもできているような人も多いと思います。ここから第一章が始まり、様々な技法が紹介されます。
変数の名前をよく考える
皆さん、コードを書くときに変数名はどのように決めているでしょうか?変数が多いと考えるのがめんどくさくて「a, b, c」と順番につけてしまったり、サイトでよく使われているような「foo」や「tmp」などをついつい使ってしまうという人もいると思います。
これがいつでも間違いというわけではありません。tmpなどは名前の通り、寿命の短い、一時的な(temporary)変数に使うのであれば問題ありませんし、「a, b, c」でも、連続性が大事な場合にはこれがベストということもあるでしょう。しかし、深い理由もなしに適当に変数の名前を付けてしまうと、コードが読みづらくなったり、バグに気づきにくくなったりします。下に本の中に載っているコードを一つ引用します。
// ループを回す際の変数としてi, j, kを使っている | |
for (int i = 0: i < clubs.size(); i++) | |
for (int j = 0: j < clubs[i].members.size(); j++) | |
for (int k = 0: k < users.size(); k++) | |
if (clubs[i].members[k] == users[j]) | |
cout << "user[" << j << "] is in club[" << i << "]" << endl; | |
// 変数に意味を持たせている | |
for (int club_i = 0: club_i < clubs.size(); club_i++) | |
for (int members_i = 0: members_i < clubs[i].members.size(); members_i++) | |
for (int uses_i = 0: users_i < users.size(); k++) | |
if (clubs[club_i].members[users_i] == users[members_i]) | |
cout << "user[" << members_i << "] is in club[" << clubs_i << "]" << endl; |
ところでこのコードを読んでいて気づいたことはないでしょうか。実はこのコードにはバグが入っています。上のコードでは5行目、下のコードでは13行目です。インデックスの指定が逆になっています。上のコードではこのバグは注意してみないとわかりません。しかし、下のコードだと
members[users_i]と明らかに変なことがわかります。
リファクタリングの方法論
リファクタリングというのは、例えば、ある関数があるときにその中身を読みやすいように整理したり、繰り返し書いている部分をほかの関数として新たに定義してコードを簡潔にしたりする操作のことです。本書で著者がリファクタリングの際に重要だと主張しているのは主に以下の3点です。
- プログラムの主目的と関係のない「無関係の下位問題」を抽出する
- 関数には一度に1つのことをやらせる
- 最初にコードを言葉で説明する。それをもとにしてきれいな解決策を考える
business = Business() | |
business.name = request.POST["name"] | |
url_path_name = business.name.lower() | |
url_path_name = re.sub(r"['\.]", "", url_path_name) | |
url_path_name = re.sub(r"[^a-z0-9]+", "-", url_path_name) | |
url_path_name = url_path_name.strip("-") | |
business,url = "/biz/" + url_path_name | |
business.date_created = datetime.datetime.utnow() | |
business.save_to_database() | |
#無関係の下位問題を抽出 | |
CHARS_TO_REMOVE = re.compile(r"['\.]+") | |
CHARS_TO_DASH = re.compile(r"[^a-z0-9]+") | |
def make_url_friendly(text): | |
text = text.lower() | |
text = CHARS_TO_REMOVE.sub('', text) | |
text = CHARS_TO_DASH.sub('-', text) | |
return text.strip("-") | |
business = Business() | |
business.name = request.POST["name"] | |
business.url = "/biz/" + make_url_friendly(business.name) #コードが簡潔になった | |
business.date_created = datetime.datetime.utnow() | |
business.save_to_database() |
このような抽出作業は、コードを見やすくすること以外にも恩恵をもたらします。例えば、関数として抽出することによって、このコードをほかの場所で再利用することができます。また、独立に関数として存在しているので、コードの改善がしやすくなります。
テストを書く
プログラミングにおける、テストについてどれくらい知っているでしょうか?テストの技法は少なくともプログラミング言語の入門書では扱っていない分野だと思います。テストというと、テスト駆動開発(TDD)を想像する方も知ると思いますが、ここではテスト駆動開発自体については触れられていません。
ここで説明されているのは、どのようにテストコードを書くべきか、ということです。基本的な原則はここまでの章で説明したこととほとんど同じで、
- 一度に一つのことをやる
- 無関係の下位問題を抽出する
まとめ
ここまでで、「リーダブルコード」で紹介されているコードを読みやすくするテクニックのいくつかを紹介してきました。コードの読みやすさは、仲間と共同で大規模な開発を行っているときはもちろん、一人で趣味などで開発をしている人にとっても非常に重要なファクターです。自分が書いたコードでも時間がたてば他人が書いたコードと同じです。本書は、すべてのプログラマーにとって効率よく開発を進めていくための必読の書といえます。