フューチャー技術ブログ

新入社員が「リーダブルコード」を読んでみた

top

O’Reilly Japan リーダブルコード

はじめに

はじめまして。フューチャーに2023年4月に新卒入社した関根です。今回の投稿が初の投稿となります。

最初の記事は何が良いか?と考えたのですが、業務の事を考えるとまずは可読性の高いコードを書けた方が良いと考え、教材に「リーダブルコード」を選びました。名著ですね。超有名な本なのに入社するまで読んだことがありませんでした。という事でこれを機に学んでいきます。

しかし、ただ本の内容をまとめていても面白く無いなーと感じたため、私が初めてプログラミングを触った大学1年性の時のC言語のコードを取り出してきて、それを修正しながら学ぶ形式にします。内容はかなり初歩的ですが、焦らず基本からです。

今回の内容はリーダブルコードの第一部「表面上の改善」を参考にしています。本の詳しい章立てなどはこちらの目次をご確認ください。

最大照度を発見するコード

こちらのコードは照度センサーから最大照度を発見するためのコードです。
6年前の当時のコードをほぼそのまま取り出しています(一部省略はしています)。

可読性の低い箇所を修正してください。

int main() {
float a[20];
float max = 0;

for(int i = 0; i < 20; ++i) {
a[i] = lum.lux(); //センサーから照度を取得
if(max < a[i]){
max = a[i];
}
printf("%7.1f[lux]\r\n",a[i]);
}
}

変数名などを可読性の高いものに変えよう

上から順に見てみましょう。

まずaが何を指しているのかが全くわかりませんね。コード全体を見れば、センサーから取得された照度を格納する配列と理解できますが、コード全体を読まなくてはならない時点で不親切です。

ここでの照度の単位はLuxであるためluxValuesや、照度の英語がilluminanceであることから、illuminanceを格納するarrayという事でilluminanceArrayも良いかもしれませんね。こう書くことにより、この名前を見るだけでも、「これには照度の値が複数格納されているのだな」というのが誰が見ても分かるようになります。親切ですね。

それで言うと、float型の変数、maxというのも不親切です。先ほどのaよりは、この変数には最大値が含まれるのだな、と分かりますが、何の最大値なのか?は変数名から全く分かりません。これも同じようにmaxIlluminanceや、maxLuxValueなどがあるでしょうか。これであれば一目見れば何の最大値なのかが分かるようになります。

まだ2つしか修正していませんが、先ほどのコードに反映させてみましょう。

int main() {
float illuminanceArray[20];
float maxIlluminance = 0;

for(int i = 0; i < 20; ++i) {
illuminanceArray[i] = lum.lux(); //センサーから照度を取得
if(maxIlluminance < illuminanceArray[i]){
maxIlluminance = illuminanceArray[i];
}
printf("%7.1f[lux]\r\n",illuminanceArray[i]);
}
}

文字数は多くなりましたが、各行で何をしているのかが分かりやすくなりました。

使用する英単語等は「照度 英語」や「照度 翻訳」のようにすれば出てきます。

まとめますと、変数の命名をする際は以下に注意すればいいでしょうか。

  • その変数に必要な情報を入れる。何に使う変数なのか?(今回なら照度だからilluminance)
  • 面倒だからとローマ字で書かない。(shodoArrayとかは良くない)
  • 変数を書くときはローワーキャメルケースで。
    (単語を繋げる際に、2単語目以降の頭文字を大文字にする。lowerCamelCase, firstNamemaxLuxValueなど)

補足説明  命名する際は全てローワーキャメルケースにするべき?

変数を命名する際によく使う形式がいくつかあります。

  • アッパーキャメルケース
    • 単語を繋げる際に、全ての単語の頭文字を大文字にする
    • UpperCamelCase, UpdateEmployee, EditEmployeeなど
  • スネークケース
    • 単語を繋げる際に、_(アンダーバー)でつなげる
    • local_file, employee_idなど
  • アッパースネークケース/コンスタントケース
    • 単語を繋げる際に、_(アンダーバー)でつなげつつ、全ての文字を大文字にする
    • ARRAY_SIZE, DEFAULT_IDなど

リーダブルコードの2.6節「名前のフォーマットで情報を伝える」ではこの名前の書き方で変数や関数に情報を持たせる方法が記載されていました。

例えば、

  • クラス名やメソッド名にはアッパーキャメルケース
  • ローカル変数にはスネークケース
  • 定数にはアッパースネークケース

などです。

こうする事で、コードを読んでいる際に「このUpdateEmployeeはアッパーキャメルケースだな。つまりクラス名かメソッドだ」などと名前とは別に情報を簡単に付加出来ます。

しかしこのあたりのルールは開発チームが採用する、言語や開発規約に依存すると思います。これについては勝手に変更するのではなく、チームの方針に従いましょう。

  • 全てキャメルケースにするのが良いとも限らない
  • 命名規則は使い分けると情報量を付加できる

マジックナンバー

次に気になるのは何でしょうか。

可読性、というよりは再利用性を高める部分になりますが、コードの中に20という数値が2回出ているのが気になりますね。今回は小さいコードなので、問題にはなっていませんが、より大規模なコードになり、20という数値が100回出てきたとしたらどうしましょう。全て一々修正していたら日が暮れますね。

こういった直に記述された数値のことをマジックナンバーと呼びます。

20という数値には一見情報がありそうで、何も情報はありません。

例えば

for(int i = 0; i < 20; ++i) {

これだけ急に見せられても何のコードか全く想像ができませんね。

20回何かを表示するためなのか、配列の中身を表示するためなのか。

そこで定数の出番です(C言語の場合は配列のサイズ指定をする際にconstは使えず、マクロを使用する必要があります)。この20という数値は照度が格納された配列のサイズを表すため、ILLUMINANCE_ARRAY_SIZEなどが良いでしょうか。

C言語のマクロでは、全て大文字で単語間を_で繋ぐのが一般的です(アッパースネークケース)。

これをコードに反映させると、

#define ILLUMINANCE_ARRAY_SIZE 20
int main() {
float illuminanceArray[ILLUMINANCE_ARRAY_SIZE];
float maxIlluminance = 0;

for(int i = 0; i < ILLUMINANCE_ARRAY_SIZE; ++i) {
illuminanceArray[i] = lum.lux(); //センサーから照度を取得
if(maxIlluminance < illuminanceArray[i]){
maxIlluminance = illuminanceArray[i];
}
printf("%7.1f[lux]\r\n",illuminanceArray[i]);
}
}

これで配列のサイズを変更したい!となった場合は1行目の定数(マクロ)の数値を変更するだけで、まとめて2か所変更が出来ますね。可読性を向上させながら、再利用性を高める事が出来ました。

ループイテレータ「 i 」をどうするか?

上記の調子で行くと、for文の繰り返しで使われているiの名前も変えた方が良いと感じますね。

iilluminanceArrayのインデックスを表しているため、illumIdx(illuminance Indexの略)などが良いでしょうか。では反映させてみましょう。

#define ILLUMINANCE_ARRAY_SIZE 20
int main() {
float illuminanceArray[ILLUMINANCE_ARRAY_SIZE];
float maxIlluminance = 0;

for(int illumIdx = 0; illumIdx < ILLUMINANCE_ARRAY_SIZE; ++illumIdx) {
illuminanceArray[illumIdx] = lum.lux(); //センサーから照度を取得
if(maxIlluminance < illuminanceArray[illumIdx]){
maxIlluminance = illuminanceArray[illumIdx];
}
printf("%7.1f[lux]\r\n",illuminanceArray[illumIdx]);
}
}

確かにコードの可読性は上がったような気がします。

しかしコードが1行1行長くなってしまいましたね。今回は小さいコードであるため、これでも読めないことはないですが、やり過ぎには注意です。一般的にiはfor文の繰り返しで使用される変数名として浸透しています。(iはindexから来ているみたいです)そのため、何でもかんでも変数名を長いものにするのが良い、という訳でも無いみたいです。

読みやすいコードを書く最大の意義は次に読む人が、読むのに(理解するのに)時間を要さないコードです。丁寧に書こう!という気持ちが先走り、コードの長さが10倍になってしまったら本末転倒です。

分かりやすく書くことも大事ですが、同時に行き過ぎて無いだろうか?と考えるのも同じぐらい大事そうですね。

処理をまとめる

ここでは前節のiillumIdxに変更するのは取り消しておきましょう。

以下の状態ですね。

#define ILLUMINANCE_ARRAY_SIZE 20
int main() {
float illuminanceArray[ILLUMINANCE_ARRAY_SIZE];
float maxIlluminance = 0;

for(int i = 0; i < ILLUMINANCE_ARRAY_SIZE; ++i) {
illuminanceArray[i] = lum.lux(); //センサーから照度を取得
if(maxIlluminance < illuminanceArray[i]){
maxIlluminance = illuminanceArray[i];
}
printf("%7.1f[lux]\r\n",illuminanceArray[i]);
}
}

あと修正を加えるとしたらなんでしょうか。

最後のprintf文が気になりますね。このprintfではセンサーから取得した照度の値を表示するために書かれています。であれば、最後に書く必要はないでしょう。

照度を取得した段階で表示しても問題ないはずです。

#define ILLUMINANCE_ARRAY_SIZE 20
int main() {
float illuminanceArray[ILLUMINANCE_ARRAY_SIZE];
float maxIlluminance = 0;

for(int i = 0; i < ILLUMINANCE_ARRAY_SIZE; ++i) {
//センサーから照度を取得
illuminanceArray[i] = lum.lux();
printf("%7.1f[lux]\r\n",illuminanceArray[i]);

//最大値を探す
if(maxIlluminance < illuminanceArray[i]){
maxIlluminance = illuminanceArray[i];
}
}
}

以下はリーダブルコードからの引用になります(4.6節、4.7節)。

人間の脳はグループや改装を1つの単位として考える。コードの概要をすばやく把握してもらうには、このような「単位」を作ればよい。
(中略)
文章は複数の段落に分割されている。それは、

  • 似ている考えをグループにまとめて、他の考えと分けるためだ。
  • 視覚的な「踏み石」を提供できるからだ。これがなければ、ページのなかで自分の場所を見失ってしまう。
  • 段落単位で移動ができるようになるからだ。

これと同じ理由で、コードも「段落」に分けるべきだ。

つまり、コードの流し読みが出来るかどうか?というのが一つの観点になるかなと思いました。パッとコードを見た時に、どれだけ内容を把握できるか。

今回ぐらいのコード量であればわざわざ分ける必要も無いかもしれませんが、分けたことにより、for文の中には2つの処理があるんだなというのが分かりやすくなります。2つの塊としてfor文内を認識できるという事。

上の塊は照度の値を取得、下の塊は最大値を探す、と瞬時に分かります。

この書き方は大規模な開発になるほど有用になりそうです。

before after

今回加えられる修正はこのくらいでしょうか。

最後に修正前と修正後を比べてみようと思います。

before
int main() {
float a[20];
float max = 0;

for(int i = 0; i < 20; ++i) {
a[i] = lum.lux(); //センサーから照度を取得
if(max < a[i]){
max = a[i];
}
printf("%7.1f[lux]\r\n",a[i]);
}
}
after
#define ILLUMINANCE_ARRAY_SIZE 20
int main() {
float illuminanceArray[ILLUMINANCE_ARRAY_SIZE];
float maxIlluminance = 0;

for(int i = 0; i < ILLUMINANCE_ARRAY_SIZE; ++i) {
//センサーから照度を取得
illuminanceArray[i] = lum.lux();
printf("%7.1f[lux]\r\n",illuminanceArray[i]);

//最大値を探す
if(maxIlluminance < illuminanceArray[i]){
maxIlluminance = illuminanceArray[i];
}
}
}

全体的にコードの情報量が増えて読みやすくなりました。

beforeのコードはコードの途中から読み始めると、「aってなんだ?」となってしまいます。また、コメントで「センサーから照度を取得」という文字が無ければ、このコードが何に使われるコードなのかも殆ど分からなくなってしまいます。

対してafterのコードはどこから読んでも、このコードがどんな場所で使われるのか?が分かりやすくなっています。

またコードを読むだけで、照度を取得してその中から最大値を取得するコードだ、というのが分かります。

可読性をかなり向上させる事が出来たのではないでしょうか。

まとめと感想

今回はかなり短いコードでしたが、基本的な事とは言え結構色んな事を学べました。

命名規則命名規則の使い分けマジックナンバーループイテレータの命名処理をまとめて書く、など。
基本的とは言え、これが出来ていたか?というと、うむーという感じです。これを機に意識していきます。

まだまだ可読性を上げるための技術は沢山ありました。コメントの書き方や制御フローの書き方など。今後も自分が過去に書いたコードでいい感じの教材になりそうなものがあったらとりあげて、今回のような記事を書いてみようかな、と考えております。

最後に、技術記事を書くのが本当に初めてなのでどう書くのが自分にとって書きやすいか?また、他の人にも有益になるか?というのは考えていかないとな、と思いました。まずは場数を踏むため、色んな記事があげられるように頑張ります。