概要
- ペーペーエンジニアの覚書
- リーダブルコードのPrat2をまとめる
前回まとめ
- 前回までは表面上の改善
- 今回はコード上での改善策を取り上げる
- 制御フローを読みやすくする
- 巨大な式を分割する
- 変数と読みやすさ
制御フローを読みやすくする
- 条件式の引数の並び順
- ifの条件
- 三項演算子
- 関数から早く返す
- ネストを浅くする
まず最初は「制御フローを読みやすくする」方法についてです。こちらに関しては以下5つのアプローチがあります。
では、実際のコードを見ながら1つ1つ解説して行きたいと思います。
ちなみにjavaScriptでは使用しない「goto文」や使用機会の少ない「do/while文」については省いています。
1. 条件式の引数の並び順
- 条件式の引数の並び順にはある法則がある
- 左側 = 「調査対象」で変化する
- 右側 = 「比較対象」であまり変化しない
条件式の引数の並び順にはある法則がある
左側 = 「調査対象」で変化する
右側 = 「比較対象」であまり変化しない
2. ifの条件について
- 条件は否定形よりも肯定形を使う
- 単純な式を書く
続いては、if/elseの条件についてです。
条件は否定形よりも肯定形を使う方が直感的だと書かれていました。
if (a !== b) {} // 否定形(瞬時の判断に迷う)
if (a === b) {} // 肯定形(瞬時に判断しやすい)
そして条件は複数ではなく、なるべく単純シンプルになるよう努めましょう。
以下のように、条件が複数あると見辛くなってしまいますが、シンプルに努めることで可読性が増します。
// 条件が複数あり見ずらいパターン
if (num === 1 || num === 2 || num ===3) {
// todo
}
// シンプルで見やすいパターン
const array = [1, 2, 3];
if (array.includes(num)) {
// todo
}
3. 三項演算子
- 三項演算子については議論の余地あり
- 支持者は複数行が1行にまとまるので良いという意見
- 反対者は読みにくいしデバッグが難しい
続いては三項演算子についてです。
こちらは意見が別れる所で、賛成は1行にまとまるので良いという意見。
逆に反対者は読みにくいしデバッグし辛いという意見で別れます。
個人的には基本はif文を使用し、三項演算子によってシンプルになるのであれば使うという判断かなと思います。
// 個人的に許せる三項演算子
const timeString = hour >= 12 ? 'pm' : 'am'
// 絶対NGな三項演算子
const id = variable
? undfined
: params.id || query.id
// 三項演算子と短絡評価が混じっており、バグが混入しやすい
4. 関数から早く返す
続いては早期returnです。
これは、そのままですが、 以下のコードは年齢によって「成人」か「未成年」かを返す関数です。
早期returnすることにより、elseも使わず可読性とネストが浅くなっていることがわかります。
function hoge(age) {
if (age >= 20) {
return '成人'
}
return '未成年'
}
5. ネスト浅くする
続いては先ほどとも関連しますが、ネストを浅くしましょうという内容です。
例に上げているコードを見ただけで違和感を覚えますが、ネストの深いコードは常にどこにいるかを意識しないといけないし、カッコの範囲を意識しないといけなかったり、可読性を著しく下げてしまい、読み手に「精神的ストレス」を与えてしまいます。
1つ断言できることはネストは悪だということです。
ちなみに、ロジックを抽出し関数へ切り出したり、まとまった情報をオブジェクト化することは個人的にかなり有効だと思っています。
if (a === 1) {
if (b === 2) {
if (c === 3) {
if (d === 4 {
// こんなのがずっと続いたら。。。
}
}
}
}
巨大な式を分割する
- 説明変数
- 要約変数
- ド・モルガンの法則を使う
- 短絡評価の悪用
- 複雑なロジックと格闘する(より優雅な手法を見つける)
- DRYの原則
1. 説明変数
// NG
if (line.split(':')[0].strip() === 'root') {}
// OK
userName = line.split(':')[0].strip();
if (userName === 'root') {}
まずは「説明変数」についてです。
式を分割する簡単な方法に、式を表す変数を使う方法があり、こういった変数は「説明変数」と呼ばれます。
最初のコードはifの条件の中に、line.splitといった形で「式」が記述されています。
一方、その下のコードは「説明変数」を利用した例になります。
line.splitの処理を一旦「username変数」で受けて、その変数を元に条件を記述することにより、line.split~~~の箇所が何の値なのかがわかりやすくなっています。
2. 要約変数
// NG
if (request.user.id === document.ownerDocument.id) {
// ユーザはこの文章を編集できる
}
if (request.user.id !== document.ownerDocument.id) {
// 文章は読み取り専用
}
// OK
const isOwnsDocument = request.user.id === document.ownerDocument.id;
if (isOwnsDocument) {
// ユーザは文章を編集できる
}
if (!isOwnsDocument) {
// 文章は読み取り専用
}
続いては「要約変数」についてです。
先ほどのように式を説明する必要がない場合でも、大きなコードの塊を小さな名前に置き換えて、管理や把握を簡単にする変数のことを「要約変数」と呼びます。
画像のコードは「該当ユーザは文章を所持しているか?」という内容です。
上2つのifは変数をそのまま使用していますが、下2つのifはどんな値を比較したものなのか、要約した変数へ代入することで「関数で参照する概念」を事前に伝えることができています。
3. ド・モルガンの法則
続いてはド・モルガンの法則です。
ド・モルガンの法則で有名なのは、図のような円と円で表し、「真と偽を入れ替える」ことができるというのを示したものになります。
この法則を使えば次のように論理式を読みやすくすることができます。
4. 短絡評価の悪用
// PHP
// NG
if ((!(bucket = FindBucker(key))) || !bucket->IsOccupied());
// OK
bucket = FindBucket(key);
if (bucket != Null) {}
続いて、短絡評価についです。
短絡評価は左が真であれば、右は評価しないというものですが、1つ目のifのようなコードは可読性が著しく悪いです。
それよりも下に記述されているコードの方が可読性が高いことは一目瞭然です。
「頭のいい」コードを目指すのではなく「読みやすいコード」を心がけましょう。
5. 複雑なロジックと格闘する
続いては、複雑なロジックと格闘するです。
複雑なロジックを抜け漏れなく且つ、シンプルに実装するにはどうすべきか?ですが、
複雑怪奇なコードは誰も読んでくれないし、本当に正しいのか初見では判断が難しいです。
そういった時は「反対」から問題を解決してみるという手があります。
例えば配列を逆順にイテレートしてみるとか、データを後ろから挿入してみるなどです。
例えば、「2つの範囲が重なるのか」という目的の場合、その反対は「2つの範囲が重ならない」になります。
6. DRYの原則
続いては「DRYの原則」です。
DRYの原則とはDon't Repeat Yourselfの略で、「繰り返しを避けること」という意味です。
重複したコードを抽出し、メソッドとして切り出したりすることですが、重複したように見えて実は重複していないというケースもあるので注意が必要です。
例えば、記述コードはほとんど一緒だが、投げるAPIが全く異なっているなどといったケースは、APIの仕様が変更された際に、吸収しきれなくなるため、重複したように見えて実は重複していないケースとなります。
変数と読みやすさ
- 変数が多いと変数を追跡するのが難しくなる
- 変数のスコープが大きいとスコープを把握する時間が長くなる
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる
変数を削除する
まず1つ目は「役に立たない一時変数は削除する」です。
「巨大な式を分割する」で取り上げた「説明変数」や「要約変数」はなぜ読みやすくなったのかというと、変数が巨大な式を分割して、説明文のような役割を果たしていたためです。
今回は「不要な変数」が使われていたら逆に読みにくくなってしまうので、削除しましょうという内容です。
変数のスコープを縮める
- グローバル変数は避ける
- 定義の位置を下げる
- 変数は一度だけ書き込む
まとめ
- 制御フローを読みやすくする
- 条件式をシンプルに
- 三項演算子は良し悪し
- 早期returnなどでネストを浅く
- 巨大な式を分割する
- 説明変数や要約変数で分割して名前に置き換える
- ロジックを単純にするため「反対」から問題を解決してみる
- 重複コードはまとめる(DRYの原則)
- 変数を読みやすさ
- 複雑な式を分割していなかったり、すでに明確である場合の一時変数は不要である
- フラグを作ったら負け
- 変数のスコープを小さく