見出し画像

読書レポート #7 『リーダブルコード より良いコードを書くためのシンプルで実践的なテクニック』


----- この本を選んだ背景 -----

エンジニアになって7年目となり、実務経験はある程度積んできたように思う。しかしながら、技術書の類をあまり読めていないのが現状であった。
エンジニアなりたてのときは研修や実務でわからないことがあったら都度Javaのテキストを開き、それでもわからないなら教育担当の上長をお呼びして教えていただいたことを覚えている。
そこで、今回は技術書の中でも汎用性が高く名著と名高い「リーダブルコード」を読み、実務に活きるコードの学びを得ようと思った。

----- 内容 -----

◆優れたコードとは?

優れたコードの大前提は「理解しやすい」ことである。
理解しやすいというのは、教科書に出るような形や単純明快な変数名を用いるなどの工夫を凝らしたコードであろう。バグが起きにくいコードを作るなら文字数の短いコードが良い。しかし、理解しにくい場合は「良い」とは限らない。
自分だけしか使わないものなら動けば何でもいいかもしれないが、プログラマのほぼ全員が複数人で作るシステムに関与しているだろう。そういうときに「読みづらいコード」があるとそのシステムの技術的な足かせにさえなる。

◆表面上の改善

●変数名に情報を詰める

プログラムにとって変数名は大切である。特に初見のコードでは変数名から処理を類推して処理がどうなっているのか理解を深めるから、プログラムの質を左右するといっても過言ではない。
命名のコツは下記のようなものである。

  • 明確な単語を選ぶ

  • 汎用的な名前は避ける

  • 具体的な名前を付ける

  • 接尾辞や接頭辞を使って情報を追加する

  • 名前の長さを決める

  • 名前のフォーマットで情報を伝える

・明確な単語を選ぶ
 変更前:getFile
 変更後:downloadFile
”get"はどのプログラムでも使われるであろう単語であるが、単体だとどこから情報を取得してくるのかわからない。例えばインターネットから情報を取るなら"fetch","download"の方が明確であろう。

・汎用的な名前は避ける
汎用的な変数名として「tmp」は真っ先にあがるだろう。実装途中のプログラムならともかく、一時的な値の置き場所以上の役割があるのに変数名がtmpのままだったら悪手といわざるを得ない。
また、ループイテレータにもこれが当てはまるだろう。"i, j, k"あたりが一般的に用いられるが、反復が入れ子になっている場合は読みづらくなる。これもある程度わかりやすく書き換えるのが良いだろう。

・具体的な名前をつける
プログラム実行時にログに追加情報を出すオプションがあり、そのコマンドが"--run_locally"だとする。初見ではローカルで動かすことはわかっても、その目的までピンとは来ないだろう。目的は追加ログを出すことであるから「--extra_logging」と改名した方が良い。

・接尾辞や接頭辞を使って情報を追加する
プログラムでは時間やパスワードを扱ったりすることが多々ある。
時間は多くの単位が存在するため、時間を扱う場合はそれが何なのかを接尾辞に書いておくと良いだろう。(例:経過秒数: passed_second)
パスワードは変換前と変換後では大きく文字列が異なるため、これを識別するために名前を変えるのが良い。(例:変換前のパスワード:plaintext_password)

・名前の長さを決める
すべての変数を明確かつ具体的に記すと変数名はかなり長くなるだろう。
しかし、利用するスコープが小さい変数ならば長くする必要性は薄い。長期旅行より日帰り旅行の方が荷物は少なくて済むように。具体的な名前を作る上で、思った以上に単語自動補完機能が使える。

・名前のフォーマットで情報を伝える
ここでいうフォーマットは、キャメルケース(camelCase)やスネークケース(snake_case)といった変数名のフォーマットのことである。定数ではスネークケース・変数名はキャメルケースなど、チームの開発方針として決めておくとトラブルは少なくなるだろう。

●誤解されない名前を付ける

「誤解されない」というのは、他の意味と間違えて捉えられない用語ということである。曖昧さや用例を詳細化・具体化する情報を付加することで誤解されないようになる。
定義名の設定のコツとして、変数名の接頭辞を工夫することが挙げられる。

  • 限界値:"max","min"

  • 範囲指定:"first","last"

  • 包含/排他範囲の指定:"begin","end"

  • 論理値:"is","has","can"

●美しさ

優れたソースコードは「目に優しい」のである。
目に優しいことを筆者は「美しい」と表現する。
美しいコードを作るために、3つの原則を用いている。

  1. 読み手が慣れているパターンと、一貫性のあるレイアウトを使う

  2. 似ているコードは似ているように見せる

  3. 関連するコードをまとめてブロックにする

先述の「理解しやすさ」に通ずる部分もあるが、美しさを意識してコードを書くことで理解しやすくなる。
また、副次的効果として追加・改修が容易になる。

コードの並びが影響を与えることは少ないという。
それならば、並びも基準を作って統一した方が良いだろう。
 例
 ・対応する画面の表示アイテム順
 ・アルファベット順

10行以上コードが連続している場合、どうしても見づらくなる。
この場合はコメントと空行を入れて「段落分け」を行うのが良い。
こうすることで、視覚的な踏み石を提供できて理解しやすいコードとなる。

●コメントすべきことを知る

コメントの目的は「書き手の意図を読み手に伝えること」である。
意図を伝えるのはコードの理解しやすさを向上させるためである。理解しやすいさを向上させる情報なら積極的に書いてよい。
伝えるうえで不要なこと、必要なことをこの章で説明する。

・不要なコメント:コードからわかること
適切な変数名や簡潔なロジック・統一感のあるレイアウトが整っていれば、大抵の情報はコードからわかる。優れたコードにコメントはいらない。
逆に、コメントが必要なほどわかりにくいメソッドがあるとするなら、コメントではなくメソッドに欠陥があるといえる。メソッド内の変数名やロジックを修正した方が良いだろう。

・必要なコメント:ロジック実装の背景
メソッド実装の理由や背景を説明すると、仕様が変わったりした際の調査を省ける可能性が高い。
 例
 このデータだと、ハッシュテーブルよりもバイナリツリーの方が40%速かった。左右の比較よりもハッシュ計算のコストが高いようだ。

・必要なコメント:コードの欠陥
接頭辞に"TODO"などを付けると他の開発者にも親切である。
 例
 TODO: JPGだけでなくPNG,GIFアップロードにも対応する

●コメントは正確で簡潔に

不要なコメントはコードの表示領域を狭めるため、コードの理解を妨げることになる。そのため、コメントは領域に対する情報の比率が高い必要がある。一行で説明できるものを数行かけて説明する必要は全くない。
正確で簡潔なコメントのコツをこの章で説明する。

・あいまいな代名詞を避ける
「これ・あれ・それ・どれ」といった代名詞はアンチパターンであろう。
雑に指定した代名詞を使うなら、何を指しているか明示すべきだ。

・婉曲的表現を避け、断定的表現を使う
エンジニアは断定的口調を避けるべきという言葉もある。だがそれは顧客と要件定義や意見交換をする際のふるまい方であり、コメントを書くことに対しては当てはまらない。むしろコメントは断定的表現を意識した方が良い。
 例
 改善前
  これまでにクロールしたURLかによって優先度を変える
 改善後
  これまでにクロールしていないURLの優先度を高める

・関数の動作を正確に表現する
例えば、ファイルの行数を数える関数のコメントを考える。
 改善前
  渡されたファイルに含まれる行数を返す
 改善後
  渡されたファイルに含まれる改行文字(\n)を数える
改善前のコメントの場合、行数を返すことは理解できてもどうやって行数を数えているか・気を付けるべきことは何かまではわからない。しかし、改善後のコメントであれば、空ファイルを渡したら0が返るであろうことも理解できるし、"\r"が無視されることも理解できる。

◆ループとロジックの単純化

この章では、理解しやすいソースコードの書き方について説明する。

●制御フローを読みやすくする

条件やループなどの制御フローはできるだけ自然にすることが求められる。

・条件式の引数の並び順

 if (weight > 100)

 if (100 < height)

一般的にわかりやすいといえるのは、上の行である。
以下の条件式はどうだろうか。

 if (bytes_recieved > bytes_expected)

 if (bytes_expected > bytes_recieved)

これも上の行がわかりやすいといえる。
上の行がわかりやすいのは、英語の用法と一致しているからである。英語では主語→述語の順で表現される。(今回の例でいえば"My weight is 100 lbs.", "Recieved file size is larger than expected size.")したがって、主語を先にして条件式を書くとよい。

・do-whileループを避ける
制御構文としてdo-whileループが存在する言語(Perlなど)がある。
do-whileループは継続条件が最後に記述されているのが特徴である。
ただ、コードは上から下に読むことが一般的であることから、避けるべきパターンといえる。
do-whileループはwhileループで書き直せることが多いため、whileループを利用することが望ましい。

●巨大な式を分割する

何十行も連続して書かれたコードは、体感でも読みづらいと感じるだろう。
連続して書かれたコードを読み解くにはコードに書かれたいくつかの情報を整理ながら読むしかないが、ヒトは一度に大量のことを整理することはできない。最近の研究では、一度に3-4個のものしか考えられないようである。そのため、連続して書かれたコードはいくつかに分けて理解しやすくする必要がある。

要約した変数を使う
条件式に式を直接代入した際、非常に読みづらくなることがある。
こうした際は要約した変数を一行加えると読みやすくなるだろう。

 変更前
 if (line.split(':')[0].strip() == "root") {
  …
 }

 変更後
 userName = line.split(':')[0].strip()
 if (userName == "root") {
  …
 }

また、同じ名前の変数や記述が3回以上用いられるようなことがあった場合、これも要約した変数に格納して利用すると読みやすくなるだろう。

ド・モルガンの法則を使う
ド・モルガンの法則を利用することで論理式をより簡単なものに変更することが可能である。
 例
 条件A・条件B・条件Cのいずれかに該当する、を満たさない
 → 条件Aを満たさない、かつ条件Bを満たさない、かつ条件Cを満たさない

厳密には異なるが、似たような考え方に「対偶」があり、これを利用して論理式をより簡単に変更することも可能である。
 例
 「ファイルが存在し、かつ読み取り制限がない」を満たさない
 →「ファイルが存在しない、または読み取り制限がある」を満たす

●変数と読みやすさ

要約する変数の存在意義は、下記に集約される。

  • 複雑な式を分割する

  • 複雑な式をわかりやすい名前に変換する

  • 何度も使う値や式を一回の定義に集約する

上記の存在意義を意識した変数の扱い方について説明する。

存在意義のない変数は削除する
いずれの存在意義にも該当しない変数は、削除した方がよい。
特に該当しやすいのが、中間結果を保管する変数である。本当に必要な変数もあるだろうが、大抵はロジックの見直しや巨大な式の分割・切り分けメソッドによる外注によって対処できる。

変数のスコープを小さくする
グローバル変数を修正する場合、修正の影響範囲が広くなる。仮にクラス内の1メソッドでしか使われていなくても、クラス構成を知らない開発者からすればグローバル変数は全体で使われている想定の扱いをする。
無駄な手間を減らすためにも、変数のスコープはなるべく小さく設定しておくのが無難である。

原則、変数の書き込みは一度だけとする
任意の変数の書き込み箇所が多い場合、変数のとりうる値がどうなるかわからなくなる。とりうる値がいくつもあると、バグが発生しやすくなる。これを防ぐための方法として「変更されない変数」を定義することが挙げられる。
JavaScriptの"const"、Javaの"final"といった変数定義を用いることで、とりうる値を制限しバグを生みにくくなる。

◆コードの再構成

●無関係の下位問題を抽出する

「無関係の下位問題」というと仰々しいが、「最も解決したい問題を解決するためのひとつのステップ」と捉えると理解しやすいだろう。最も解決したい問題を「上位問題」ととらえるならば、上位問題を細分化したうちの一つの問題を「下位問題」と表現できるからだ。
エンジニアリングの本質は、大きな問題を小さな問題に分割しそれぞれの問題の解決策を出すことに他ならない。エンジニアリングの本質を読みやすいコードにも当てはめることができるのである。

例えば、地点Aから最も近い国の首都までの距離を返すプログラムがあるとしよう。
このプログラムの実装と利用データは以下のようになると推測される。

利用データ
 ・首都の位置情報データ
  首都名・緯度・経度のデータ
 ・地点Aの緯度・経度のデータ
(※)は首都ごとに繰り返し実行される(反復制御)

実装(改良前)

  1. 最も近い距離を格納する変数(closest_dist)を定義する

  2. 地点Aの位置情報をデータから抽出する

  3. 地点Aの緯度をラジアンに変換する

  4. 地点Aの経度をラジアンに変換する

  5. その国の首都の位置情報をデータから抽出する(※)

  6. その国の首都の緯度をラジアンに変換する(※)

  7. その国の首都の経度をラジアンに変換する(※)

  8. 球面三角法の第二余弦定理により距離を算出する(※)

  9. 算出された距離がclosest_distより短ければclosest_distに格納する(※)

  10. closest_distを返す

この時、下位問題となる部分は「球面三角法の第二余弦定理により距離を算出する」であろう。ここは別メソッドとして抜き出すのが適切である。さらにラジアンの変換も含めて別メソッドに抜き出すと、メソッドは下記のように定義できる。
 spherical_distance(地点Aの位置情報、その国の首都の位置情報)
 補足)位置情報は緯度・経度で示される
この抜き出しの結果、改良後は以下のような実装となる。

実装(改良後)

  1. 最も近い距離を格納する変数(closest_dist)を定義する

  2. 地点Aの位置情報をデータから抽出する

  3. その国の首都の位置情報をデータから抽出する(※)

  4. 抜き出した関数spherical_distanceを呼び出す(※)

  5. 算出された距離がclosest_distより短ければclosest_distに格納する(※)

  6. closest_distを返す

このメソッドであれば上位問題である「首都までの距離を返す」に沿った構成で理解しやすいコードとなっている。また、spherical_distance()は単独でチェックできるメソッドとなり、保守および再利用のしやすいプログラムとなった。

下位問題を抽出することの恩恵としては、下記にまとめられる。

  • 上位問題を解決するメソッドが理解しやすくなる

  • 下位問題を解決するメソッドの保守をしやすくなる

  • 下位問題を解決するメソッドを他の部分で再利用しやすくなる

●一度にひとつのことを

一度に複数のことをするコードは理解しづらいものである。
ヒトは一度に複数のことをしようとすると効率が落ちてしまうことは既に確認されており、これは理解しやすいコードを書く上でも共通のことである。

上位問題を出発点に、細分不可能になるまで下位問題を抽出していく。そこまでできたら、下位問題を解決するコードを書いていく。
下位問題どうしで関連性の強いものがあればメソッドを共通化・統合したりしていく。
こうすることで、より理解しやすいコードとなるだろう。

●コードに思いを込める

コードそのものに思いは存在しないが、コードを実装するエンジニアは思いを良いコードという形で表現することが可能である。
エンジニアにおける思いとは「次に読む人がすぐ理解しやすいように書いてあげよう」である。
思いを表現するための手順を簡潔に説明すると、以下になる。

  1. コードの動作を小学生でも伝わるように説明する

  2. 説明の中で使っているキーワードやフレーズに注目する

  3. キーワードやフレーズに合わせてコードを書く

説明にある手順は、(想像上の)小学生に説明をしてコードを磨いていく流れである。説明によって設計を言語化することで、曖昧な点があぶりだされる。曖昧な点はおおむね問題点があったり見落としがあったりするポイントであるため、プログラムが磨かれていくのは必然であろう。

●短いコードを書く

小池が過去に所属したシステム開発会社のCTOに言われたプログラミングの極意が、リーダブルコードにも記載があった。彼がこの本を読んでいたかはわからないけど(きっと読んでいる)、長年現場で経験されていたことを端的に言い表した極意だったんだなあと改めて思った。それは以下である。

最も読みやすいコードは、何も書かれていないコードだ。

D.Boswell,T.Foudher 著・角征典 訳, リーダブルコード, オライリー社, 2012, p168,

「教えていただきありがとうございます。今でも『何も書かないことだよ』と言ってくれた極意は役に立っていますよ」と当のCTOにはここでお伝えしておきたいと思います。ありがとうございました。

回りくどく言うなら「そのシステムに不要な機能を盛り込むと、余計なコードを書くことになり開発・保守コストが増大する」である。
例えば静岡県内限定のチェーン店のアプリに地球規模で現在地から最も近い距離を計算するプログラムは必要ないだろうし、時差を考慮した店舗の営業状況判定プログラムも必要はないだろう。最も簡単に課題を解決できるプログラムを用意すればよいのだ。

コード量を小さくするコツとして、身近なライブラリを使うことやOSの関数を使うことが挙げられる。身近なライブラリは変化の激しいプログラミング界を長く生き残っているものである。長く生き残るのは、開発企業が緻密な設計と膨大なチェックを行われたものだけしかないと言ってよい。そうしたものを把握し活用することで少ないコード量で安定した動作ができるシステムを作れるだろう。
プログラムの管理コストは、ざっくりいえばコード量に対して指数関数的に増大するといえる。すなわち、コード量を削減できることは管理コスト削減に大きな貢献ができることにつながるのである。

◆選抜テーマ

●テストと読みやすさ

テストコードを読みやすくすることで、プログラムの保守および改修が容易になる。読みやすいテストコードはどういうものを指すかというと、追加や変更がしやすく見えるものである。読みやすさを高めるためには「大切でない情報は隠し、大切な情報は目立つようにする」ことを心掛けることだ。

テストコードを見やすくしたら、次はエラーメッセージを見やすくするべきである。大概の言語のテストコードのライブラリには、エラーメッセージの見やすいメソッドが用意されている。例えばPythonでは”assertEqual()”メソッドを利用すると、以下のように表示される。(テストコードは例示用)

import unittest

class MyTestCase(unittest.Testcase):
    def TestFunction(self):
        a = 1
        b = 2
        self.assertEqual(a, b)
if  __name__ == '__main__':
    unittest.main()

#実行結果
File "MyTestCase.py", line7, in testFunction
        self.assertEqual(a, b)
AssertionError: 1 != 2

"AssertionError: 1 != 2"に示されるように、期待値と入力値が同時に出力されている。よりわかりやすくしたいならば、テストコードの出力を司るコードに自分で実装するのも手である。

エラーメッセージを見やすくしたら、次はテストの入力値を確認しよう。
たとえば数字の確認をしたい場合は、負の数・0・少数・大きな数(int型で扱えない範囲)・境界値といった観点がある。機能やバリデーションの定義にもよるが、これらのいくつかを選んで入力値を構成する必要がある。選ぶ際は必要以上に複雑な値でなくてもよい。

適切な入力値を選べたら、あとはテストメソッドの名称をつけよう。
テスト対象およびテストしたい状況を簡潔に書いたものが良い。

----- 学んだこと -----

私がこの本を読んで学んだことは、自分に以下の観点が欠けているのを自覚したことである。

  • コードを見直す際は、無関係の下位問題を抽出せよ

  • コメントをつけるよりコメントのいらないロジックを書け

  • コメントでこそあど言葉は禁句

特に「コードを見直す際は、無関係の下位問題を抽出せよ」はエンジニアリングの本質を再自覚させるとともに、人生のあらゆる問題を乗り越えるための解決策として大いに役立つということを学べた。

リーダブルコードはプログラミングの経験がない人向けではなく、プログラミングをある程度こなした人が読むべき本だとも思った。そういう意味では、今出会えたことは非常に良いタイミングだったと思う。

この記事が気に入ったらサポートをしてみませんか?