見出し画像

【脱・初心者🔰】スパゲッティコードを生まないコーディング術5選!【JavaScript】

どうもお久しぶりです、Keshitanです!

皆さんはプログラムを書いていて、「どこがどうなって、何が起こっているか、ワッッッかんねー!!!」……となった経験、ありませんか?

そういったときのコードの様相とは、まぁ"想像を絶するもの"で、ぐっちゃぐちゃのスパゲッティコード🍝となっていますよね。。。最終的にはファイル📄をそっ閉じしてゴミ箱🗑️の彼方へ……『新規ファイルを作成』までが王道ではないでしょうか?

今回は、『【脱・初心者🔰】スパゲッティコードを生まないコーディング術5選!【JavaScript】』と題しまして、そんなスパゲッティコードと"おさらば"するための、JavaScript初学者必見の内容を集めてみました!




"スパゲッティコード"

ソースコードの構造が複雑かつ難解で、
全容が把握しきれないもののことを
「スパゲッティのように絡み合っている」
としてスパゲッティコードと呼びならわすそうな。

🍝は何故生まれるのか?


「スパゲッティコードは何故生まれてしまう」のでしょうか?理由は十人十色千差万別でしょうが、単純なものとしては煩雑さを回避する書き方を知らないからというのがあるのではないでしょうか?特に初学者からすると尚のことだと考えます。

上級プログラマーであれば必ずといって良いほど「リファクタリング」、つまりは「ソースコードの整理・整頓」を行っています。

この記事でいう「コーディング術」とは、言わばJavaScript版便利収納術であり、本記事の内容は一番取っ付きやすいものに絞って紹介しています。


🍝を許すな!


そういえば何故、スパゲッティコードを回避したいのでしょうか?もっと言えば何故スパゲッティコードは嫌悪されるのでしょうか。

それはバグ潰しアプデ仕様変更を行うときに、嫌というほど困った経験から来るものでしょう。理由は単純明快、「何が書いてあるか分からない!」これに尽きます。つまるところ、

(何が書いてあるか分からないから)
 バグが生まれやすい。
(何が書いてあるか分からないから)
 バグが探せない。
(何が書いてあるか分からないから)
 追記しにくい。
(何が書いてあるか分からないから)
 変更すべき箇所が分からない。

ということなのです。

スパゲッティコードは、個人単位であれば"将来の自分"が、チーム単位であれば"チーム全員"が困り果て、生産性がダダ下がりとなります。コーディング術とはプログラミングにおける必須テクニックなのです。


脱・初心者🔰のコーディング術

JavaScriptにおける初歩の初歩的
コーディング術を5つ紹介します。
きっとすぐにでも実践できる内容でしょう。

💠脱・var宣言!


"var"宣言とは、これのこと:

Before

var xxx = 1234
var yyy = 9876

/* 処理 */

console.log(xxx, yyy)

その他の宣言方法はこんな感じ:

After

const xxx = 1234
let yyy = 9876

/* 処理 */

console.log(xxx, yyy)

つまり、言いたいことはそのまんま!


ver num = 15

という再代入・再宣言可能であるvar宣言をやめましょう!


ということです。では何故「var宣言はNG」なのか?それは全ての処理に目を通さないと値が確定しないからです。例えばBefore/* 処理 */が以下のような実装だとしましょう:

Beforeの/* 処理 */

xxx += 4321
var yyy = 6789

このときのBefore最終行console.log(xxx, yyy)による出力結果はどうなるでしょうか?

Beforeの出力結果

5555 6789

つまりはvar宣言は「自由に」再代入ができちゃうし、「自由に」再宣言もできちゃうしで、道中やりたい放題できてしまう宣言なのです。

道中やりたい放題できるとどうなるか?変数が想定通りの値であるか、「「「一見関係がなさそうな箇所でも」」」逐一読み進めないといけないのです。これでは非常にストレスが溜まりますし、可読性が著しく低いです。

それでは他方の宣言、let及びconstはどうでしょう。After/* 処理 */が以下のような実装だとしましょう:

Afterの/* 処理 */

xxx += 4321
let yyy = 6789

すると出力結果はErrorになります。これはの行もの行もエラーです。つまりはlet宣言やconst宣言は、再代入や再宣言を制限し、値の確実性を担保してくれているのです。言い換えれば「うっかり代入」「うっかり宣言」を防ぎます。

また、値の確実性という観点では、

const>>>>(越えられない壁)>>>>let>>>var

であるため、原則定数(const)宣言で、最終手段としてletを用いましょう。各種宣言の違いについて、表形式でまとめておきます:

$$
\begin{array}{c|ccc}
& \mathrm{const} & \mathrm{let} & \mathrm{var} \\ \hline
再代入 & ✕ & 〇 & 〇 \\
再宣言 & ✕ & ✕ & 〇
\end{array}
$$


ちなみに上記でいう「最終手段」としてのletは
主にfor文で見かけることとなるでしょう。
上記表より、constは再代入不可でletは再代入可能
であるため、for文での「n巡目」の表現には
letの存在が不可欠となります。


💠脱・連結演算子!


"連結"演算子ってありましたっけ。あぁ、こういうの?:

Before

const unit = 100_000
const populations = {
  "JPN": 1_244 * unit,
  "USA": 3_400 * unit,
  "BRA": 2_164 * unit,
  "GBR": 677 * unit,
  "EGY": 1_127 * unit,
  "AUS": 264 * unit,
}

const question =
"ブラジルの総人口は、日本と豪州と英国の総人口よりも多い。〇か✕か。"

const answer =
"✕:ブラジルの総人口は" + populations.BRA + "人である一方、\n"
+ "日本・豪州・英国は" + populations.JPN + populations.AUS + populations.GBR + "人である。"

console.log(question)
console.log(answer)

「+」祭りで読みにくいコードだなぁ…。これを読みやすく変えるとこんな感じ:

After

const unit = 100_000
const populations = {
  "JPN": 1_244 * unit,
  "USA": 3_400 * unit,
  "BRA": 2_164 * unit,
  "GBR": 677 * unit,
  "EGY": 1_127 * unit,
  "AUS": 264 * unit,
}

const question =
"ブラジルの総人口は、日本と豪州と英国の総人口よりも多い。〇か✕か。"

const answer = 
`✕:ブラジルの総人口は${populations.BRA}人である一方、
日本・豪州・英国は${populations.JPN + populations.AUS + populations.GBR}人である。`

console.log(question)
console.log(answer)

つまり、ここでの主張はこういうこと。


const str = "合計:" + a + b

という「+」を使った文字列の結合をやめましょう!


ということです。なぜ文字列の"結合"としての「+」を利用してはいけないのか?それはBeforeを実際に実行してみれば分かります:

Beforeの実行結果

ブラジルの総人口は、日本と豪州と英国の総人口よりも多い。〇か✕か。
✕:ブラジルの総人口は216400000人である一方、
日本・豪州・英国は1244000002640000067700000人である。

……はい。桁数がバクり散らかしました。これでは、人口の超新星爆発で食糧危機どころではないですね。

なぜこのようになるか?理由は単純で、暗黙の型変換が行われたからです。Beforeで言えば、

// 文字列 + 数値 -> 連結
"日本・豪州・英国は" + populations.JPN

//"日本・豪州・英国は124400000"

となり、知らず知らずのうちに数値であるpopulations.JPN文字列に変換されています。後続の総人口数も同様に変換されて意図しない結果となります。

一方で、Afterテンプレートリテラル表記を用いることで、正確性や可読性が向上しています:

`✕:ブラジルの総人口は${populations.BRA}人である一方、
日本・豪州・英国は${populations.JPN + populations.AUS + populations.GBR}人である。`

//✕:ブラジルの総人口は216400000人である一方、
//日本・豪州・英国は218500000人である。

テンプレートリテラルは「`      `」(バッククォート)で囲み、埋め込みたい箇所を「${        }」で指定するだけです。たったこれだけで得られる恩恵は非常に大きいので、是非ともマスターしたいですね。


ちなみに数値の計算部を丸括弧で囲めば
「+」を用いても正しい出力結果に
なりますが、やはり可読性に難あり
なので、あまりおすすめはできません。


💠脱・マジックナンバー!


"マジック"ナンバー??そんなのあるんですか!え、どれのこと?:

Before

const inputYourGenderCode = 2

if (inputYourGenderCode === 1) {
  /* 処理 */

} else if (inputYourGenderCode === 2) {
  /* 処理 */

} else {
  /* 処理 */

}

何がマジックなんだか不明なんですけど!でも、言いたいことは"コレ"で何となく分かるはずです:

After

const genderDict = {
  "unknown": 0,
  "male": 1,
  "female": 2,
  "other": 9
}

const inputYourGenderCode = 2

const isMale = inputYourGenderCode === genderDict.male
const isFemale = inputYourGenderCode === genderDict.female

if (isMale) {
  /* 処理 */

} else if (isFemale) {
  /* 処理 */

} else {
  /* 処理 */

}

さぁどうでしょう、結構違いますね。その中でも特に主張したいこと、それは…


if (statusCode === 404) {
  /* 処理 */
}

という"状態"を表す定数のべた書きをやめましょう!


ということです。なぜ「状態を表す定数のべた書き」はNGなのか?それは第三者からすれば暗号でしかないからです。Beforeにツッコミを入れると、こういうこと:

const inputYourGenderCode = 2

// 「1」に等しいから何?
if (inputYourGenderCode === 1) {
  /* 処理 */

// 「2」に等しいと何をする??
} else if (inputYourGenderCode === 2) {
  /* 処理 */

// 「それ以外」って何???
} else {
  /* 処理 */

}

第三者目線、不親切の極みですね。これを目に優しくしたAfterは、このように読めるでしょう:

// 性別と数値を対応付けているのか。
const genderDict = {
  "unknown": 0,
  "male": 1,
  "female": 2,
  "other": 9
}

const inputYourGenderCode = 2

// 性別コードに合致するか見ているな。
const isMale = inputYourGenderCode === genderDict.male
const isFemale = inputYourGenderCode === genderDict.female

// 最初は男性の場合で、
if (isMale) {
  /* 処理 */

// 次に女性の場合。
} else if (isFemale) {
  /* 処理 */

// 最後はその他の場合か。
} else {
  /* 処理 */

}

第三者が読んで意図の分からないコードは、忘れた頃の貴方も困り果て、しまいには「誰だ!こんなスパゲッティコード🍝を書いたのは!!!!」と言ってファイル📄をゴミ箱🗑️に放り投げることでしょう。親切な書き方は、記述量の増加を招きますが、可読性の圧倒的な向上によりQoLは爆上がりです。

マジックナンバーは経験則的にif文で見られがちです。定数(const)宣言を活用して初心者を脱していきましょう!


ちなみにマジックナンバーの"マジック"とは
所謂皮肉の一種で、「意図は分からんが正常に
動作するし、まるで魔法みたいだな!」という
皮肉の意が込められているそう。対象が文字列なら
マジックワードと言ったりなんだり。


💠脱・古典的forループ!


"古典的"forループ??なんすか古典的って。例えばこんなヤツ:

Before

const blackList = ["xxxxxxxxxxx", /* ..., */ "xxxxxxxxxx"]

let phoneNumberList = ["xxx-xxxx-xxxx", /* ..., */ "xxxx-xx-xxxx"]
for (let i = 0; i < phoneNumberList.length; i++) {
  phoneNumberList[i] = phoneNumberList[i].replace(/-/g, "")
}

for (let i = 0; i < phoneNumberList.length; i++) {
  if (blackList.includes(phoneNumberList[i])) {
    console.log(`🚫NG: ${phoneNumberList[i]}`)
  } else {
    console.log(`✅OK: ${phoneNumberList[i]}`)
  }
}

for文がいっぱいで、うーん。。。これは読む気失せますねぇ。これを「・初心者🔰」するとこんな感じ:

After

const blackList = ["xxxxxxxxxxx", /* ..., */ "xxxxxxxxxx"]
const phoneNumberList = [
  "xxx-xxxx-xxxx",
  /* ..., */
  "xxxx-xx-xxxx"
].map(phoneNumber => phoneNumber.replace(/-/g, ""))

phoneNumberList.forEach(phoneNumber => {
  if (blackList.includes(phoneNumber)) {
    console.log(`🚫NG: ${phoneNumber}`)
  } else {
    console.log(`✅OK: ${phoneNumber}`)
  }
})

この例で何を伝えたいかお分かりでしょうか??そうです。


for (let i = 0; i < array.length; i++) {
  /* 処理 */
}

という"古典的な"for文の書き方をやめましょう!


ということです。「なぜ古典的か?」という疑問は後に回すとして、ここではひとまず、「古典的=手続き的」としましょう。

古典的なfor文とは所謂、手続き的な書き方というもので、分かりやすく言うなれば「むかしむかしある所に…」と1つ1つ事柄を列挙するイメージです。つまりはBeforeでは、こういうこと:

let phoneNumberList = ["xxx-xxxx-xxxx", /* ..., */ "xxxx-xx-xxxx"]
// 回します、iは最初0で、iはphoneNumberList.length未満で、iは1ずつ増加
for (let i = 0; i < phoneNumberList.length; i++) {
  // phoneNumberListのi番目要素はハイフン(-)を除去されます
  phoneNumberList[i] = phoneNumberList[i].replace(/-/g, "")
}

一方、手続き的ではない方、ここでは「宣言的」としましょうか。Afterでは、このように読めます:

const phoneNumberList = [
  "xxx-xxxx-xxxx",
  /* ..., */
  "xxxx-xx-xxxx"
// phoneNumberListは上記の各要素からハイフン(-)を除去した配列
].map(phoneNumber => phoneNumber.replace(/-/g, ""))

ここでのポイントはAfterを日本語化したときに、ほぼ一文で説明ができる内容だということです。それはmethodメソッド動詞で命名されることから容易に分かります。言ってしまえば英文の構造と同様で、

array主語 .map動詞 (func)なんか色々.

と考えられます。主語、動詞、、、と来れば99.9%の確率で一文として完結するでしょう。methodメソッド簡潔に書く能力に優れます(Wミーニング、ここで爆笑)

また、古典的なfor文では書き分けられなかった目的の違いもメソッドにより表現できます。.mapを読む時点で「forはforでも配列が欲しいんだな」というのが見て取れ、.forEachを読む時点で「とりあえずこの配列の要素を回していきたいんだな」と意図が明瞭に分かります。

つまるところ、脱・初心者の近道Arrayのメソッドを知るところにあるかもしれません。


ちなみになぜ"古典的"か、それはプログラミング言語
の始祖的存在『C言語』由来の記法であるためです。
50年程前からある構文なので由緒正しいと言えば
聞こえは良いですが、悪く言えば古めかしく時代遅れです。
教育上の観点では避けて通れませんが、jsではまぁ使いません。


💠脱・破壊メソッド!


メソッド教信者の皆様、一つ謝らせて下さい。"破壊"メソッド信者は異教徒です:

Before

const piNumbers = [ 3, 1, 4, 1, 5, 9, 2 ]
const piMainNumbers = items.splice(0, 4)

console.log(piMainNumbers, piNumbers)

ちょっと過激派ですか?でも次のコードを読めば分かるはずです:

After

const piNumbers = [ 3, 1, 4, 1, 5, 9, 2 ]
const piMainNumbers = items.slice(0, 4)

console.log(piMainNumbers, piNumbers)

ん???よく分からない???違いはメソッドがsplice->sliceとなっているところですよ。言いたいことはこういういうことで、


items.splice(0, 4)

や、

items.reverse()

といった"破壊"メソッドの使用をやめましょう!


ということです。ただのメソッドに過ぎないこれらが何故"破壊的"であるのか?それはBeforeの実行結果と、Afterの実行結果の比較により判明します:

Beforeの実行結果

[ 3, 1, 4 ] [ 1, 5, 9, 2 ]

Afterの実行結果

[ 3, 1, 4 ] [ 3, 1, 4, 1, 5, 9, 2 ]

つまりは、メソッドsplicesliceは共に「配列の一部を返す」という点では同一であるが、spliceは同時に「元の配列に変更を加えてしまう」ものであったということです。言わば、

Before第2行

const piMainNumbers = items.splice(0, 4)

というつの宣言には、つの操作が混在していたということになります。勿論、これにより可読性は下がります。実質的に再代入となんら変わりがないわけなので。

従って、メソッドは簡便な表現ですが、そのメソッドが「「「破壊的であるか否か」」」という視点は使う上で重要です!そして、使用はできるだけ避け、破壊メソッドを利用しましょう!


ちなみに破壊メソッドを"安全に"利用する方法があります。
それは[…items].reverse()という、スプレッド構文
…itemsを利用する方法です。原理は単純で、
使い捨ての新しい配列を作って破壊しているだけです。
しかし、今のJSではitems.toReversed()で解決できる
ように、非破壊versionのメソッドも用意されています。


おまけ


今回選ばせて頂いた脱・初心者のためのコーディング術5選は、「宣言->演算子->if文->for文->メソッド」という初学者が学ぶであろうガイドに沿ってピックアップしました。個人的には選りすぐりのものを選んだつもりですが、参考になりましたでしょうか。

5選の内容には含みませんでしたが、一番のコーディング術は"休息を取ること"コレ一択です。最新の研究でもヒトの脳は休息時にタスクの整理を行うそうで、騙されたと思って休んでみてください。一番効きます!!!



こんな感じでゲームプログラミングを中心としてnote記事を執筆していますので、もし気に入って頂けましたらスキ・フォロー・コメントをして頂けると幸いです!

それではまたの機会に。


この記事が参加している募集

最後までお読み頂き、誠にありがとうございます。記事の内容にご満足頂けましたら、是非ともサポートのご検討をお願いしたく思います! もしご支援頂ければ、細々と書き綴るnote活動に豊かさが生まれます。頂きました支援金は、存続のための活動資金として大切にお使いしたく思います。