『リーダブルコード』を現場で読み解く! 開発スピードを向上させる、読みやすいコードの書き方【今こそ読み解きたい名著】
圧倒的名著として知られる『リーダブルコード』ですが、高い評価には理由がある!現役エンジニアが、この一冊を読み込み、変数名の命名法など、読みやすいコードを書く極意とその必要性を抽出します。
アプリエンジニアの池田惇(@jun_ikd)です。 エンジニア向け名著を読み解いていく当企画、今回は『リーダブルコード』 (Dustin Boswell、Trevor Foucher著、角征典訳 オライリー・ジャパン、2012年)から良いコードを書くテクニックを学びます。
リーダブルコード(以下、本書)は、より良いコードを書くためのテクニックがまとめられた一冊です。本稿では、良いコードとは何かについて取り上げ、いくつかの具体的なテクニックや私の経験談を紹介します。すでに実践している方が多いかもしれませんが、コードレビューで他人に伝えるときなどに裏付けとして本書を学んでおくことは価値があると思います。
良いコードとは何か
良いコードとは何かを考えるため、逆に悪いコードとは何か、を考えてみましょう。本書にも悪いコードは以下のように例示されています。
// 与えられた緯度経度に最も近い’array’の要素を返す。 // 地球が完全な球体であることを前提としている。 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 = array[i]; closest_dist = dist; } } return closest; };
おそらく多くの方が、以下のように感じるのではないでしょうか。
- 読みづらい
- 関数が長く、変数も多い。
- どう動いているかわからない
- 複数の計算が1つの関数内で行われている。
- 見た瞬間うんざりする
- 見慣れない公式も登場し、読んで理解するコストが高く感じる。
こうした「悪いコード」に対して、本書では「良いコード」の目的を以下のように述べています。
本書の目的は読みやすいコードを書くことである。その中心となるのは、コードは理解しやすくなければいけないという考えだ。具体的に言えば、誰かが君のコードを読んで理解する時間を最短にするということだ。
先ほど挙げた悪いコードの対極にあたるのが、本書の掲げる「読みやすいコード」です。自分以外の人も理解しやすいコードを書くことでチームの開発効率が上がり、バグも少なくなるでしょう。
良いコードとチーム開発
では、自分以外がコードを理解できているのはどんな状態でしょうか。単に「読めればいい」というわけではありません。
「コードを理解する」というのは、変更を加えたりバグを見つけたりできるという意味だ。他のコードと連携する方法も理解しておかなければいけない。
チーム開発では他人が書いたコードを改修することも多く、ソフトウェアは全体が連携して動作します。ですので、手を加えられる状態であることが重要と言えるでしょう。
事業の継続性に関わる
これは私の考えですが、コードの質と事業の継続性には深い関係があると思います。
開発が進むにつれてコード量は増大してきます。うまくコントロールしないと扱いづらくなり、プロダクトの開発スピードが低下します。ちょっとした機能追加に多くの時間がかかってしまい、実装する頃には競合サービスに追い抜かれてしまうかもしれません。
本書でも以下のように述べられています。
プロジェクトは巨大になって、すべてを把握できる人は誰もいなくなる。新しい機能を追加するのが苦痛になってくる。コードを扱うのが厄介になって楽しくなくなる。
(中略)
プロジェクトが成長しても、コードをできるだけ小さく軽量に維持するしかない。
事業を継続し、プロダクトの成長を持続させるためには、本書で言われているように、できるだけ小さく軽量な、良いコードが必要なのではないでしょうか。
ここからは本の中で取り上げられている理解しやすいコードを書くためのポイントと実際の例をいくつか紹介します。代表的なテクニックを見ていきましょう。
変数と理解しやすさ
まずは変数を取り上げます。すぐに取り組める内容なので、初めにやってみるといいでしょう。
変数名に具体的な情報を込める
変数名にどんな英単語を使うべきか悩むケースは少なくないはず。本書では使う単語を工夫し、分かりやすい名称にするコツを紹介しています。
例えば、「get」はあまり明確な単語ではない。
def GetPage(url);「get」という単語からは何も伝わってこない。このメソッドはページをどこから取ってくるのだろう? ローカルキャッシュから? データベースから? インターネットから? インターネットから取ってくるのであれば、FetchPage()やDownloadPage()のほうが明確だ。
意味がはっきり通る単語を変数名に使うだけで、コードを読む人へのメッセージになるのです。
本書では、より伝わりやすい単語の代替案として以下の例が挙げられています。
例えば表中のstartを代替案に置き換えると、私の感覚では以下のようなイメージを持てます。
- launch
- 起動する、新たに立ち上げる
例) アプリケーションの起動 - create
- 今存在しないものを作り出す
例) データやオブジェクトの生成 - begin
- 繰り返しや終わりのある処理が始まる
例) タイマー処理の開始 - open
- 新たにオブジェクトを利用する
例) ファイルやDBの利用開始
このように、より具体性の高い単語を使うことで名前から得られる情報量を増やすことができます。
省略形を使う
例えば変数名でHyperTextMarkupLanguageと書くことはなく、HTMLという省略形を用いると思います。このような省略形を用いるかどうか、いかに判断するべきでしょうか。
ぼくたちの経験からすると、プロジェクト固有の省略形はダメだ。
(中略)
新しいチームメイトはその名前の意味を理解できるだろうか? 理解できるなら問題ない。
プログラマは、evaluationの代わりにevalを使う。documentの代わりにdocを使う。stringの代わりにstrを使う。だから、新しいチームメイトもFormatStr()の意味は理解できる。でも、BEManagerの意味は理解できない。
指針として、プロジェクト外でも広く使われている省略形は使い、プロジェクト独自の省略形は避けるべきと述べられています。
自分のチームやプロダクトで当たり前に使っている省略形だとしても、新しいメンバーが加わった際に理解できるか考えてみるのが良いと思います。例えば、組織名や社内技術等の略称は新メンバーには理解できません。多用せずに最小限にとどめておくのが良いでしょう。
変数のスコープを縮める
変数のスコープも小さいほど良いとされています。
変数のことが見えるコード行数をできるだけ減らす。
(中略)
アクセスはできるだけ制限して、変数のことが「見えてしまう」コードを減らすのがいいとされている。
では、なぜそうするのがいいとされているのだろう? それは、一度に考えなければいけない変数を減らせるからだ。
クラスのメンバ変数の例を引用します。まず、悪い例です。
class LargeClass { string str_; void Method1() { str_ = …; Method2(); } void Method2() { // str_ を使っている } // str_ を使っていないメソッドがたくさんある }
メンバ変数str_
はクラス内のどこからでもアクセスできます。しかし、実際に使用しているのはMethod1()
とMethod2()
のみです。
では、以下はどうでしょうか。
class LargeClass { void Method1() { string str = …; Method2(str); } void Method2(string str) { // str を使っている } // その他のメソッドはstrが見えない。 }
Method1
にローカル変数としてstr
を定義し、Method2
には引数として渡す形になりました。その他のメソッドからはstr
が見えないため、前の例よりも変数のスコープを縮めることができています。
式と関数
次に、式や関数についてです。同じ言語であっても、プログラムの記述にはさまざまな方法があります。しかし、だからといってメンバーごとに違った書き方をしてしまうと、理解しにくいコードになってしまいます。
例えば、WebAPIを使ってデータを取得するなど、1つのアプリケーション内で似たような処理が複数箇所あるとします。各箇所で違った書き方をされてしまうと、処理が似ていることに気づきにくく、理解しにくいコードになってしまいます。そのため、個人によってバラバラにせずにチームで統一できるといいでしょう。
ここでは、記述の統一と関数の長さについてとりあげます。
式の左側に変化するもの、右側にあまり変化しないものを入れる
条件式を書く時、左右をどのように決めれば良いのでしょうか。
以下の2つのコードはどちらが読みやすいだろうか。
if (length >= 10)または、
if (10 <= length)ほとんどのプログラマは最初のほうが読みやすいと言うだろう。
本書では、左側は調査対象であり変化するもの、右側はあまり変化しない比較対象と指針が示されています。上の例示ではlength
は変数なので変化します。しかし 10
は単なる値なので変化しません。このことから、lengthを左側にしている最初の例が読みやすく感じるのです。
この原則は多くのエンジニアに自然に身についており、間違える回数は少ないと思います。だからこそ、逆にしてしまうと目立ってしまうので、コードを読み解く集中力を奪ってしまうと感じます。
三項演算子の使い分け
三項演算子の使用には複数の意見があります。読みづらくなるため一切禁止するという意見や、短く書けるため積極的に使うべきという意見です。一方、本書ではバランスをとって使用することが推奨されています。基本的にはif/elseを使い、三項演算子はそれによって簡潔になるときだけ使うというルールです。
例を挙げます。まず、三項演算子によって読みやすくなる例です。
三項演算子が読みやすくて簡潔な例を挙げよう。
time_str += (hour >= 12) ? "pm" : "am";三項演算子を使わないと以下のようになる。
if (hour >= 12) { time_str += "pm" } else { time_str += "am" }
次に、三項演算子が不向きな例です。
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);これは、単純な2つの値から1つを選ぶようなものではない。
(中略)
if/else文を使えば、コードがより自然になる。if (exponent >= 0) { return mantissa * (1 << exponent); } else { return mantissa / (1 << -exponent); }
ここで重要なのは、行数の短さが簡潔さではないということです。行数を短くするよりも、他の人が理解するのにかかる時間を短くすることが重要だと述べられています。指針に基いて適切に利用することで、無駄な記述を削減しつつ理解しやすいコードにできると思います。
関数の分割
一般的に大きすぎる関数は望ましくなく、小さな関数に分割すべきとされています。この理由となる記述を引用します。
一度に複数のことをするコードは理解しにくい。例えば、オブジェクトを生成して、データをキレイにして、入力をパースして、ビジネスロジックを適用しているようなコードだ。これらのコードがすべて絡み合っていると、「タスク」が個別に完結しているコードよりも理解するのが難しい。
鍵となる考え
コードは1つずつタスクを行うようにしなければいけない。
関数が大きくなるほど一度に複数の処理を実行していることが多いと思います。複数の課題を同時に処理する。もしくは単一の課題を処理する。両者を比較した際、より簡単なのはいうまでもなく後者です。つまり、1つの処理ごとに適切に分割することで、全体を理解するまでのスピードを早めることができます。さらに関数名も適切に命名されていれば、関数内の処理を読まずとも概要を知ることができると思います。
正しさよりも一貫性が大切
言語や環境によってスタンダードが異なるものや、個人の好みで決まったルールがあります。例を挙げます。
最終的には個人の好みになってしまうこともある。例えば、クラス定義の開き括弧の位置がそうだ。
class Logger { … };または、
class Logger { … }どちらを選んだとしても、コードの読みやすさに大きな影響はない。でも、この2つのスタイルを混ぜてしまうと、すごく読みにくいものになってしまう。
チームを異動した時に改行位置・インデント・メンバ変数のプレフィックスなど、違和感を持つかもしれません。本書では一貫性のあるスタイルは「正しい」スタイルよりも大切であるとされています。間違っていると感じても、プロジェクト内で一貫性を保つことを心がけましょう。
実際のプロジェクトで『リーダブルコード』をどう活かすか
ここまで、本書で述べられているテクニックを挙げてきました。
私が実際のプロジェクトでどのように取り組んできたか、筆者の経験談から簡単に紹介します。
全体のスタイル修正は他の開発と混ぜずに行う
インデントや改行の位置など、ソースコード全体のスタイルをどうしても変えたい場合があります。その時は、開発の区切りがいいタイミングで行います。具体的にはプルリクエストの個数が少ないタイミングを狙って実施すると良いでしょう。
スタイルの修正のみをコミットし、他の開発は混ぜないようにします。新しいスタイルもすぐにマージしてもらえるように、あらかじめチームで合意しておく必要があるでしょう。
デフォルトに沿わない判断
Androidの公式サンプル等では、メンバ変数にm
プレフィックスを付けています。私が新規にAndroidプロジェクトを立ち上げる際にはこのプレフィックスは付けないことにしました。
使用したいライブラリとプレフィックスの相性が悪く、AndroidStudioなどIDEを使っていればメンバ変数を色分けしてくれるので、プレフィックスなしでも十分に識別できると考えたからです。
このように、きちんとした理由があれば、必ずしもデフォルトに沿う必要はないと考えています。新しいメンバーが加わった際にも理由を説明できればOKなのです。
最悪なコードを修正しても最高にはならない
残念ながら、見た瞬間にうんざりしてしまうようなコードがあります。なぜこんな実装になっているのか実装者もよく分かっておらず、誰も触れないようなコードです。
このようなコードは、既存の問題を分析することばかりに時間がかかるため、修正しても良いコードにすることは難しいのです。
そんなときは既存のコードを捨てて、思い切って新たに作り直しましょう。良いコードを作ることだけに集中できるので、修正するよりも速い場合が多いです。その際、必ずテストコードを書いて品質を保証できるようにします。
関連リンク
codicはネーミングを助けてくれるツールです。日本語文章を入力すると変数名を生成するなどの機能があります。特に英語が苦手だと、複数の単語を使った変数名で語順を間違えることがあります。このようなツールを使うと、英語として正しい語順にすることができます。
「変数名に具体的な情報を込める」の項で述べた、類語への変更も簡単にできます。
おわりに
リーダブルコードで押さえたいポイントを挙げ、実際のプロジェクトでの経験談を紹介しました。良いコードを書くスキルを身につけるには時間がかかるかもしれません。コードレビューなど粘り強く取り組み、少しずつスキルアップしていきましょう。
著者プロフィール
池田 惇(いけだ・じゅん)@jun_ikd
【今こそ読み解きたい名著】バックナンバー
編集:薄井千春(ZINE)