条件文とループを読みやすくする
はじめに
こんにちは、SHIFT の開発部門に所属しているmurasawaです。 中途で入社、バックエンド関連の開発を担当しています。
現在、認証基盤の開発を行っています。
開発の中でDB等、新たに学んだり得た知見をアウトプットし理解を深めていくとともに技術の共有として役に立てば幸いです。
今回、自身のきれいなコードを書く能力を上げたいと思い、読んだ本「リーダブルコード」から学んだことをアウトプットしていきます。
ある特定のコード規約があるわけではなく、コードを書く際にきれい、あるいは見やすい、共有しやすい書き方をまとめたものになっています。
エンジニアの仕事としてチームや職種にもよるとは思いますが、基本的にはコードを書く時間よりも読んで理解する時間・機会のほうが多いと考えています。
リーダブルなコードにより、読む際の理解を迅速に行うことができることで本筋であるコードを書くことに時間を使え、
チームとしての生産性も上がる可能性があります。
チームやプロジェクトでコード規約がある場合はコード規約に従っていただき、コード規約外での実装や、コード規約を決める際の指標にしていただければ幸いです。
また、現在主流の記述とずれがある場合がありますが、あくまで説明のためのコードであるので、本題理解のためにご了承お願いいたします。
内容は多くの言語に適用できるものですが、例にはjavascriptを使用しています。
条件式とループ
ループして条件によって処理を変えるなどループと条件は良く一緒に使われることが多いと思います。
ただ処理が複雑になるにつれてif文が増え、ループが増え、ループの中にif文を書くなど読みづらくなる場合が多々あると思います。
できるだけ相手に伝わりやすいように、意識できたらよいと本から学んだことを例示していきます。
条件式の引数
突然ですが、条件式の引数の並び順A,Bがあります
どちらが読みやすいでしょうか?
// A
if(length >= 10)
//B
if(10 <= length)
// A
if(bytes_received < bytes_expected)
// B
if(bytes_expected > bytes_recieved)
おそらく多くの人がAのほうが読みやすいと考えられます。
この処理を日本語で説明しようとするとAのほうが自然に訳すことができます。
例えばAは
// A
if(length >= 10)
もし、長さが10以上であれば
// A
if(received_value < expected_value)
もし、受け取った値が期待値より小さければ
と訳すことができると思います。
大してBは
//B
if(10 <= length)
もし、10が長さより小さければ
// B
if(bytes_expected > bytes_recieved)
もし、期待値が受け取った値より大きければ
と訳すことができると思います。
日本語としてAのほうが自然でわかりやすいことがわかります。
調査対象を左、比較対象を右としたほうが多くの人が違和感なく読むことができると考えられます。
if/elseブロックの順番
if/elseを書く際に
if ( name === 'taro' ) {
//第1の処理
} else {
//第2の処理
}
と書くのと
if ( name !== 'taro' ) {
//第2の処理
} else {
//第1の処理
}
と書くのは処理的には違いませんが、注目する場所や伝わる情報が違います。
一番上の条件が最も強調され目立ちます。
このifブロックの処理において
if ( name !== 'taro' )
が大事なのであれば先に来るべきであるし、 そうでないなら
if ( name === 'taro' )
肯定文ですっと頭に入ってくるほうが読みやすくなると考えられます。
以下のようにケースによっては
if ( !clients.item.length ){
...
} else {
...
}
if ( !clients.item.length )を一番目に置くことで
後続の処理でclients.itemを使う処理でなくでも頭の中に
clients.itemが残り考えてしまいます。
気を引く処理は先に片づけてしまうことで
if ( clients.item.length ){
...
} else {
...
}
不可が減り読みやすいと感じることが多くなると思います。
場合により判断が必要にはなりますが、
①その処理に取って重要な条件はどちらか
②目立つ条件を先に処理して、簡潔に考えられるようにする
など気にするとif/elseが読みやすくなると考えられます。
三項演算子
C言語などにもありますが、三項演算子というif/elseを簡潔に書ける場合があります。
if/elseで書くと
let isAdult;
if ( age >= 18 ) {
isAdult = true;
} else {
isADULT = false;
}
であるものが三項演算子を使うと
const isAdult = age >= 18 ? true : false;
と一行で簡潔に書くことができ可読性も高まります。
ただ次のようなケース
return number > 0 ? value * 1 / number : value * Math.abs(number) / 10;
は「行数を少なくするために1行にまとめよう」と考えるあまり読みずらくなっています。
こういった場合は単純に
if ( number > 0 ) {
return value * 1 / number;
} else {
return value * Math.abs(number) / 10;
}
としたほうが見やすく自然になります。
関数の中で条件を満たしたら早く返す(アーリーリターン)
関数の中でreturn文が一つしかないほうが良いと考えている人もいると思いますが、早く返せるのであれば早く返したほうが良いと考えられます。
return 一つでの極端な話をすると
const minNumber = (array) => {
let min = null;
if( array.includes(0) ) min = 0;
else if( array.includes(1) ) min = 1;
else if( array.includes(2) ) min = 2;
else if( array.includes(3) ) min = 3;
else if( array.includes(4) ) min = 4;
else if( array.includes(5) ) min = 5;
return min
}
と条件によって変数に代入して最後にreturnする方式だと
・最後まで読まないと何が返るのかわからない
ため、考えることが多くなります。
一方で、
const minNumber = (array) => {
if( array.includes(0) ) return 0;
if( array.includes(1) ) return 1;
if( array.includes(2) ) return 2;
if( array.includes(3) ) return 3;
if( array.includes(4) ) return 4;
if( array.includes(5) ) return 5;
return null
}
のように条件を満たして期待する処理が終わった都度returnすることで
分岐の処理一つ一つで何の値が返るかわかり、処理を追いやすくなります。
上記は極端な例ですが普段のコードでも同じです。
条件を満たし処理を終えたら変数に格納して処理を続けて最後にreturnしたり、 無理にelse ifで書くのではなく、都度返し処理を終わらせることで
覚えておくことが減り、読む人の負担を減らすことができます。
ネストを浅くする
ネストが深くなるとそれだけ考えることが多くなり理解しにくくなります。
if ( calcurate.result === 'success'){
if( initialize.result !== 'success' ) {
console.log('initialize error')
return false;
}
console.log('calcurate success');
return true;
}
上記のコードは
calcurate.result === 'success'
の状態で、
initialize.result !== 'success'
初期化が終わっていなかったら失敗したという結果:false
を返しています
ネストは深まれば深まるほど 頭にとどめておく情報が増加してしまいます。
なぜこのようなコードが生まれたのか
最初コードは
if ( calcurate.result === 'success'){
console.log('calcurate success');
return true;
}
単純なif文でしたが
途中でinitializeが成功していないと不正な結果になる可能性があることに気づき、
分岐を追加した
if ( calcurate.result === 'success'){
if( initialize.result !== 'success' ) {
console.log('initialize error')
return false;
}
console.log('calcurate success');
return true;
}
最も単純に修正でき、流れも一応わかる
ただこれが数か月後流れを忘れたほかのメンバーや自分が見たら読みづらいコードになっていると考えられます。
本来であれば、流れや単純な修正だからとこれを許容しないで、
initialialが失敗していたらcalcurate以前に失敗通知をしなければいけないのでは?
と考え
if( initialize.result !== 'success' ) {
console.log('initialize error')
return false;
}
if ( calcurate.result !== 'success'){
console.log('calcurate error')
return false;
}
console.log('calcurate success');
return true;
このように全体の行数が増えたとしてもネストが少なく、条件が単純に考えられるように前後の処理を見直すことも大事です。
終わりに
以上、条件分岐やループの際に読みやすいコードを書くために考えることをまとめました。
実際に開発を行っていて、過去指摘されたり、読みにくいと感じた要素が詰まっていました。
プログラミングは言語であり読むものです。
特別なものと考えがちですが、日本語、英語と同じように文法があり、読みやすい文読みにくい文があります。
そこを意識して、書くようにすればとよりチームとしてよいコードが書けていくのかなと考えるきっかけになりました。
お問合せはお気軽に
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
PHOTO:UnsplashのPankaj Patel