リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)はエンジニアであればほとんどの人が持っている、コードをきれいに書くためのノウハウが詰まっている名著です。
今回はこのリーダブルコードについて実務8年以上経験しているエンジニアの視点から重要なことをまとめていきます。
この記事は是非ブックマークなどをしてコードを書くたびに何度も見返すことをオススメします。
またエンジニアを生業としてやっていくのであればこの本は持っておくことをオススメします。
理解しやすいコードとは?
この本の伝えたことを端的に言うと
「理解しやすいコードを書けるようにするにはどうすればよいか?」ということである。
では理解しやすいコードとは一体どういうコードだろうか?
それは
「他の人が短時間で理解できるように書かれているコード」
と定義しています。
これは今回の記事の中で一番重要なことなので覚えておいてください!
他の人が短時間で理解できるように書かれているコードとは?
これは文章の意味そのまま受け取ってもらえば良いです。
例えば同僚にコードを読んでもらって、そのコードを理解するまでにかかった時間が短ければ短いほど理解しやすいコードということです。
更に「理解しやすい」というワードを深ぼると、
- 変更を加えやすい
- バグをすぐに見つけやすい
- 他のコードと連携するときに容易
という意味。
ではこれを踏まえた上で「理解しやすいコードを書くにはどういうことを意識する必要があるのか」見ていきます。
名前に情報を詰め込む
変数名、関数名、クラス名、型名などプログラムでは独自に名前をつけることが多々あります。
それらの名前には必ず「情報を詰め込むこと」!!
情報を詰め込むこととは?
端的に言うと「その名前を見ただけで、何を表す変数、関数なのかすぐに理解できること」です。
具体的には以下のことに気をつけましょう!
- 明確な単語を選ぶ
- 汎用的な名前を避ける(使う状況を選ぶ)
- 抽象的な名前よりも具体的な名前を使う
- 接尾辞や接頭辞を使って情報を追加する
- 名前の長さを決める
- 名前のフォーマットで情報を伝える
一つ一つ見ていきます。
明確な単語を選ぶ
インターネットからページを取ってくる関数の場合の例
def GetPage(url);
よりも
DownloadPage(url);
の方が明確!
「get」という単語だとこのページはどこから取ってくるのかわからない。ローカルキャッシュ?データベース?インターネット?など様々受け取れる。
なので、インターネットから取得する場合は「download」の方が明確である。
またこれは実務では実践出来ていないプロジェクトの方が多い印象です。
というのも人によってこの辺のさじ加減は様々なのと、
それをちゃんとフィルタリングする仕組みをチームで整えるのは現状出来ているプロジェクトが少ないからだと思います。
ただこの要素は重要なので覚えておきましょう!
せめて上に出した例のような使い分けは出来るようにするのが望ましいです。
関数名に悩んだ時はこちらの記事が関数名のよく使われる単語集になっていてオススメです。
汎用的な名前を避ける(使う状況を選ぶ)
「tmp」「retval」「foo」など汎用的な名前は何を表しているかわからないので避けること!
変数名は目的やその値を意味しているのが望ましい。
function(v) {
var retval = 0;
for (var i = 0; i < 10; i++) {
retval += v[i]*v[i];
}
return retval
}
このコードの「retval」はただ返り値ですという意味しか変数名が表してなく、悪い変数名である。
この場合はvの2乗の合計「sum_squares」というような変数名が良い。
ただ汎用的な名前でも良い場合もある
if(right < left) {
tmp = right;
right = left;
left = tmp;
}
このような場合は「tmp」という汎用的な名前で問題ない。
理由としてはこの変数名は一時的な保管に使われているため。
まとめ
汎用的な名前を使う時はそれ相応の理由があることが重要!
抽象的な名前よりも具体的な名前を使う
任意のTCP/IPポートをサーバがリッスン出来るかを確認するメソッドの場合
ServerCanStart()
よりも
CanListenOnPort()
の方がより具体性があり良い。
接尾辞や接頭辞を使って情報を追加する
名前は短いコメントのようなものです。
絶対に知らせたいことは変数名に含めるべきである。
例えば
string id = 'af84ef845cd8';
のフォーマットが大切ならhex_idという変数名にするのが望ましい。
その場合接尾辞や接頭辞をうまく使うと良い。
状況 | |
passwordはプレインテキストなので処理をする前に暗号化すべき | password よりも plaintext_password |
エスケープ処理がされている単語の場合 | words よりも escaped_words |
例としてはこの表のように使うと良い。
名前の長さを決める
名前は長すぎると、プログラムを書く際のエディター画面を占領してしまい返ってわかりづらくなってしまう。
かといって短すぎると変数に情報がなさすぎるので良い塩梅にしましょう!
例えば
ConvertToStringという関数名はToStringにしても情報の欠損がなく伝わるので、こういう最適化をするのが望ましい。
またスコープが小さい場合は短い変数名でも問題ない。
なぜならスコープが小さい分、理解するのに必要な情報が直ぐ側にあるから、短くても理解に困らないです。
名前のフォーマットで情報を伝える
これは言語によっても多少異なるが、クラス名はアッパーキャメルケース、変数名はキャメルケースなど
フォーマットが言語ごとに予め決められているので、それに習ってフォーマットを変えることで名前がわかりやすくなります。
まとめ
一旦ことでまとめておくと「名前を見ただけで情報を読み取れるようにすること!」こと。
本では、更に詳しく解説されている是非見てみると良いです。
誤解されない名前
名前が他の意味と街がられることは無いか?何度も自問自答したり、同僚に聞いて確認しよう!
filter関数について
データベースの問い合わせ結果を処理するコード書いている場合
results = Database.all_objects.filter("year <= 2011");
これは
- 「year<=2011」のオブジェクト
- 「year<=2011」ではないオブジェクト
どちらを示しているのかわからない。
filterが「選択する」のか「除外するのか」どちらなのかわからない曖昧な言葉だからである。
- 「選択する場合」はselect
- 「除外する場合」はexclude
にするとわかりやすくなる。
限界値を含める時はmin,maxを使う
例えばショッピングカートには商品が10点までしか入らないケースを考えてみよう。
その時のマジックナンバーを
CART_TOO_BIG_LIMIT = 10
この変数名だと
「未満(10を含まない)」なのか「以下(10を含むのか)」はマジックナンバーからだとわからない。
限界値の変数はmax,minを使うとわかりやすくなる。
MAX_ITEMS_IN_CART = 10
こうすることで限界値が10まで(10を含む)ということがマジックナンバーからもわかる。
実際のプロジェクトではここまで意識できているか?
ここもまた難しい点ではあるが出来ている部分もあれば出来ていない部分もあるのが現状。
逆に言えば出来なくてもある程度はプロジェクトでは問題ないとも言えます。
ただこれも細かい話ですが重要なことなので、意識してコードを書くようにするのが良いです!
これらの例以外にも本書には載っているので、エンジニアをちゃんとやるのであれば買って必ず一読しておき、
仕事中にも何度か変数名に詰まったりしたときに読み返すのが良いです。
なぜプログラムコードの美しさが大事なのか?
- 読み手が慣れているパターンと一貫性のあるレイアウトになっている
- 似ているコードは似ているように見せる
- 関連するコードをまとめてブロックにする
ということです。
これは言語にもよりますがある程度リンターで解決出来ます。
例えばGo言語は公式でリンターが作られていて、それに従うと、
- 改行の位置
- イコールの位置
- import文の順番
- スペースの位置
など勝手にリンターが対応してくれます。
なのでこの章はリンターにある程度おまかせするのが良いです。
これ以上は人やプロジェクトのさじ加減になるのでここでは明言しません。
ただ他にも述べられている事があるので、その部分は本書で見てください。
リンターでも対応出来ないことが多々書いてあります。
プログラムコードにコメントすべきこと・すべきでないこと
「コメントの目的は書き手の意図を読み手に知らせること」です。
コードを書いている時は書き手にはたくさんの大切な情報があるが、
他の人がそのコードを読む時はその情報は書かれていない。
コードを読む人が見るのは目の前にあるコードだけです。
コメントを書く際に意識すべきことは以下3つ
- コメントするべきでは「ない」事を知る
- コードを書いているときの自分の考えを記録する
- 読み手の立場になって何が必要になるかを考える
プログラムでコメントするべきではないこと
コメントを書くとその分読む時間が長くなり、また画面を占領してしまうため、なるべく不要なコメントは書かないことです。
ではコメントすべきこととすべきでないことの違いは何か?
「コメントのコメントをしないことである」
例を上げると
// 与えられたsubtreeに含まれるnameとdepthに合致したNodeを見つける
Node* FindNodeInSubtree(Node* subtree, string name, int depth);
これは価値がないコメントです。
シグネチャからsubtreeの中のnodeを見つける関数というのは容易にわかります。
もし仮に上記のようなケースでコメントを入れないとわからない場合は、
名前が悪いケースになるので、名前を変えることをしましょう!
プログラムコードを書いているときの自分の考えを記録する
書いてはいけないコメントはわかったら、次は書いたほうが良いコメントも理解しましょう。
書いたほうが良いコメントは
「コードを書いているときにその情報が無いと、そのコードが理解できない大切な考え」
です。
例えば
- このデータだとハッシュテーブルよりもバイナリツリーの方が40%速かった
- 左右の比較よりもハッシュの計算コストのほうが高い
- このクラスはfatしてきているので、サブクラスを作って整理したほうが良い
- TODOコメント
などです。
これらのコメントはなぜそのようなコードにしたのかと言うのが書かれていて、
読み手はコメントがないとわからない大切な情報です。
読み手の立場になって何が必要になるかを考える
これは上記に書かれた部分と重複しますが、コメント必要かどうかは読み手が決めるものなので、
「常に仕様を理解していない他の人がこのコードを読んだときに、理解できるのか?」
というのを自問自答すると良いです。
プログラムコードのコメントは正確で簡潔に
コメントは画面の量も取られる上、読むのにも時間がかかるので簡潔に書かなければならない。
例えば
「データをキャッシュに入れる。但し先にそのサイズをチェックする」
これだと「そのサイズ」というのがキャッシュを指すのか、データを指すのかわからない。
正しくは
「データをキャッシュに入れる。但し先にデータサイズのチェックをする」
というのが良い。
もう一つ例を出すと
「このファイルに含まれる行数をカウントして返す」
というコメントはあまり良くない。
理由としては
- 空行はカウントするのか?
- “Hello\n”は1行か?2行なのか?
- 改行コードは何でも良いのか?
などが考えられる。
正すとすると
「このファイルに含まれる改行文字(‘\n’)を数える」
この様になる。
こうすると空行も含まれることがわかり、またキャリッジリターン(\r)は含まれない事がわかる。
制御フローを読みやすくする
条件やループなどの制御フローはできるだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く。
if文
if (10 <= length)
よりも
if (length >= 10)
の方が読みやすい。
この書き方は英語の用法にあっている。
英語で「もし君が1年間で10万ドル以上稼ぐならば」や「もし君が18歳以上ならば」というのは自然です。
しかし「もし18年が君の年齢以下ならば」というのは不自然ですね。
まとめると
調査対象の値が左辺に来るのが望ましいです。
またif文は否定形よりも肯定形を使うほうが解釈しやすいです。
三項演算子
if文を使う際単純な場合はif文よりも三項演算子を使うこと。
その方が行数も短く、冗長にならずに済む。
ただし行数を短くする目的で若干複雑なif文を三項演算子にしてしまうと、
他の人が読みづらいコードになってしまうため避けること。
他にも
- goto文はコードの可読性が下がるため避けること
- ネストを浅くする
などのことが書かれています。
そちらも重要なので詳しく例を知りたい方は本書で確認ください。
巨大な式を分割する
巨大な式は飲み込みやすい大きさに分割する
巨大な式を分割するには
- 説明変数を使う
- 要約変数を使う
を使うと良いです。
説明変数とは例えば
if line.split(':')[0].strip() == 'root'
というコードを説明変数を使うと
username = line.split(':')[0].strip()
if username == 'root'
という風に置き換えられる。
コード行数は2行になってしまったが、意味不明だった左辺が変数名をつけることで、
何を表している変数なのか、ぐっと理解しやすくなりました。
要約変数は
final boolean user_owns_document = (request.user.id == document.owner_id)
条件式を変数に要約することで、
どういう条件なのかがパット見て理解できるようになりました。
まとめると
今回紹介した、説明変数、要約変数を使うだけで、かなり見通しは良くなってきます。
他にもここに記載していない内容について触れたい方は本書で確認ください。
変数の読みやすさ
前の章では分かりづらいコードは変数にして説明を付与することを勧めたがここではその逆で、
変数を削除したり、スコープを縮めることで、変数を読みやすくします。
- 役に立たない一時変数を削除
- 変数のスコープを縮める
- 変数は基本的にイミュータブルにする
を行うと大分コードが読みやすくなります。
役に立たない一時変数削除
now = datetime.datetime.now()
root_message.last_view_time = now
このnowという一時変数は説明変数になっていない(説明変数を使わなくてもわかる)ので、
一時変数は使用しないほうがわかりやすくなります。
変数のスコープを縮める
変数を利用する際に、あまりにも遠くに変数が定義してあると、
コードを読む際にまた変数定義を見に行って、読みたいコードに戻って。。。
とかなり可読性が下がる。
できるだけその変数を利用する際に変数を定義すること。
変数は基本イミュータブルにする
同じ変数を操作する場所があちこちにあると、
現在変数の値がわからなくなります。
なるべく変数はイミュータブルにすべきです。
まとめ
- 役に立たない一時変数を削除
- 変数のスコープを縮める
- 変数は基本的にイミュータブルにする
この3つは並のエンジニアであれば出来なければ行けないです。
他にもいくつか触れてないことが本書で書かれています、
この基本的な章はエンジニアとして大事なので抑えておきましょう。
一度に一つのことだけをするように実装する
コードは一つずつタスクを行うようにする
「巨大な式を分割する」という章と少しにていますが、
一つの関数では一つのタスクのみ行うことを心がけましょう。
一つの関数で複数のことをやりすぎると巨大な式になってしまい、可読性落ちたりテストが書きにくくなります。
ではどのように一つの関数で一つのタスクを行うのか?
- コードが行っているタスクを全て列挙する。このタスクという言葉はゆるく使っている。「オブジェクトが妥当かどうかを確認する」のような小さいこともあれば、「ツリーの全てのノードをいてレートする」のように曖昧なこともある
- タスクをできるだけ異なる関数に分割する。少なくとも異なる領域に分割する
この手順に沿って巨大なタスクを分割すると良いです。
例えば
「都市」を選ぶときには「LocationName」→「SubAdministrativeAreaName」→「AdministrativeAreaName」の順番で使用可能なものを使う。
以上の3つが使えない場合は「都市」に「Middle-of-Nowhere」というデフォルト値を設定する。
「国」に「CountryName」が使えない場合は「Planet Earth」というデフォルト値を設定する。
の仕様をそのまま実装すると
var place = location_info["LocationName"];
if (!place) {
place = location_info["SubAdminitrativeAreaName"];
}
if (!place) {
place = location_info["AdministrativeAreaName"];
}
if (!place) {
place = "Middle-of-Nowhere";
}
if (location_info["CountryName"]) {
place += "," + location_info["CountryName"];
} else {
place += ",Planet Earth";
}
のようになる。
これを一つ一つのタスクに分解すると、
- location_infoディクショナリから値を抽出する
- 「都市」の優先順位を調べる。なかったらデフォルトで「Middle-of-Nowhere」にする
- 「国」を取得する。なければ「Place Earth」にする
- placeを更新する
このようにタスクが分解できます。
このタスクごとに関数を実装すれば可読性があがります。
初心者の頃はこの分割が出来ずに大きな関数を作りがちになります。
徐々に分割出来るように癖をつけて日々実装してやるのが良いです。
コードに思いを込める
これは「巨大な式の分割をする」、「一度に一つのことだけをするように実装する」のまとめのような章になる。
つまり
「おばあちゃんでもわかるように説明できなければならない」 by アルベルト・アインシュタイン
プログラムでもこれを体現する必要があるということです。
本書では以下の手順を示している。
- コードの動作を簡単な言葉で同僚にもわかるように説明する
- その説明の中で使っているキーワードやフレーズに注目する
- その説明に合わせてコードを書く
この手順で巨大な式を分割していく。
まとめ
リーダブルコードについて現役中堅エンジニアとして重要な部分をまとめました。
この本をある程度実践体現できるだけで、大分可読性の良いコードを書けるようになります。
また実際この本書の内容を全て体現できるエンジニアは稀有です。
なのでこの本書を体現出来るように、
一度読んで終わりではなく、日々エンジニアとしてコードを書いていく中で、
綺麗に収まるように書く方法がわからなくなったときや、複雑なコードを書く前など
何度も読み返して自分の中に定着していくようにすることをオススメします。