リーダブルコードを読んで大切だと思ったことをまとめる

リーダブルコードを読んで大切だと思ったこと、さらに今後意識するべきことをまとめていきます。

内容のまとめ

リーダブルコードでは、集団開発をする上で必要な最低限のコーディングルールがまとまっていました。

そして、この本の中で述べられているものは大きく分けると3つの項目があります。

  • 命名規則について
  • ロジックの単純化について
  • コードのまとめ方について

命名規則

エンジニアには必ず変数や関数を命名するという作業を行う場面があります。

しかしこの命名を”hoge”や”fuga”などふざけた名前にしてしまうと、コードが無法地帯となり後々の技術負債につながる可能性があります。

それを避けるために、命名に関してはある程度の共通ルールを設けおく必要があります。
また、わかりやすい変数名をつけることで後でコードを見返した際にバグの発見が容易になるという利点もあります。

明確な単語を選ぶ

コードを読んでいるとよく出現する、get,putなどの汎用的な単語での命名を避けた方が無難です。
これは具体的な単語を選択することでその変数の役割を想像できる状況を作ることを目的としています。

getはgetでもどうやってgetしてくるのか、それを具体的に想像できる単語を選ぶべきです。

よく使う単語代替案
senddeliver,dispatch,announce,distribute,route
findsearch,extract,locate,recover
startlaunch,create,begin,open
makecreate,set up,build,generate,compose,add,new
getfetch,download
stopkill,pause

tmpやretvalなどの汎用的な名前を避ける

コードの例文などで一時的な値を保存するときなどにtmpやretvalなどの変数を用いることがあります。

しかし、これはあまり良い命名ではありません

tmp,retvalのようなあまり意味を持たない値を持つことはコードを読む際に混乱を招きやすく、バグを見つけにくくなってしまいます。しかし、どうしてもこのような命名をしたいときは、tmp_fileなど情報を追加することで混乱を避けることが効果的です。

このtmpという値は中間結果の保持などで使われることが多いですが、リーダブルコードではそもそもの中間結果の保持を推奨していません。それは、中間結果の保持はコードを改善することで必要がなくなることが多いため、なるべく中間結果は保持しないスタンスでいる必要があるからです。

このことからわかるようにtmpなどの変数や中間結果の保持などは何か明確な理由があるときにのみ用いるべきであることがわかります。

値の単位

時間やバイト数のように計測できるものがあれば、変数名に単位を入れるとわかりやすくなります。たとえばstart_msのようにミリセカンドを表す_msをつけることでその変数はミリ秒を扱っていることがわかりやすくなります。

その他にも、変数の意味を理解して欲しいときには変数に重要な属性を付与することが効果的です。
特にデータを安全にする関数を呼び出した後はそのデータの「状態」を変数名に追加することでデータの状態を伝える必要があります。たとえば、セキュリティ保護がされていないURLを扱うときにはuntrustedUrlと名付け、データを安全にする関数を呼び出した後にtrustedUrlなどに変数名を変えることでデータの状態を正しく伝えることができます。

扱うもの変数名
時間_ms,_secs
サイズ_mb,_kbps
周波数_cw

境界値の命名

コードを書くうえでバグが起きやすいのは、値の境界値を扱う場合です。条件分岐の場合に扱う変数が以上なのか以下なのか限界値を含むのか含まないのか意識しながらコードを読むときにも混乱を招きやすいことがあります。
しかし、比較に扱う変数にその境界値の情報を持たせておくとコードの理解スピードを早めることができます。

  • 限界値を含めるときはminとmaxを使う
  • 範囲を指定するときはfirstとlastを使う
  • 包含・排他的範囲にはbeginとendを使う

ロジック単純化

ロジックが複雑だと、コードの読み手がそこで躓いて時間を奪ってしまったり、何回も読み返すような手間を取らせてしいます。そんな状況を避けるためにも、ロジックを単純化して読みやすい制御フローを意識する必要があります。

条件式


ifなどの条件式ではよく比較をする。以下のコードではどちらが読みやすでしょうか?

if (10.24568425 <= length)

if (length >= 10.24568425)

ほどんどの場合後者の方が読みやすいと思います。
このように、左側に変化する調査対象の式、右側にあまり変化しない比較対象の式を記載することで、コードの読み手の違和感をなくすことができます。

また、以下の式は同じものです。

// 条件式1
if (a == b) {
    case1
} else {
    case2
}

//条件式2
if (a != b) {
    case2
} else {
    case1
}

この2つの式は同じことをしているが、圧倒的に上の式の方が読みやすいことがわかります。
単純で読みやすい条件式にするには優劣があります。

  • 条件は否定系よりも肯定系をつかう
  • 単純な条件を先に書く
  • 関心を惹く条件や目立つ条件を先に書く

これらの優劣は衝突することもあるので、そのときは自分で判断して優先度を決める必要があるのです。

関数から早く返す

関数で複数のreturn文を使うのに抵抗がある人がいるかもしれませんが、それは大きな間違いです。

読みにくいコードの代表としてネストが深いコードがあります。このネストをいかに少なくするかでコードの読みやすさは大きく変わります。関数から早めに値を返すことができれば、ネストを浅くして読みやすいコードを実現することが可能になり、可読性が向上するのです。
これを日頃から意識することで、コードをクリーンに保つことができます。

コードのまとめ方について

巨大な式の分割

巨大な式というのは理解がしにくいものが多いことが一般的です。一度に覚えておかないといけない変数名が増えたり、何度もコードを読み直す手間もかかかります。
そんな巨大なコードの塊は、分割する必要があります。

効果的な分割の方法に説明変数を導入するというものがあります。説明変数は難解な式を説明する変数に置き換えるというものです。

productId = line.split((':')[0].strip()
if productId <= 1000

line.split((':')[0].strip()が一見なにを表しているか分からないような式でも、一度productIdのような説明変数に格納することでコードの可読性が向上したことがわかります。このように簡潔な名前で式を説明することで、「コードをドキュメント化」することができます。

そしてもう一つのテクニックに「ド・モルガンの法則」を用いるというものがあります。

if(!( a && !b ))

という条件式がある時大抵の読み手は立ち止まって考えてしまいます。
このような条件式があるときには、notを分配して、and/orを反転する(notを括り出す)というド・モルガンの法則を用いることで簡潔に表すことができます。

つまり、

if(!( a && !b ))if( !a || b )

というように表すことができるということです。

このように難解な式や巨大な式はいかにシンプルに読みやすく書くことができるがを考える必要がある。

変数と読みやすさ

変数を何も考えないで使うとプログラムが理解しにくくなる。

  1. 変数が多いと変数を追跡するのが難しくなる
  2. 変数のスコープが大きいとスコープを把握する時間が長くなる
  3. 変数が頻繁に変更されると現在の値を把握するのが難しくなる

このような問題に対処するには以下のような対処が必要である。

  • 邪魔な変数を削除する
  • 結果をすぐに使って、中間結果を保持しないなど工夫して不必要な変数は削除する必要がある
  • 変数のスコープをできるだけ小さくする
  • 変数を数行のコードからしか見えない位置に移動することで一度に考えなければいけない変数を減らすことができる
  • 一度だけ書き込む変数を使う
  • 変数を操作する場所が増えると、現在値の判断が難しくなる。そこで、変数に一度だけ値を設定すれば、(あるいは、constやfinalなどのイミュータブルにする方法を使えば)、コードが理解しやすくなる
  • どうしても変数を変更する必要がある場合は、変更する箇所をなるべく少なくすることを意識する必要がある

無関係の下位問題の抽出

無関係の下位問題を積極的に見つけて抽出するためには以下を考える必要がある。

  1. 関数やコードブロックを見て「このコードブロックの高レベルの目標は何か?」と自問すること
  2. コードの各行に対して「高レベルの目標に直接的に効果があるのか?あるいは、無関係の下位問題を解決しているのか」と自問する。
  3. 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする
無関係の下位問題の抽出例

ここでいう無関係の下位問題を抽出した状態というのはコードを抽出して別の関数にする際にその抽出されたコードがどのように呼び出されるのか意識しない状態であるということである。

プロジェクト固有のコードから汎用コードを抽出することでほとんどのコードは汎用化できる。一般的な問題を解決するライブラリやヘルパー関数を作っていけばプログラムに固有の小さな核だけが残る。

例えば、ディズニーランドに行った人が平均で1日何個のアトラクションに乗るのかを求めるコードを書くとする。
そうすると高レベルの目標は「ディズニーランドに行った人が平均で1日何個のアトラクションに乗るかを求める」ということになる。

そのためには

  • ディズニーランドの1日の来場者の数を求める処理
  • 1日でディズニーランド内のアトラクションが乗せた人の数を求める処理
  • 一人当たり何個のアトラクションに乗ったかを求める処理

これらを考える必要がある。
しかし、ここで考えるべきは3つ目の一人当たり何個のアトラクションに乗ったかを求める処理であり、そのほかの2つはそれを求めるのに必要な材料でしかない。つまり、無関係の下位問題である。

どのようにデータを扱っているかによるが、ここで1日の来場者を求める処理や1日でディズニーランド内のアトラクションが乗せた人の数を求める処理が複雑になっており、その関数の可読性を下げている要因になる場合これらは抽出する必要が出てくる。
特に1日の来場者を求める処理は他の場所でも使う可能性があるため、他の場所でも使うことができる汎用的な関数になることができる。

このように無関係の下位問題を抽出することで、その関数が何をしたい関数なのかを明確に捉えることが可能になる。そして、汎用的な関数を抽出することでコードの再利用性が高まる。

一度に一つのことを

一度に複数のことをするコードは理解しにくい。オブジェクトを生成して、データを整理して、データを加工して….と複数の「タスク」が絡み合っていると、「タスク」が個別に完結しているコードより読みにくくなる。

関数は一度に一つのことを行うべきという意見は有名かもしれないが、ここでいう一度に一つのことをというのも同じようなことである。しかし、関数に限ったことではない。一度に1つのタスクをするための手順は以下である。

  1. コードが行なっている「タスク」を全て列挙する。この「タスク」という言語はゆるく使っている。「オブジェクトが妥当かどうかを確認する」のような小さいことから、「ツリー全てのノードをイテレートする」のように曖昧なものもある。
  2. タスクをできるだけ異なる関数に分割する。

これらは、新しくコード書くときにも有効な方法だがリファクタリングに用いても効果を発揮する。複数のタスクを扱うコードを発見したときは、タスクを列挙し、異なる関数に分割できないかを考えることでコード全体がシンプルで読みやすくなる。

タスクを分割すると、それぞれのタスクに対するコードに集中できるため新たなリファクタリング方法を思いつくこともある。

まとめ

リーダブルコードでは主に変数とロジックをいかにして読みやすくするかということがまとめられており、具体的な例を用いて説明がなされていた。
その中でこれは意識するべきだと感じたものが3つある。

  1. ロジックの言語化
  2. 各ブロック、各領域でそのことだけを考えればいいようにコードを書く
  3. 読みやすいコードを書くことに対して妥協しない

特に3つ目の「読みやすいコードを書くことに対して妥協しない」に関しては本の中でも

理想とは程遠いインターフェースに妥協することはない

と書かれている。これは言い換えると読みやすいコードを書くことに関して妥協してはいけないということだ。読みやすいコードは自分のためにもなるが、他人がコードを理解しチーム全体の開発効率を向上させるために必要不可欠な要素だと言える。

また、これらのことを普段から「意識」することが何よりも大切である。最初は難しいかもしれないが最後には無意識下で読みやすいコードを書くことができるようになるためにも意識し、行動に移すことが重要である。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です