💭

【JavaScript・TypeScript】配列の操作はなるべくfor文を避けるべきと思う理由と代わりに使うメソッドの例

2024/09/15に公開

タイトルの通り、配列の操作を行う際は、for文をなるべく用いない方が良いと思っています。
主な理由はコードの見通しが良くなるためです。

具体的なコードを用いて解説していきます。

配列の要素から1つ条件を満たす要素を返す場合は、find()を用いる

まず、以下のコードをご覧ください。
ネストが深すぎて、読むのが嫌になると思います。

const findValidItem = (items: number[]): number | undefined => {
    if (items.length > 0) {
        for (let i = 0; i < items.length; i++) {
            if (items[i] > 0) {
                if (items[i] % 2 === 0) {
                    if (items[i] < 100) {
                        return items[i];
                    }
                }
            }
        }
    }
    return undefined;
};

同じ意味のコードが、以下のように書けます。

const isValidItem = (item: number): boolean => 
    item > 0 && item % 2 === 0 && item < 100;

const findValidItem = (items: number[]): number | undefined => 
    items.find(isValidItem);

上記のように書けば、正の偶数で100未満の数値が1つ返ってくるコードと簡単にわかると思います。
ここで用いているfind()は、配列内で条件を満たした最初の要素を返すメソッドです。

配列の要素から複数の条件を満たす要素を返す場合は、filter()を用いる

まず、以下のコードをご覧ください。
何がしたいのか読み解くのも嫌だと思います。

const filterItems = (items: number[]): number[] => {
    const result: number[] = [];

    for (let i = 0; i < items.length; i++) {
        if (items[i] > 0) {
            if (items[i] % 2 === 0) {
                if (items[i] < 100) {
                    result.push(items[i]);
                }
            }
        }
    }

    return result;
};

同じ意味のコードが以下のように書けます。

const isValidItem = (item: number): boolean => 
    item > 0 && item % 2 === 0 && item < 100;

const filterItems = (items: number[]): number[] => 
    items.filter(isValidItem);

上記のコードであれば、引数として受け取った配列の中から、正の数で100未満の偶数を全て残した配列が返ってくるコードだとわかると思います。
ここで用いているfilter()は、配列から引数として受け取った条件を満たす配列の要素のみを抽出して、新しい配列を作るメソッドです。

配列の要素を並べ替える場合はsort()を用いる

以下のコードをご覧ください。
相変わらずネストが深い上に冗長で、読みにくいコードかと思います。

interface Book {
    title: string;
    publishDate: Date;
}

const sortBooks = (books: Book[]): Book[] => {
    const sortedBooks = [...books];

    for (let i = 0; i < sortedBooks.length - 1; i++) {
        for (let j = i + 1; j < sortedBooks.length; j++) {
            if (sortedBooks[i].publishDate < sortedBooks[j].publishDate) {
                const temp = sortedBooks[i];
                sortedBooks[i] = sortedBooks[j];
                sortedBooks[j] = temp;
            }
        }
    }

    return sortedBooks;
};

同じ意味のコードが以下のように書けます。

interface Book {
    title: string;
    publishDate: Date;
}

const sortBooks = (books: Book[]): Book[] => 
    [...books].sort((a, b) => b.publishDate.getTime() - a.publishDate.getTime());

これなら、本の出版日が新しい順に並び替えている処理だとわかると思います。
ここで用いているsort()は、引数で定義したソート順で配列の並び順をソートするメソッドです。
引数は省略することもでき、省略した場合は、全ての配列要素を文字列に変換し、文字コードに従ってソートされます。

※なので、ほとんどの場合は引数でソート順を定義することになると思います。
日付や数値の大小に従って並び替えることがほとんどだと思うので、、、

最後に

サンプルコードの冗長な例で、if文の条件を無駄に分割しているので、「本当にそんな構造のコードに実務で触ることになるのかな?」と疑問に思われる方もいらっしゃるかと思いますが、このような構造のコードに実務で私は触れたことが過去にあります。
(言語はTypeScriptではなくC#で、実務なのでもっとif文の配下にたくさんコードが書かれていました。
このような状況下で障害調査やバグの修正を行なっていたので、しんどかったです、、、)

for文を使わないことによってネストが一段深くなるのを避けることができる上、
簡潔に書くことができて保守するのが楽になると思います。

過去の私のような辛さを味わう人が減るよう、読みやすいコードを書いていきたいと思います。

Discussion