mitsuのぶろぐ

基本的にはプログラミングの話のつもり。フロントエンドよりです。

ループ処理するなら forEach > for > while という順でmethodを検討すべき、と思っている理由

TL;DR

forEach などのarrayのインスタンスが持ってる「その配列の中身を操作する」メソッドの方が
無限ループ も起きないしいいよね、というところです。

はじめに

配列などの複数のデータに対して、何らかの処理をするとなった場合ループの処理を実装するかと思います。
その際に端的に言うとメソッドとして forEachforwhile あたりが選択肢になってくるかと思いますが、個人的にはforEach -> for -> while の順で使用するメソッドを検討したほうがいいと思ってます(forEachが使えるならforEachを使おう、使えないなら次にforが使えないか検討しよう、という意味です)

なぜ、この順番で検討したほうがいいかと思ってる理由を以下に記述してきます。
※一旦jsの話として書きますが、思想自体は他の言語でも共通だと思います。

ループ処理を使うときに意識したいこと

個人的に意識したいところとしては以下の2点です

無限ループ

意識したいこととして2点あげてますが、はっきりいってこれがメインの理由です。
警察につかまる騒動でも一時期話題になりましたが、ループでまわす処理が終わらずずっと続いてしまうあいつのことです。
クライアント側にしろサーバー側にしろメモリは食われて動作しなくなるし、100歩譲ってメモリが生きてても後続の処理は実行されなくなるので、絶対に避けたいところです。

デバッグのしやすさ

ループしたいデータの中身が絶妙にキレイではなく、特定のデータに対してバリデーションかけたりが必要だったりするパターンもあるかと思います。
その際に実行タイミングで確認したいとなったときに1件1件止めながらのチェックとかは、件数すくない場合はいいですけど、多くなるととてもじゃないけどそんなことはできません。
そのため確認がしやすい構造のほうがよいです。
※詳細は割愛します。

各methodの特徴

forEach

const sampleArray = [1, 2]
sampleArray.forEach((sample) => {
  console.log(sample)
})

jsで書くとなると上のようなかんじになります。

利点

確実に Array内の要素の個数 しか実行されません。 空の配列でも実行はされますが、エラーとかは履きません。 確実に無限ループを防げます。

またtypescript (× VSCode)の場合、

type SampleArrayType = {  // SampleArrayTypeという型の規定です
  id : number
  text : string
}

const sampleArray : SampleArrayType[] = [ // ※
  {
    id : 1,
    text : "Sample 1"
  },
  {
    id : 2,
    text : "Sample 2"
  },
]

sampleArray.forEach((sample) => {
  console.log(sample.hoge) // <- "hoge"なんていない、とEditor上で怒られる
})

// ※ ": SampleArrayType[]"の表記は はsampleArrayという変数は"SampleArrayType"という型の"配列"([])で変数定義します、という型定義です。
// そのため、
// {
//   id : "3",
//   text : "Sample 3",
//   name : "Lifebook"
// }
// とか突っ込むと「idがstringで定義されてるけど、SampleArrayTypeの型としてそれは駄目やで」と
// 「SampleArrayTypeの型にnameなんてプロパティ存在しないで」と怒られます

forEach内の sample の変数が型推論で自動的にSampleArrayTypeの型として認識され、エディター上でエラーを吐いてくれます。
実行時のエラーじゃなくて、実装時にわかってくれるのは修正コストが一番低いのでめちゃありがたいです。

欠点(?)

ループ数が確実にArrayの個数に依存するので、forとかよりは多少自由度はさがりますが、そこは実装次第な気がします。

for

const sampleArray = [1, 2]
for (let index = 0; index < sampleArray.length; index++) {
  console.log(sampleArray[index])
}

利点

上にも書いてますが、forEachよりは多少自由度はあるかなと思います。
スタートのindexを0ではない数字からスタートしたり、ループ数をindex < sampleArray.lengthではなく別の数字にしたい、などあればこちらをいじるといろいろできます。

欠点

ただし、個人的には上記をいじると無限ループのもとになりかねないと思っているので、それならforEach内で分岐書いたりしたほうがいいと思います
極端な話しですが、上の例でいうと let index = 3、 index > sampleArray.length と書いた瞬間に無限ループです。

const sampleArray = [1, 2]
for (let index = 3; index > sampleArray.length; index++) {
  console.log(sampleArray[index])
}

画面ロード時に必ず実行されるタイプの処理であればテストのタイミングで気づくかと思いますが、何らかの処理(特定のタイミングでボタンを押した際に実行など)をしたタイミングでループがまわるものとかは時折テスト漏れで本番時に発覚するなどありえるため、実行回数が必ず決まっているもののほうがよいなと思います

While

const sampleArray = [1, 2]
let count = 0
while( count <= sampleArray.length){
  console.log(sampleArray[count])
  count++
}

利点

正直上の例文ではwhileの必要性はまったくないですし、冗長になってますが、再帰的に何かを処理したり、ループしたい内容がループの処理をした結果増減する(ループする回数がループ前に確定していない)ものとかだとwhileを使わざるをえないかと思います

欠点

この3つのメソッドの中で一番取り扱いに注意が必要だと思っています。
上記forの話での無限ループの記述は若干冗談を交えているところはありますが、(とはいえ可能性は全然あると思ってます)
上のwhileの場合、 count++ の処理を消した瞬間に無限ループなので、本当に怖いと思ってます。

終わりに

ということでループ処理についてちょっとまとめてみました。
無限ループのくだりは初期実装時というよりも、とくに修正タイミングとかで如実になる内容かと思うので、メンテナンス性の高いコードを書くという点では意識するとよいのかな〜と思います。