【第1部】 forループのネストから脱却しよう。Stream APIと早期リターンで実現する、読みやすいコレクション処理

この記事は、保守性の高いコード設計シリーズの一部です。これまでの記事では、条件分岐を整理するための様々なデザインパターンを解説しました。今回は、多くの条件分岐が潜む「コレクションのループ処理」に焦点を当て、ネストを解消し、可読性を高めるための具体的なテクニックを紹介します。

こんにちは。プログラミングにおいて、配列やリストといったコレクションの要素を一つずつ処理するforループは、基本中の基本です。しかし、ループ処理の中にif文による条件分岐が加わると、コードはあっという間にネストが深くなり、読み解くのが困難な「スパゲッティコード」になりがちです。

今回は、このようなforループのネストを解消し、コードの意図を明確にするための2つの強力なアプローチを学びましょう。

目次

1. そもそも、ループを書かない。Stream APIに任せる

まず考えたいのは、「本当にこのforループは手で書く必要があるのか?」ということです。多くのプログラミング言語には、コレクションを効率的に扱うための豊富な機能が備わっています。Javaでは、その代表がStream APIです。

【Before】forループで、特定の商品の有無を自前でチェックする

// カートに「魔法のポーション」が含まれているかチェック
boolean hasMagicPotion = false;
for (Product each : shoppingCart.getProducts()) {
    if (each.name().equals("魔法のポーション")) {
        hasMagicPotion = true;
        break;
    }
}

このコードは、「どのように(How)」探すかを逐一記述しています。しかし、私たちが本当に表現したいのは、「何がしたいか(What)」、つまり「特定の条件に一致する要素があるかどうかを知りたい」ということだけのはずです。

【After】Stream APIを使い、やりたいこと(What)を宣言的に記述する

boolean hasMagicPotion = shoppingCart.getProducts().stream()
                                    .anyMatch(product -> product.name().equals("魔法のポーション"));

Stream APIのanyMatchメソッドを使うことで、コードは一行になりました。forifbreakもありません。**「商品のストリームの中に、名前が『魔法のポーション』であるものと一つでも一致(anyMatch)するか?」**という、やりたいことそのものを表現するコードになり、意図が非常に明確になりました。


2. ループ内のネストをcontinuebreakで解消する

とは言え、複雑な処理では自前でループを書く必要がある場合もあります。そんな時でも、ネストを浅く保つテクニックがあります。

continueで不要な処理をスキップする

continueは、現在のループ処理を中断し、次の要素の処理に移るための命令です。これは、ループ内におけるガード節のように使うことができます。

【Before】ifのネストで、割引対象の商品にのみ処理を行う

for (Product product : products) {
    // 在庫がある商品についてのみ処理
    if (product.hasStock()) {
        // さらにセール対象の商品についてのみ処理
        if (product.isSaleTarget()) {
            // メインの処理:割引を適用する
            product.applyDiscount(0.1);
            // ...
        }
    }
}

【After】continueを使い、対象外のものを早期に除外する

for (Product product : products) {
    // 在庫がなければ、この商品の処理はスキップ
    if (!product.hasStock()) continue;

    // セール対象でなければ、この商品の処理はスキップ
    if (!product.isSaleTarget()) continue;

    // メインの処理
    product.applyDiscount(0.1);
    // ...
}

ifのネストがなくなり、平坦な構造になりました。「割引対象外」という異常系(前提条件)をループの先頭で除外することで、メインの処理が明確になります。

breakでループ自体を中断する

breakは、条件が満たされた場合にループ全体を終了するための命令です。

【Before】フラグ変数とif-elseでループの終了を管理する

int totalPrice = 0;
boolean process Succeeded = true;
for (Product product : products) {
    if (product.isValid()) {
        // 有効な商品なら価格を加算
        totalPrice += product.price();
    } else {
        // 一つでも無効な商品があれば、処理を中断してフラグを立てる
        processSucceeded = false;
        break;
    }
}

【After】早期breakでシンプルに記述する

int totalPrice = 0;
for (Product product : products) {
    // 無効な商品が見つかった時点で、即座にループを抜ける
    if (!product.isValid()) {
        totalPrice = 0; // or throw exception
        break;
    }
    totalPrice += product.price();
}

breakを使うことで、ループを抜ける条件が明確になり、余計なフラグ変数も不要になりました。

まとめ

コレクションのループ処理は、少しの工夫で劇的に読みやすくなります。

  • まずAPIの利用を検討する: forループを手で書く前に、Stream APIのような高レベルな機能で実現できないか考えましょう。
  • ループ内のガード節としてcontinueを使う: 前提条件を満たさない要素は、ループの先頭でスキップさせましょう。
  • ループの中断にはbreakを使う: ループ全体を終了させる条件を明確にしましょう。

▼次の記事 次回は、List<Product>のようなコレクションをそのまま扱うことの危険性に焦点を当てます。コレクションとその操作ロジックを一つのクラスにカプセル化する「ファーストクラスコレクション」という強力な設計パターンを紹介します。

【第2部】 Listをそのまま使わない。「ファーストクラスコレクション」で実現する、安全で凝集度の高いコード設計

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次