#変数に泣かされた事ありますか?
変数はいい大人でも泣かしにかかってきます。
リリース直前であろうと、某ジャ〇アンのようにギッタンギッタンにしてきます。
大抵は意図していないタイミングで書き換えられ、その結果が他のロジックに引き継がれ
結果、微妙にズレた結果となり…テスト結果は
プログラミングの初歩中の初歩である変数で、泣かされるなんて馬鹿らしくないですか?
良い機会なので復習してみませんか?
#本記事について
##読み方
重要なことだけ教えろって方は、大項目と強調文字だけ読んでください。
- 駆け出しエンジニア、2-3年目のエンジニア
- リーダブルコードを書きたい方
- 変数の書き方について復習する。
- より良い変数の書き方を理解する。
##注意点
- サンプルコードはJavaScriptで書いています。
- すべての言語に適用可能でない可能性があります。
- 業務でコーディングする際は、プロジェクトのコーディング規約に従ってください。
- 速度を求められる汎用的なライブラリや専門的な処理はパフォーマンス重視のため、本記事の内容は適用されません。
#なぜ変数に泣かされるのか?
原因は決まってよくあるパターン。つまり「ヒューマンエラー」です。
そのヒューマンエラーを発生させないためにも、工夫が必要となるわけです。
長いコードを読んで、長いコードを書いて、エラーを吐かずに運よく動作してしまったら、中々気づきません。
そこでバグを見つけやすくするために、読みやすいコード(リーダブルコード)を書く必要性が出てくるというわけです。
#リーダブルコード
##リーダブルコードは、なぜ間違いに気づきやすいのか?
「リーダブルコードを書くとバグが減る?どういう事?」と駆け出しエンジニアの方は思うかもしれません。
リーダブルコードというのは、文章と変わりありません。
日本語で極端に例えると、こうです。
おはようございます。
いい天気ですね。
今日も朝から元気出して頑張りましょう。
こんばんは。
いい天気ですね。
今日も朝から元気出して頑張りましょう。
違和感ありますね?
「こんばんは。」と挨拶してるのに「朝から元気だして…」と。
今って夜?朝?どっちやねん!日本語間違えてるやろ?ってなります。
このようにリーダブルコードは読みやすいだけでなく、コードの「違和感」を感じることができるのです。
つまり、実行しなくても流し読みするだけで「違和感」を感じ、ここに不具合があるのでは?とアタリをつけやすいのです。
リーダブルコードを書くには、経験や練習が必要です。
様々な原則やノウハウの上に成り立っているものであり、一気に実践していく事はオススメしていません。
オススメしない理由は、書き方を徹底的に理解した上でステップアップしないと癖付かないからです。
今回はローカル変数のみにフォーカスを当てて、リーダブルコードを意識した書き方をしていきましょう。
#良いローカル変数の書き方
##変数の再代入禁止
###サンプル
とりあえず例を見てみましょう。
fryメソッドは引数を焼くメソッドだと思ってください。
let egg = '卵';
let friedEgg = fry(egg); // 新しい変数を定義して代入している。
console.log(friedEgg); // 「目玉焼き」と出力される。
let egg = '卵';
egg = fry(egg); // 変数の再代入をしている。
console.log(egg); // 「目玉焼き」と出力される。
悪いコードの良くない点はどこでしょうか?
簡単です。「卵」を焼き「目玉焼き」にしたのにも関わらず、「egg」に代入している点です。
「目玉焼き」を「egg」なんて変数に代入すると、「卵」だと人は勘違いをしてしまう人もいるでしょう。
これが不具合を生み出すヒューマンエラーの原因の一つです。
対して、良いコードはfryメソッドで焼いた後「friedEgg」、つまり「焼いた卵」と分かりやすい名前に代入しています。
再代入を禁止するとはどういうことか?
このように「状態」が変化したものに、同じ名前をつけると「違和感」を感じます。
つまり、再代入を禁止するということは、「状態」は変化しているんだから、適切な名前を付けなさいという意味もあります。
言語で変数の再代入禁止をサポートしているものは、積極的に使う方がよいでしょう。(javascriptのconst, Kotlinのval等)
const egg = '卵';
// egg = fry(egg) // eggに再代入しようとするとエラーとなる。
const friedEgg = fry(egg); // 新しい変数を定義して代入している。
console.log(friedEgg); // 「目玉焼き」と出力される。
###例外
for文で使うiや合計のsumなどは再代入しないとロジックが実現できないため、この「変数の再代入禁止」の例外です。
その他にも非常に大きなデータ(画像データ等)を扱う場合は、例外対象となります。
また今回は解説しませんが、再代入を禁止してもコレクション(リストやマップ)やクラス等は中身の要素が変化することがあります。
##意味を持たない初期化禁止
###サンプル
とりあえず例を見てみましょう。
// ----色々な処理----
const name = 'Taro';
console.log(name);
let name = ''; //とりあえず初期化
// ----色々な処理----
name = 'Taro';
console.log(name);
無意味な初期化は、前述の「変数の再代入禁止」にも違反します。
空文字は特に意味を持たず、ただ初期化するためだけに使っています。
###意味を持たない初期化を禁止するとはどういうことか?
nullや空文字のような意味のないもので初期化していたとしても、コード読む人からすれば「意味を持たない初期化」と言う事が分かりません。
頭の中で「nameは空文字だ…空文字だ…」なんて思いながら読み進めていると、脳のような低スペックコンピュータはすぐパンクします。
そうならないためにも、変数は必要になってから、初期化し利用すべきです。
そうすれば、意味を持たない初期化を必要としなくなるはずです。
もし、null等で初期化したくなる場合は、変数を宣言するタイミングやロジックがそもそも良くないコードになっている事を疑ってください。
###例外
ライブラリの制約等の理由でどうしてもnull等の意味を持たない初期化を行いたい場合があります。
その場合は、その言語の特性を良く理解した上で使用してください。
(nullを使用しなくていいように機能が用意されている場合があります。)
##長い変数名を付けない
###サンプル
とりあえず例を見てみましょう。
fry(egg, true)で硬めの目玉焼きを作れるとしましょう。
function createFriedEgg(isHard) {
const egg = '卵';
return fry(egg, isHard);
}
const forDad = createFriedEgg(true); // 父の分
const forMom = createFriedEgg(false); // 母の分
console.log(`父の${forDad}`); // 「父の固めの目玉焼き」と出力される。
console.log(`母の${forMom}`); // 「母の目玉焼き」と出力される
const eggForDad= '卵';
const friedEggForDad = fry(eggForDad, true);
console.log(`父の${friedEggForDad}`); // 「父の固めの目玉焼き」と出力される。
const eggForMom = '卵';
const friedEggForMom = fry(eggForMom, false);
console.log(`母の${friedEggForMom}`); // 「母の目玉焼き」と出力される
悪いコードは、純粋な処理が見えにくくなっています。
それに対して、良いコードの方はどうでしょう?
メソッド抽出を行い、処理の結果を「forDad」「forMom」に代入するだけで「作った目玉焼きは父/母の分」と言う事が読み取れると思います。
読みやすいと思いませんか?
###長い変数名を付けないとはどういうことか?
メソッドも変数のように「何をどうするか」という目的をはっきりさせる事で文脈を作り出します。
それを受け取るのが変数ですが、メソッドに書かれている情報と重複する内容を書くと、回りくどく読みにくくなるのです。
つまり、この変数は「父/母の分」という一番大事な本質だけを主張しています。
const forDad = createFriedEgg();
変数はそのスコープの中で一番主張したい事だけを名前にする事で、非常に読みやすくなるのです。
(情報欠落しすぎるリスクはありますが、型で補えば良いという話はまた次回以降に…)
もし「forDad」だけでは読みにくくなりそうな状況があったとすれば、それはクラス抽出やメソッド抽出を行って最適な処理にまとめ上げてない場合です。
クラスやメソッドへ適切に処理をまとめる事によって、スコープ(変数の有効範囲)が小さくなり、変数に本質的な名前をつけることができるようになります。
変数名に本質以外の情報を持たせたくなった場合は、まずはクラスやメソッド等の作りの悪さを真っ先に疑いましょう。
変数名にその解決を押し付けてはいけません。
(3単語くらいの変数名を付けたくなった時は、かなり怪しいはずです。)
##変数名に型を書かない
###サンプル
まず例を見てみましょう。
const egg = '卵';
const count= 10;
console.log(`${egg}を${count}個買いました。`);
const stringEgg = '卵';
const numberCount = 10;
console.log(`${stringEgg}を${numberCount}個買いました。`);
型を書いてくれて、なんて親切!なわけはなく…
パッと見て、変数名から意味を読み取れないレベルに可読性が落ちています。
このレベルまで落ちた長文のソースコードは「違和感」にも気づかないでしょう。
つまり、大体このようなコードの中に不具合が発生する確率が高くなるわけです。
###変数名に型を書かないとはどういうことか?
意味はそのままです。変数名に型は不要です。
変数名に型をつけていた習慣があったのは、昔の開発環境が貧弱でコードを遡らないと型が分からなかったからです。
現在のIntelliJやVSCodeのようなIDEを使用して開発する際は、下記のように型がリアルタイムに分かるような手段があるため、変数名に型をつけて可読性を下げるような事は避けた方が良いと思います。
まとめ
紹介したものは、良いローカル変数を書くための方法のほんの一部です。
(変数名に適した英単語や、その他のルール等もQiitaや書籍に載っていると思います。必要に応じて本記事に追記します。)
ローカル変数は何気なく使う小さな道具ですが、使い方を理解し工夫するだけで読みやすいコードになります。
半年後の自分、仲の良いプロジェクトメンバー、保守を担当する方が読む事を思って、工数の許す限りリーダブルコードを書くように心がけましょう。
a, bやそれっぽい名前を付けれいれば、私は理解できるから問題ないという方がいらっしゃれば…
ぜひ半年後に改修を行ってください!
**変数を笑う者は変数に泣くぞ。**本当に…
#おまけ
特に動作もしない意味のないコードですが、今回紹介した内容を使ったものです。
まだ読みやすくないでしょうか?
特にそれぞれの処理に意味はありません。
(クラスやメソッドを上手く使えば、もっと読みやすいリーダブルコードになります。)
function createBreakfast(isHardFriedEgg) {
const bread = 'パン'
const toastedBread = toast(bread)
const sausage = 'ソーセージ'
const boiledSausage = boil(sausage)
const egg = '卵'
const friedEgg = fry(egg, isHardFriedEgg)
return [toastedBread, friedEgg, boiledSausage]
}
function createBabyBreakfast() {
const milk = 'ミルク'
return warm(milk)
}
const forDad = createBreakfast(true)
const forMom = createBreakfast(false)
const forBaby = createBabyBreakfast()
startBreakfast(...[forDad, forMom, forBaby])
#後書き
とりあえず、多くの言語に共通しそうなローカル変数について、後輩向けにメモとして書きました。
敢えて、クラス等は使用せずに解説しているため、分かり辛かったり違和感があったと思います。
(サンプルコードは、ルールを説明するために雰囲気で書いたので間違ってる可能性あります。JavaScript苦手。)
また、「違和感」や「リーダブルコードの良さ」にフォーカスを当てたかったため、実践的なサンプルではありません。
業務ロジックの「違和感」は、言語やプロジェクトの慣れによるところも大きいので、日常生活内で分かるようなサンプルにしています。
「駆け出しエンジニアの方」「まだ業務に慣れていない方」「コードレビュー文化のない現場で悩んでいる方」のお役に立てれば幸いです。