web-dev-qa-db-ja.com

後で使用するためにループにフラグを設定するのはコードのにおいですか?

特定の条件がtrueになるまでマップを反復し、後でその条件を使用してさらにいくつかのことを行うコードを作成しました。

例:

Map<BigInteger, List<String>> map = handler.getMap();

if(map != null && !map.isEmpty())
{
    for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())
    {
        fillUpList();

        if(list.size() > limit)
        {
            limitFlag = true;
            break;
        }
    }
}
else
{
    logger.info("\n>>>>> \n\t 6.1 NO entries to iterate over (for given FC and target) \n");
}

if(!limitFlag) // Continue only if limitFlag is not set
{
    // Do something
}

フラグを設定し、それを使用してより多くのことを行うのはコードの匂いです。

私は正しいですか?どうすればこれを削除できますか?

30

ブール値を本来の目的で使用することに問題はありません。バイナリの区別を記録することです。

このコードをリファクタリングするように言われたら、おそらくループを独自のメソッドに入れて、代入+ breakreturnになるようにします。変数さえ必要ないなら、簡単に言うことができます

if(fill_list_from_map()) {
  ...
70
Kilian Foth

それは必ずしも悪いことではなく、時にはそれが最良の解決策です。しかし、ネストされたブロックでこのようなフラグを設定するとcan、コードが追跡しにくくなります。

問題は、スコープを区切るブロックがあるが、スコープ間で通信するフラグがあり、ブロックの論理的な分離を壊していることです。たとえば、limitFlagmapの場合、nullはfalseになるため、mapnullの場合、「何かを行う」コードが実行されます。これはあなたが意図していることかもしれませんが、ネストされたスコープ内のどこかでこのフラグの条件が定義されているため、見逃しやすいバグである可能性があります。可能な限り狭い範囲内に情報とロジックを保持できる場合は、そうするようにしてください。

25
JacquesB

「コードのにおい」について推論しないことをお勧めします。それはあなた自身のバイアスを合理化するための最も怠惰な方法です。時間が経つにつれて多くのバイアスが生まれ、それらの多くは合理的ですが、それらの多くは愚かになります。

代わりに、あるものを別のものよりも好む実用的な(つまり、独断的ではない)理由があり、同様のすべての質問に対して同じ答えを持っているとは考えないでください。

「コードのにおい」は、あなたがではない考えているときのためのものです。あなたが本当にコードについて考えるつもりなら、それを正しく行いましょう!

この場合、周囲のコードに応じて、決定は実際にはどちらの方向にも進む可能性があります。それは実際には、コードが何をしているかについて考える最も明確な方法であると考えることに依存します。 (「クリーン」コードは、他の開発者に何をしているかを明確に伝え、それが正しいことを簡単に確認できるようにするコードです)

多くの場合、人々はフェーズに構造化されたメソッドを記述し、コードは最初にデータについて何を知る必要があるかを決定し、次にそれに基づいて動作します。 「決定」部分と「それに作用する」部分がどちらも少し複雑である場合、これを行うことは理にかなっており、多くの場合、「知る必要があること」をブールフラグのフェーズ間で実行できます。でも、旗の名前をもっと良くした方がいいです。 "largeEntryExists"のようなコードを使用すると、コードがよりクリーンになります。

一方、「// Do Something」コードが非常に単純な場合は、フラグを設定する代わりにifブロック内に配置する方が理にかなっています。これにより効果が原因に近づき、リーダーはコードの残りの部分をスキャンしてフラグが設定した値を保持することを確認する必要がなくなります。

14
Matt Timmermans

はい、それはコードのにおいです(それを行うすべての人からのキューの反対票)。

私にとって重要なのは、breakステートメントの使用です。これを使用しなかった場合、必要以上のアイテムを反復処理することになりますが、これを使用すると、ループから2つの可能な出口点が得られます。

この例では大きな問題ではありませんが、ループ内の条件がより複雑になったり、初期リストの順序が重要になったりすると、バグがコードに侵入しやすくなると想像できます。

コードが例のように単純な場合は、whileループまたは同等のマップ、フィルター構成に縮小できます。

コードがフラグとブレークを必要とするほど複雑な場合、バグが発生しやすくなります。

したがって、すべてのコードのにおいがするように、フラグが表示された場合は、それをwhileに置き換えてみてください。できない場合は、追加の単体テストを追加します。

5
Ewan

あなたがすでに持っている情報を伝えるためにブール値を設定することは私の考えでは悪い習慣です。簡単な代替手段がない場合は、カプセル化の不良などのより大きな問題を示している可能性があります。

ForループロジックをfillUpListメソッドに移動して、制限に達した場合にそれを解除する必要があります。その後、すぐにリストのサイズを確認します。

それがあなたのコードを壊すなら、なぜですか?

0
user294250

実際に確認している内容を示すlimitFlag以外の名前を使用してください。そして、マップが存在しないか空の場合、なぜ何かをログに記録するのですか? limtFlagはfalseになります。マップが空であればループは問題ないので、チェックする必要はありません。

0
gnasher729

まず、一般的なケースとして、コレクションの一部の要素が特定の条件を満たすかどうかを確認するためにフラグを使用することは珍しくありません。しかし、私がこれを解決するために最もよく見たパターンは、チェックを追加のメソッドに移動し、それから直接戻ることです( 彼の答え で説明されているKilian Fothのように):

_private <T> boolean checkCollection(Collection<T> collection)
{
    for (T element : collection)
        if (checkElement(element))
            return true;
    return false;
}
_

Java 8なので、 Stream.anyMatch(…) を使用するより簡潔な方法があります。

_collection.stream().anyMatch(this::checkElement);
_

あなたの場合、これはおそらく次のようになります(質問でlist == entry.getValue()を想定):

_map.values().stream().anyMatch(list -> list.size() > limit);
_

特定の例の問題は、fillUpList()への追加の呼び出しです。答えは、このメソッドが何をすることになっているかに大きく依存します。

補足:現状では、fillUpList()の呼び出しは、現在反復している要素に依存しないため、あまり意味がありません。これは、質問の形式に合わせて実際のコードを削除した結果だと思います。しかし、それは正確に解釈するのが難しく、したがって推論するのが難しい人工的な例につながります。したがって、 Minimal、Complete、およびVerifiable

したがって、実際のコードは現在のentryをメソッドに渡すと想定しています。

ただし、さらに質問する必要があります。

  • このコードに到達する前に、マップ内のリストは空ですか?もしそうなら、なぜBigIntegerキーのリストまたはセットだけでなく、すでにマップがあるのですか?空でない場合、リストを埋める必要があるのはなぜですか?リストにすでに要素がある場合、これはupdateまたはこの場合の他の計算ではありませんか?
  • リストが制限を超える原因は何ですか?これはエラー状態ですか、それとも頻繁に発生することが予想されますか?無効な入力が原因ですか?
  • 制限よりも大きいリストに到達するまでに計算されたリストが必要ですか?
  • 何かを行う」の部分は何をしますか?
  • この部分の後で充填を再開しますか?

これは、コードの断片を理解しようとしたときに頭に浮かんだいくつかの質問にすぎません。だから、私の意見では、それが本当のコードの匂いです:あなたのコードは意図を明確に伝えていません。

これは次のいずれかを意味している可能性があります(「すべてまたは何も」ではなく、制限に達した場合はエラーを示します)。

_/**
 * Computes the list of all foo strings for each passed number.
 * 
 * @param numbers the numbers to process. Must not be {@code null}.
 * @return all foo strings for each passed number. Never {@code null}.
 * @throws InvalidArgumentException if any number produces a list that is too long.
 */
public Map<BigInteger, List<String>> computeFoos(Set<BigInteger> numbers)
        throws InvalidArgumentException
{
    if (numbers.isEmpty())
    {
        // Do you actually need to log this here?
        // The caller might know better what to do in this case...
        logger.info("Nothing to compute");
    }
    return numbers.stream().collect(Collectors.toMap(
            number -> number,
            number -> computeListForNumber(number)));
}

private List<String> computeListForNumber(BigInteger number)
        throws InvalidArgumentException
{
    // compute the list and throw an exception if the limit is exceeded.
}
_

または、これが意味することもあります(「最初の問題まで更新」)。

_/**
 * Refreshes all foo lists after they have become invalid because of bar.
 * 
 * @param map the numbers with all their current values.
 *            The values in this map will be modified.
 *            Must not be {@code null}.
 * @throws InvalidArgumentException if any new foo list would become too long.
 *             Some other lists may have already been updated.
 */
public void updateFoos(Map<BigInteger, List<String>> map)
        throws InvalidArgumentException
{
    map.replaceAll(this::computeUpdatedList);
}

private List<String> computeUpdatedList(
        BigInteger number, List<String> currentValues)
        throws InvalidArgumentException
{
    // compute the new list and throw an exception if the limit is exceeded.
}
_

または、次のようにします(「すべてのリストを更新しますが、大きくなりすぎても元のリストを保持します」)。

_/**
 * Refreshes all foo lists after they have become invalid because of bar.
 * Lists that would become too large will not be updated.
 * 
 * @param map the numbers with all their current values.
 *            The values in this map will be modified.
 *            Must not be {@code null}.
 * @return {@code true} if all updates have been successful,
 *         {@code false} if one or more elements have been skipped
 *         because the foo list size limit has been reached.
 */
public boolean updateFoos(Map<BigInteger, List<String>> map)
{
    boolean allUpdatesSuccessful = true;
    for (Entry<BigInteger, List<String>> entry : map.entrySet())
    {
        List<String> newList = computeListForNumber(entry.getKey());
        if (newList.size() > limit)
            allUpdatesSuccessful = false;
        else
            entry.setValue(newList);
    }
    return allUpdatesSuccessful;
}

private List<String> computeListForNumber(BigInteger number)
{
    // compute the new list
}
_

または、次の場合でも(最初の例のcomputeFoos(…)を使用しますが、例外はありません)。

_/**
 * Processes the passed numbers. An optimized algorithm will be used if any number
 * produces a foo list of a size that justifies the additional overhead.
 * 
 * @param numbers the numbers to process. Must not be {@code null}.
 */
public void process(Collection<BigInteger> numbers)
{
    Map<BigInteger, List<String>> map = computeFoos(numbers);
    if (isLimitReached(map))
        processLarge(map);
    else
        processSmall(map);
}

private boolean isLimitReached(Map<BigInteger, List<String>> map)
{
    return map.values().stream().anyMatch(list -> list.size() > limit);
}
_

あるいは、まったく別のことを意味するかもしれません…;-)

0
siegi