LoginSignup
0
0

ほぼ知識のない初学者がリーダブルコードを読んでみた

Posted at

初学者がリーダブルコードを読んでみて書籍の概要や「なるほどな」「これは意識していこう」と感じたことなどをメモしたものをQiitaにあげてみた記事です。

リーダブルコードについて

リーダブルコードは基本的なことを丁寧に説明している書籍である。

1 章:理解しやすいコード

読みやすいコードを書くことが目的

「コードは理解しやすくしなければいけない」という考えが重要。

優れたコードとは 他の人が最短時間で理解できるように書かれているコード と定義されている。

もちろんコードは短く書いた方が良いが理解するまでにかかる時間を短くするほうが大切。短くまとまっていればそれで良いっていうわけではない。

高度に最適化されたコードでももっと理解しやすくできるはずだし、理解しやすいコードは設計やテストのしやすさに繋がることが多い。

何を優先したら良いのか、どうすればいいかわからなくなったら「読みやすさの基本定理」を最優先に考えよう。

リファクタリングしてコードを短くしていくことが必ずしも理解しやすいこととは限らないため、自問自答して本当に理解しやすいコードなのかを考えていくことが大切。

第 Ⅰ 部 表面上の改善

2 章:名前に情報を詰め込む

読みやすさにはまず表面上の改善からしていく必要がある。

名前は短いコメント であり、短くても良い名前をつけることができれば名前だけで多くの情報を伝えることができる

たとえば getData という名前からは何も伝わってこず、何のデータをどこから取得するものなの?となってしまう。

同様に size なども何のサイズを示しているのかはわからない。

そういった不明確さがある気取った言い回しより目的に適した明確な名前をつけていく必要がある。

エンティティの値や目的を表した名前を選んでいこう。

(i=0;とかで使用されることが多い)ループイテレータ i,j,k などについては、1 つのみの場合はそのまま使用しても構わないがイテレータが複数あるときはもっと明確な名前を付けた方が良い。members_i(mi)などもっと説明的に名前をつけていくことでどの変数のイテレータなのかということが理解しやすくなる。

また tmp などの汎用的な名前を使うときはそれ相応の理由を用意する必要があり、理由がない場合は使用を避ける。

抽象的な名前よりも具体的な名前を使っていこう。

elapsed などの経過を示すだけでなく、変数に値を追加して elapsed_ms みたいにどの単位での時間の経過なのか、size_mb のようにどんなサイズなのかを付けると明確といえる。

他にも html の文字コードを utf-8 にした html_utf8、
入力されたデータを URL エンコードした data_urlenc のように情報を変数に追加した方がいい例がある。

万が一変数の意味を間違えてしまったときに、バグになりそうな部分にはとくに属性を追加した方が良い。

スコープ(影響範囲のようなもの)が小さければ名前に多くの情報を詰め込む必要はないため、そういった場合変数の名前は短くて良い。

自分だけやチーム内だけでわかる名前の短縮については行わないべきだが、何も知らない新しい人がコードをみたときに理解できる部分についてはは短縮化してよい。(string = str,document = doc など)

また、ただ長い名前でその単語がなくても理解できるようなものについて不要な単語は投げ捨てるべき。
その単語をなくしても意味の明確さが変化しないものは必要ない。

名前のフォーマットで情報を伝えることもできる。
どういう規約にするかは自分自身かチームで決めること。

プロジェクトに一貫性を持たせることが大切。

簡潔にまとめると下記になる。

  • 明確な単語を選ぶ
  • 汎用的な名前は避ける
  • 具体的な名前を使って物事を詳細に説明する
  • 変数名に必要な情報を追加する
  • スコープの大きな変数にはそれなりの長さの変数名を
  • 大文字やアンスコに意味を含めてフォーマットから情報を伝える(27p 参照)

3 章:誤解されない名前

名前が他の意味に誤解されることは本当にないのかを確認する

例えば max_length などもバイト数、文字数、単語数など何の長さなのか色んな解釈ができるため、max_chars など何を意味しているのかわかる名前に。

限界値を含めるときは min と max を使用する。
たとえば limit だと未満を表しているのか以下を表しているのかわからない。そのため限界値を明確にする必要がある。

[product_purchase_limit = 5]なら5までなのか5未満(つまり4まで)なのか明確じゃない
[max_product_purchase = 5]なら5までなのかが明確

また、start,stop のような表現だと上記同様 stop の値でストップなのかその値前にストップなのか受け手によってわかれる。
範囲を指定するときは first,last を使用する と、どこから始まってどこが最後なのかわかりやすくなる。

排他的範囲の場合は begin と end を使用する。 例えば 12:00am を end だとすると 11:59:59pm までの範囲が対象となる。

bool read_password = ture;といったものについてもパスワードをこれから読み取るものなのか、すでに読み取っているものなのかがわからない。

is,has,can などつけてわかりやすくすることも多い。

名前を否定系にするのもやめたほうがよい。

bool disable_ssl = false;よりも
bool use_ssl = true;で肯定形にしたほうが読みやすいといえる

つまり最善の名前とは誤解されない名前である

4 章:美しさ

優れたコードは目に優しいものである必要がある

  • 読み手が慣れているパターンと一貫性のあるレイアウトを使用する
  • 関連するコードをまとめてブロックにする
  • 似ているコードは似ているようにみせる

適切な改行をいれたりして似ているコードは見た目も同じように見せよう。

コードの見た目をよくすることで表面上の改善だけでなくコードの構造の改善にも繋がる。
コードを整列しておけばタイプミスなどの気付きにくいミスにも気付きやすくなる。

コードの並びがコードの正しさに影響を及ぼすことは少ないが、無作為に並べるのではなく意味のある順番に並べた方が良い。
重要なものから並べたり、HTML の input フィールドと同じ並び順にするなど(一連のコードでは同じ順番にするべき)

行の間に余白を作って大きなブロックを論理的な段落にわけると見やすい。

上記でいった正しいスタイルを意識していくことは大事だがそれにより全体的に一貫性がなくなることは良くない。

一貫性のあるスタイルは正しいスタイルよりも大切

5 章:コメントすべきことを知る

コメントの目的はコードの動作を説明することだけではなく「書き手の意図を読み手に知らせること」

下記のようにコードを見てすぐにわかるようなことをコメントに書かない。

// ユーザー情報を更新
updateProfile(user.info)

// ユーザー情報を取得する
getProfile(user.id)

関数の名前や引数をそのまま文章で記載するコメントは価値のないコメントで不必要。

// このクラスは汚いからサブクラスを作成し整理した方が良い
class searchBook {
// 処理
}

上記のように、自分の考えを記録することはコメントの使用方法として良いといえる。

さらにこのように記法を利用してコメントを書いておくのも良い。

記法 典型的な意味合い
TODO: あとで手をつける
FIXME: 既知の不具合があるコード
HACK: あまり綺麗じゃない解決策
XXX: 危険!大きな問題がある

TODO: もっと時間計算量を意識した記述にするなど。

また定数にもコメントをつけておくと定数の値を決めたときに何故その値にしたのか、頭の中で考えていたことなどの背景を記録しておける。

image_quality = 0.72;
// 0.72 ならユーザーはファイルサイズと品質の面で妥協できる

こういったコメントがあれば何故その数値なのか、この数値で問題ないのかが一目でわかり他の人から見ても調整する必要がなくなる。

他のプロジェクトを 熟知していない人から見て質問されそうなことを読み手の立場になって 想像しよう

全体像についてのコメントをしたりコードの量が長い場合に処理の最初に要約コメントを残すことで見る人の手助けとなる。コードブロックごとにコメントを書いておくと見やすくなる。

const ProcessOrder = () => {
    // 注文の検証と確認
    {
        // 処理
    }
    // 在庫の更新
    {
        // 処理
    }
    // 注文の処理
    {
        // 処理
    }
    // 注文の確認メールを送信
    {
        // 処理
    }
}

時間かかるとはわからずに実行したはいいけど想定外に時間がかかりすぎたりすることもあるため、関数を実行することでかかる時間や気を付けるべき部分、注意する部分に対しての記述をすることで無駄な時間をかけずに使うことができる。

プログラマのライターズブロック(行き詰って文章(コメント)が書けないこと)を乗り越えるには とにかくコメントを書いていくことが大切。 何も書かないよりは何でもいいから書いていこう。

  1. 頭の中にあるコメントをとにかく書き出す
  2. コメントを読んで改善が必要なものを見つける
  3. 改善していく

のような手順でコメントを書く作業になっていき、1. の品質がよくなっていけば次第に 2.3.部分の修正が必要なくなってくる。

6 章:コメントは正確で簡潔に

コメントは画面の領域を取られるし読むのにも時間がかかるため簡潔であるべき

コメントにあいまいな代名詞はさけること。

// データをキャッシュに入れる。ただし、先のそのサイズをチェックする

そのって何を指してるの?データのこと?キャッシュ?
読み手に取っては一度考えないといけないので明確でないものに関しては曖昧な代名詞は使わない方がいい。

また、処理の内容を技術的に書くよりもコードの作者の意図をコメントに書いた方が良い。

// 平方を計算する関数
def calc(num):
    return num * num // numを2乗する

とするよりも、

// 平方を計算する関数
def calculate_square(number):
    return number * number // 与えられた数値の平方を返す

とした方が一目でどんな意図があるコードなのかがわかりやすい。

最初はコメント数が多くなってしまっても、いずれは多くの意味が込められている言葉や表現を使うことによりコメントを簡潔に保つことが望ましい。

第 Ⅱ 部 ループとロジックの単純化

7 章:制御フローを読みやすくする

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

コードの読み手が立ち止まったり読み返したりしないように書くことを心掛けよう。

条件式の引数の並び順は以下の表ようにすると見やすい。

調査対象の式
変化する
比較対象の式
あまり変化しない

条件式の引数の並び順としてどちらが見やすいだろうか。

if(num < 10){} // 調査対象が左側
if(10 > num){} // 調査対象が右側

調査対象が左側の方が見やすいはずである。
上記でいう num は調査対象なため、ループを実行するたびに増えていくから変化していく。対して 10 は比較対象なため変化しない。

if/else 文のブロックは並び順を自由に変えることによってケースの順番を変えることができる。
だが、それには以下のような優劣がある。

  • 条件は否定形より肯定形を使うこと
  • if(!empty)ではなく if(empty)や if(a != b)ではなく if(a==b)とすること
  • 単純な条件を先に書くこと
  • 関心を引く条件や目立つ条件を先に書くこと

優劣が衝突したときは自分で判断する必要がある。
否定形でも単純で関心を引くような場合はそっちを先に処理するのと良いなど例によってさまざま。

三項演算子は読みやすさの点からいうと考えるところがある

ここも元となる考え方と同じだが行数を短くしてコードをすっきりさせることよりも、他の人がそのコードをみて理解するのにかかる時間を短くすることが大切。

だから基本的には if/else 文を使う方が良くて、三項演算子によって簡潔にわかるときだけ使用するのが良いといえる。

関数で複数の return を使用することはよくないと思われがちだが、複数使用することによって早く返すのは良いこと。

ネストの深いコードは理解の妨げとなるため、ネストは浅くしていくべき

もともと短縮化されていたコードに対して追加していく場合はただ追加するのではなく、コードを新鮮な目で一歩下がってみて客観的に全体を見ることが大事。

ネストを少なくするためには失敗ケースをできるだけはやめに関数から返すこと。

以下のネストに注目するとわかりやすい。

if (user_result == "SUCCESS") {
  if (permission_result !== "SUCCESS") {
    reply.WriteErrors("reading permissions");
    reply.Done();
    return false;
  }
  reply.WriteErrors("");
} else {
  reply.WriteErrors("user_result");
}
if (user_result !== "SUCCESS") {
  reply.WriteErrors("user_result");
  reply.Done();
  return;
}

if (permission_result !== "SUCCESS") {
  reply.WriteErrors("reading permissions");
  reply.Done();
  return false;
}

reply.WriteErrors("");
reply.Done();

長さとして短縮化したわけではないが、ネストは浅くなり理解しやすいコードに。

深いネストを避けるには直線的なコードを選択していこう

8 章:巨大な式を分割する

巨大な式は飲み込みやすい大きさに分割することが大切

そのために一番簡単な方法は説明変数を導入すること。

// 説明変数を使ってない
if ($user_input % 2 == 0) {
    echo "偶数です";
}

// 説明変数を使ってる
$is_even = ($user_input % 2 == 0);
if ($is_even) {
echo "偶数です";
}

行数は増えるが、説明変数を使用することで式の意味を説明してくれるため読み手がわかりやすくなる。
こういった説明ごとにを分割していくとよい。

短いコードにまとめられていても読み手の理解に時間がかかるものは良くない。

頭が良いコードに気を付けよう

頭が良いコードは他の人がコードを読むときにわかりにくくなる。

9 章:変数と読みやすさ

変数を何も考えずに使用してしまうと以下のようなことが起きる。

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

これらを解決していかなければいけない。

不要な変数は削除していくこと

import datetime

now = datetime.datetime.now();
root_message.last_view_time = now

これは datetime.datetime.now()のままでも十分わかるし、使いまわしてるわけじゃなく一度しか使ってない。
now がなくとも十分明確なので一文にまとめることができる。

変数のスコープを縮める

変数のスコープを縮めるためにも グローバル変数は避けた方がよい。

読み手はグローバル変数を使っていることによってスコープがわからず、実際には使われていなくても使われていないか意識してコードを読まなければならなくなる。

class LargeClass{
  string str;
  void method1(){
    str = ...;
    method2();
  }
  void method2(){
    //strを使う処理
  }
  //strを使用しないメソッド
};

グローバル変数からローカル変数に格下げして string str;method1 の中に入れることによってスコープがはっきりし、どこに使用されてるのかが明らかになる。

生きている変数が多いと理解しにくく、さらに 理解しにくいのは変数が絶えず変更され続けること である。

その問題に対応するための一つとして以下がある。

変数を一度のみ書き込むようにしたら良いのではないか

変数を操作する場所が増えると現在地の判断が難しくなる。

第 Ⅲ 部 コードの再編成

10 章:無関係の下位問題を抽出する

エンジニアリングとは大きな問題を小さな問題に分割してそれぞれの解決策を組み立てることであり、この原則をコードに当てはめれば堅ろうで読みやすいコードになるのではないか。

無関係の下位問題を積極的に見つけて抽出すること

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

別の関数にする技法については簡単に使えてコードを大幅に改善することができるが多くのプログラマがうまく使いこなせていない。
あくまでも無関係の下位問題を積極的に探し出すことがうまく使えるコツである。

再利用を前提とするユーティリティーコードは関数として切り出しておくと良い。

const zeroPadding = (num: string) => {
  const ret = ("00" + num).slice(-2);
  return ret;
};

上記例はは入力した生年月日の 1 桁の月日を整形する際に再利用する可能性が高いコードである。

無関係の下位問題は、別の関数として切り出しておくことで予め用意されていた関数のようなコードとしていつでも利用することができる。

上記例含め、日付が絡んだデータの整形は頻出なため関数として切り出しておくと良い。

無関係の下位問題として関数化したものは基本的に広く適用可能なため複数のプロジェクトで使用できる、つまり特定のプロジェクトから切り離されている汎用コードとなる。
こういった汎用コードをたくさん作っていくと良い。

ただあまりにもやりすぎて小さな関数を作り出していくと逆に読みにくくなってしまうこともあるので気を付けよう。

11 章:一度に一つのことを

一度に複数のことするコードは理解しにくいから一度に一つのことを心掛けるようにする こと。そうすることでコードを楽に理解できるようになる。

タスクを分割することでうまくリファクタリングすることができる方法が思いつきやすくなる。

タスクをどのように分割するかという部分よりも「分割する」ということ が大切。

p146 コピー

12 章:コードに思いを込める

誰かに複雑な考えを伝えるときは細かいことまで話しすぎるとかえって相手を混乱させてしまう。

自分より知識が少ない人が理解できるような「簡単な言葉」で説明する能力というものが大切になってくる。

これは誰かに理解してもらうことだけじゃなく、自分の考えをより明確なものにする ことにも繋がる。

そのためコードも「簡単な言葉」で書くべき。

やろうと(書こうと)してる内容(目的)はまず自分で簡単な言葉で説明してみると良い。

説明することでコードがより自然になっていくし、解決策を見つけるための近道にもなる。

うまく言葉で説明できないのであればそれは何かを見落としてる詳細が明確ではない ということ。

13 章:短いコードを書く

プログラマとして学ぶべき最も大切な技能はコードを書かないときを知ることである。

その後のテストや保守などの負担があることを常に忘れないようにする。

また機能の実装を過剰に見積もってしまうことによりアプリケーションの複雑化につながるため、不必要な機能は削除していくことも重要。

プロジェクトが成長していってファイル数が増加していったとしても、コードをできるだけ小さく軽量に維持しよう。

そのために今まで記述した内容を簡単にまとめると下記を意識する。

  • 重複コードを削除
  • 未使用のコードは無用の機能を削除
  • コードの重量を意識、軽量で機敏にする
ちなみに・・・

平均的なソフトウェアエンジニアが 1 日に書く出荷用コードは 10 行。

この出荷用の 10 行には膨大な統計・デバッグ・修正・文章・最適化・テストが存在し、これらを乗り越えてきたコードには大きな価値がある。

定期的にすべての API を読むことで標準ライブラリに慣れ親しんでおこう。そうすることが新しいコードを書いているときにふと思い出すだろう。

第 Ⅳ 部 選抜テーマ

14 章 テストと読みやすさ

自分以外のプログラマが安心してテストの追加や変更ができるようにテストコードを読みやすくすることが大事。

テストコードを読みやすくするのはテスト以外のコードを読みやすくするのと同じくらい重要で、テストが読みやすければ本物のコードの動作だって理解しやすくなる

コードを書く上で大切なのは、一般的に大切な詳細は目立つように、そうじゃない詳細はユーザから隠すようにすること。

エラーメッセージはできるだけ役立つようにする。
エラーを読みやすくすることでデバッグに使える情報がより増えていく。

テストにはコードを完全にテストすることができる最もキレイで単純な入力値の組み合わせを選択しなくてはならない。

コードを検証する完璧な入力値を一つ作るのではなく小さなテストを複数作る方が簡単で効果的、そして読みやすい。

他の命名同様、テストの機能に名前をつけるときも名前を見ただけでなにに対するテストなのかわかるような命名にしておくべき。
たとえば対象の関数にプレフィックスとして Test_をつけるなどすると、なんのものに対してのテストなのかわかりやすいといえるだろう。

あとでテストをするつもりでコードを書くとテストしやすいようにコードを設計できるようになっていく。このように進めていけば自然といいコードが構成できるようになっていくだろう。

ただし場合によってはテストに集中しすぎてしまい、本物のコードの読みやすさを犠牲にしてしまう場合もあるので集中しすぎには気をつけること。

15 章:「分/時間カウンタ」を設計・実装する

外部の視点を得る、ということが大切。
設計・実装する上でコードがユーザフレンドリーなのかどうかを外部に確認すると良い。

for ループがうるさくないか?→ 読む速度が著しく落ちてしまう
重複コードはないか?→ ヘルパーメソッド導入などで共有化してコードを小さくする

この書籍では主にコードの見た目などについて言及してきたが 設計・実装ではパフォーマンスの問題についても特に考える必要がある

コードが改善されようが処理速度が 𝑂(𝑛)になってしまうとよくない。

たとえコードの行数が多くなってしまったとしても、𝑂(1)でパフォーマンスが高く設計に柔軟性があるコード の方が優れてるコードといえる。

書籍解説

書籍の内容を身につけて自然に書けるようになるためまず実践してみること。
はじめはうまくいかなくとも読み返しながら意識して読みやすいコードを書く、そして 「読みやすいコードを書くことが当たり前」 という状態にしていこう。

自分だけで進めていても自分では「気付かない」部分があるため、他の人に読んでもらうことを意識する。
そうすることで書いたコードがより良いものになっていく。
まずは「気付ける」ことからできるようになっていこう。

そうして繰り返し続けることで当たり前にしていこう。いっときの頑張りだけでは読みやすいコードにならない。

そうして得たものをコードで見てる人に伝えていくと良い。
新人だったり、新しい仲間にもコードをみることによって読みやすいコードとは、伝えられるようにしていく。

「自分が読みやすいコードを書けるようになること」 から、「自分が読みやすいコードを他の人に伝えられるようになること」 を重視する。

意図をコードで伝えられたらそれは本当に読みやすいコードになっているということ だろう。

ただの感想

一読してみたがかなり難しく、何のために出した一例なのかがわからない点が多くあった。

(学んでないプログラミング言語だとしてもわかるような記述で書いてるとのことだったが、個人的にそんなかんじはしなかった。)

だが書籍の内容としては評判通りかなり良く、難しい部分もある中で理解できた部分についてはなるほど、たしかにと思った部分も多くあった。

読んだ後にリファクタリングしていない自分のコードを見るといかにわかりづらいもの、見づらいものかと比較して学ぶことができた。

今の自分が理解してなるほどと思った部分を意識していくことはもちろんだが、またいつか経験を積んだあとにこの書籍を見ていきたい。

読むか迷ってる人には是非おすすめする一冊。
初学者には内容が難しい部分もあるが学べることも多くあると思う。もちろん経験者だとより書籍の内容が理解できると思うので経験者・初学者どちらにもおすすめ。

書籍など活字を見ることが得意じゃなく、読んだ方がいいと思いつつもなかなか手が出せていなかったので今回この機会に読んでこのような記事に残せて良かった。

(ちなみに自分のメモ用のついでに記事をあげたので、アウトプット記事としては一例も最低限の数しかないし活字ばかり、話が飛び飛びで見づらいと思う)

まだまだ解釈違いだったりする部分もあると思うので補足等あれば是非ご教示いただきたい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0