はじめに
このまえ、何をしているかよくわからないコードを見つけたんですけどね、なんと自分が書いたコードだったんですよ。
何を思ってこれを書いたのかと著者を問い詰めてやりたかったのですが、困ったことに何も覚えていないらしいです。
あまりに困ったので、なぜこのようなことが起こるかを考えてみました。
思いついたことを列挙してみます。
免責事項
記事内のコードには javascript を使っていますが、 javascript 固有の処理は使っていないので安心してください。
「でもおまえコード汚いじゃん」は禁句です 🙄
こういう記事は自分のことを棚に上げて書くものです ((すみません))
見直しましょう
まずはじめに
書いたコードを見直すと、意外と改善点が見つかります。
見直すといいタイミングはいろいろありますが、例えば、
・コミットするときに git add -p
を使う
・レビュー依頼を出す前にマージリクエストを眺める
ということはよくやっています。
誰かに見てもらいましょう
岡目八目というやつです。誰かに見てもらって感想を聞きましょう。
たとえば処理の流れを説明してもらえれば、つまったところがわかりにくいところだと言えます。コメントをつけたりロジックを見直したりしましょう。
名前を考えましょう
いい名前は読みやすいコードに直結するので、名前を考えるのにはいつも時間を使います。
私は以下のようなテクニックをよく使います。
・codic で調べてみる
・よさそうな名前があるか、類語 (thesaurus) を調べる
・ニュアンスの違いに困ったら英英辞書をみる ex) look と show
・英文法的におかしい名前は避ける ex) isExist
よりも exists
・すでに世の中にある名前を使う ex) isIncluded
よりも includes
(javascript に Array.includes
がある)
・候補が複数あって迷うときは、その単語で google 検索をして、ヒット数の多いほうを採用する
他に良いテクニックがあったら教えてください
コメントを書きましょう
読む人のことを考えたコメントを書きましょう。
「何をしているか」より、 「何をしたいか」 を書くのがコツです。
コードを書いているときは処理をすべて理解しているので、 「ここはコメントがなくても理解できる」と思ってしまいがちです。
そこで立ち止まって、俯瞰して見れたらいいですね。難しいですが......
たとえば以下のコードは、書いている本人には簡単かもしれませんが、処理を知らない人が読むと少しつまってしまいます。
const a = 10;
let sum = 0;
if ((a & 1) === 0) {
sum += a * (a + 1) / 2;
}
コメントの有無で読みやすさが変わるので、意識してみてください。
const a = 10;
let sum = 0;
// a が偶数なら、 1 から a までの総和を計算する
if ((a & 1) === 0) {
sum += a * (a + 1) / 2;
}
このコメントは「何をしたいか」になっていないですが、例なので許して🙄
if ((a & 1) === 0)
は a
が 2.5 や NaN などのときにも true を返します。
こちらでコメントいただきました。ありがとうございます。
https://qiita.com/okm-uv/items/e790f5c5011a5218d2fb#comment-562d6e0c84c49a8627d6
条件式やネストに注意しましょう
ネストを抜けた後の状態が何通りもあると読みにくくなります。
状態を把握しきれず、バグりやすくもなります。
if (a) {
if (b) {
...
} else if (c) {
if (d && e) {
...
}
...
}
} else {
...
}
こういう時は、ガード節を使ったり、フラグに名前をつけたりします。
// これがガード節
if (a) {
...
return;
}
// ここでは a == false が保証される
// フラグに名前を付ける
const isAandB = a && b;
const isInProcess = process.busy() || waitingQueue.length > 0;
また、別の観点ですが、条件式に not 式を使うのはできるだけ避けます。
if (!flag) {
...
} else {
...
}
よりは
if (flag) {
...
} else {
...
}
のほうが読みやすいです。
書く前に既存のコードを読みましょう
似た処理をしているところがあったら読みましょう。
既存コードの処理を理解するのに加えて 書き方 も真似します。
というのも、プロジェクト内でのコードの統一性を持たせるためです。
既存コードを読むのは面倒ですが、長期的に見ると統一性のある良いコードが書けるようになります。将来の成長が約束された神ムーブです。
たとえば、偶数判定に isEven()
という関数を使っていると思ったら、いきなり num % 2 === 0
が出てくるようなマージリクエストは割と見かけます。
これをするとコードの統一性が崩れるのですが、統一されていないコードはかなり読みにくくなってしまいます。
たとえば以下のコードはどれも偶数判定ですが、混在していたら読みにくくなるのはイメージできますでしょうか。
if (a % 2 === 0) {
...
}
if ((a & 1) === 0) {
...
}
if (isEven(a)) {
...
}
if (!isOdd(a)) {
...
}
コードを読むときに4種類の処理を頭に入れておくのはすこし大変です。
(偶数判定が混在しているだけなら大丈夫ですが、他に統一されていない処理があると、気にすることが指数的に増えていって読みにくくなります)
ここでは処理について書きましたが、 変数名 についても同じことが言えます。
例えば create
という名前が好きでも、既存コードが make
と言っていたら make
と書くほうが良いです。
もしくは、既存の make
をすべて create
に書き換えましょう。
同じ処理は共通化しましょう
DRY 原則というやつです。
例を出してみます。
let sum = 0;
const a = 10;
const b = 15;
sum += a * (a + 1) / 2;
sum += b * (b + 1) / 2;
これは
// num 以下の自然数の和を計算する
function calcTotal(num: number) {
return num * (num + 1) / 2;
}
let sum = 0;
const a = 10;
const b = 15
sum += calcTotal(a);
sum += calcTotal(b);
のように書けます。
a
や b
はなにか意味のある名前になっているとさらに読みやすくなります。
calcTotal という名前の是非については不問としてください 🙄
行数が増えているので良くない、と思うかもしれませんが、問題ありません。
同じような処理を追加するときのことを考えてみてください。
const c = 20;
sum += c * (c + 1) / 2;
を追加する時と、
const c = 20;
sum += calcTotal(c);
を追加する時とでは、後者のほうがシンプルで理解しやすく、バグらせにくいです。
ここでは定数名を c
としましたが、なにか意味のある名前だとさらに読みやすくなります。
後者の利点として
・関数名を typo したらエラーで教えてくれる
・ num * (num + 1) / 2
の式に変更があったときに、書き換える箇所が少ない
などがあります。
また、上に書いたことの逆をとると、前者の問題点になります。
文章力をつけましょう
大丈夫です。読み違えていません。
ここでは、文章を書く能力について書いています。
これは持論ですが、 ソースコードは文章です。 いい文章がかければ読みやすいソースコードを書けます。
というのも、これまで書いたことはすべて「いい文章の書き方」に言い換えられる123ためです。文章力をつけるのはコードだけでなく日常生活でも役に立ちますので、とてもオススメです。
特に以下の本が役に立ちました。
アイデアのちから
理科系の作文技術
論理トレーニング101題 (これは読んでいる途中)
まとめ
以上、思いついたテクニックを書いてみました。
しかしこれらほんの一部です。世の中にはもっとたくさんのテクニックがありますので、興味がありましたら調べてみてください。
読みやすいコードはそのまま書きやすいコードとなり、柔軟な変更に対応できるようになります。逆に言うと、読みにくいコードは拡張性に乏しくバグを生みやすいコードとなってしまいます。
日々、読みやすいコードを目指して書いていますが、気づけば読みにくいコードを産んでいます。改めて思うのは、読みやすいコードを書くことよりも 「自分のコードが読みにくい」 と気づくことのほうが大切なのかな、ということです。
これがより良いコードを書く助けとなれば幸いです。