나중에 사용하기 위해 루프에 플래그를 설정하는 것이 코드 냄새입니까?


30

특정 조건이 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
}

나는 깃발을 설정하고 그것을 사용하여 더 많은 일을하는 것이 코드 냄새입니다.

내가 맞아? 이걸 어떻게 제거 할 수 있습니까?


10
왜 코드 냄새라고 생각합니까? 다른 구조에서 발생하지 않는 어떤 종류의 특정 문제를 예측할 수 있습니까?
벤 Cottrell

13
@ gnasher729 궁금한 점이 있다면 어떤 용어를 대신 사용 하시겠습니까?
벤 Cottrell

11
-1, 당신의 예는 말이되지 않습니다. entry함수 루프 내부에는 아무 것도 사용되지 않으며 무엇이 있는지 추측 할 수 있습니다 list. 되어 fillUpList채우기에 가정 list? 왜 매개 변수로 얻지 못합니까?
Doc Brown

13
공백과 빈 줄을 사용하는 것이 좋습니다.
다니엘 Jour

11
코드 냄새와 같은 것은 없습니다. "코드 냄새"는 엘리트 표준에 맞지 않는 코드를 볼 때 코를 잡고 싶어하는 소프트웨어 개발자가 고안 한 용어입니다.
Robert Harvey

답변:


70

의도 된 목적으로 부울 값을 사용하는 데 아무런 문제가 없습니다 : 이진 구별을 기록하는 것.

이 코드를 리팩토링하라는 지시를 받았다면 아마도 루프를 자체 메소드에 넣어 할당 + breakreturn; 변수조차 필요하지 않습니다. 간단히 말해

if(fill_list_from_map()) {
  ...

6
실제로 그의 코드의 냄새는 더 긴 기능이며 더 작은 기능으로 분리되어야합니다. 당신의 제안은 갈 길입니다.
Bernhard Hiller

2
해당 코드의 첫 번째 부분에서 유용한 기능을 설명하는 더 좋은 문구는 매핑 된 항목에서 무언가를 누적 한 후에 한계가 초과되는지 여부를 찾는 것입니다. 또한 fillUpList()실제로 entry반복 의 값 을 사용하는 일부 코드 (OP가 공유하지 않기로 결정 함)를 안전하게 가정 할 수 있습니다 . 이 가정이 없으면 루프 본문이 루프 반복에서 아무것도 사용하지 않은 것처럼 보입니다.
rwong

4
@ 킬리안 : 한 가지 우려가 있습니다. 이 메소드는 목록을 채우고 목록 크기가 한도를 초과하는지 여부를 나타내는 부울을 반환하므로 'fill_list_from_map'이라는 이름은 부울이 무엇을 반환했는지 명확하게 나타내지 않습니다 (채우지 못했습니다. 한계 초과 등). 반환되는 부울은 함수 이름에서 명확하지 않은 특수한 경우에 대한 것입니다. 다른하실 말씀 있나요 ? 추신 : 우리는 명령 쿼리 분리를 고려할 수도 있습니다.
Siddharth Trikha

2
@SiddharthTrikha 당신은 옳습니다. 그리고 나는 그 라인을 제안했을 때 똑같은 걱정을했습니다. 그러나 코드가 어떤 목록을 채울 것인지는 확실하지 않습니다. 항상 같은 목록이라면 플래그가 필요하지 않습니다. 나중에 그 길이를 확인할 수 있습니다. 개별 채우기가 한도를 초과 했는지 여부를 알아야하는 경우 어떻게 든 정보를 외부로 전송해야하며 IMO 명령 / 쿼리 분리 원칙은 명백한 방법을 거부하는 충분한 이유가 아닙니다. 값.
Kilian Foth

6
삼촌 밥의 45 페이지에 말한다 클린 코드 :. "기능 중 하나 일 또는 응답 뭔가 있지만 둘을해야 어느 함수는 객체의 상태를 변경해야하거나, 해당 개체에 대한 정보를 반환해야합니다 모두에 자주 리드를 수행. 혼동."
CJ Dennis

25

반드시 나쁘지는 않으며 때로는 최상의 솔루션입니다. 그러나 중첩 블록에서 이와 같은 플래그를 설정 하면 코드를 따르기가 어려울 수 있습니다 .

문제는 범위를 구분하는 블록이 있지만 범위를 통해 통신하여 블록의 논리적 격리를 차단하는 플래그가 있다는 것입니다. 예를 들어, limitFlag(가)의 경우는 false 될 것 map입니다 null경우 "무언가를"-code이 실행됩니다, 그래서 map이다 null. 이것은 의도 한 것일 수도 있지만이 플래그의 조건은 중첩 된 범위 내에서 다른 곳에 정의되어 있기 때문에 놓치기 쉬운 버그 일 수 있습니다. 정보와 논리를 가능한 한 가장 좁은 범위 내에서 유지할 수 있으면 그렇게해야합니다.


2
이것이 블록이 완전히 분리되지 않았고 나중에 추적하기 어려울 수 있기 때문에 코드 냄새라고 생각한 이유입니다. 그래서 @Kilian의 답변에서 가장 가까운 코드를 추측 할 수 있습니까?
Siddharth Trikha

1
@SiddharthTrikha : 코드가 실제로 무엇을해야하는지 모르겠 기 때문에 말하기 어렵습니다. 목록에 한도보다 큰 목록이 하나 이상 포함되어 있는지 확인하려면 단일 anyMatch 표현식으로 할 수 있다고 생각합니다.
JacquesB

2
@SiddharthTrikha는 : 범위의 문제가 쉽게와 같은 보호 절에 초기 테스트를 변경함으로써 해결 될 수있다 if(map==null || map.isEmpty()) { logger.info(); return;}이 의지하지만, 작품은 우리가 볼 수있는 코드는 함수의 전신이며, 경우에만 // Do something일부 경우지도에 필요하지 않습니다 null이거나 비어 있습니다.
Doc Brown

14

나는 '코드 냄새'에 대한 추론에 대해 조언하고 싶습니다. 그것은 자신의 편견을 합리화하는 가장 게으른 방법입니다. 시간이 지남에 따라 많은 편견이 생길 수 있고, 많은 편견이 합리적이지만 많은 편이 어리 석습니다.

대신, 한 가지를 다른 것보다 선호하는 실질적인 (즉, 독단적이지 않은) 이유가 있어야하며 모든 유사한 질문에 대해 같은 대답을 가져야한다고 생각하지 않아야합니다.

"코드 냄새"는 생각 하지 않을 때를위한 것 입니다. 실제로 코드에 대해 생각한다면 올바르게 수행하십시오!

이 경우 결정은 주변 코드에 따라 실제로 진행될 수 있습니다. 실제로 코드가 수행하는 작업을 생각하는 가장 명확한 방법이라고 생각하는 것에 달려 있습니다. ( "깨끗한"코드는 다른 개발자에게 수행중인 작업을 명확하게 전달하고 올바른지 쉽게 확인할 수있는 코드입니다.)

많은 사람들이 단계적으로 구조화 된 메소드를 작성합니다. 여기서 코드는 먼저 데이터에 대해 알아야 할 사항을 결정한 후 조치를 취합니다. "결정"부분과 "작용"부분이 모두 약간 복잡한 경우에는이를 수행하는 것이 좋으며 종종 "알아야 할 사항"은 부울 플래그의 단계 사이에서 수행 될 수 있습니다. 그래도 깃발에 더 나은 이름을 부여하는 것이 좋습니다. "largeEntryExists"와 같은 코드는 코드를 훨씬 깨끗하게 만듭니다.

반면에 "// Do Something"코드가 매우 간단한 if경우 플래그를 설정하는 대신 블록 안에 넣는 것이 더 합리적 입니다. 결과가 원인에 더 가까워지고 독자는 플래그가 설정 한 값을 유지하도록 나머지 코드를 스캔 할 필요가 없습니다.


5

그렇습니다. 코드 냄새입니다.

나를 위해 중요한 것은 break진술을 사용하는 것입니다. 사용하지 않으면 필요한 것보다 많은 항목을 반복하지만 사용하면 루프에서 두 개의 가능한 종료 점이 제공됩니다.

예제에서 큰 문제는 아니지만 루프 내부의 조건부 또는 조건부가 더 복잡해 지거나 초기 목록의 순서가 중요 해지면 버그가 코드에 들어가기 쉽다는 것을 상상할 수 있습니다.

코드가 예제처럼 단순하면 while루프 또는 동등한 맵 필터 구성 으로 줄일 수 있습니다 .

코드가 플래그와 중단을 요구할 정도로 복잡 할 경우 버그가 발생하기 쉽습니다.

모든 코드 냄새와 마찬가지로 : 플래그가 표시되면 플래그를로 바꾸십시오 while. 당신이 할 수 없다면, 추가 단위 테스트를 추가하십시오.


나에게서 +1 그것은 코드 냄새가 확실하며 왜, 어떻게 처리 해야하는지 분명히 말하십시오.
David Arno

@ Ewan : SO as with all code smells: If you see a flag, try to replace it with a while예를 들어 이것에 대해 자세히 설명 할 수 있습니까?
Siddharth Trikha

2
루프에서 종료 점이 여러 개 있으면 추론하기가 더 어려워 질 수 있지만이 경우 루프 조건을 플래그에 의존하도록 리팩토링하면-로 대체 for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())해야 for (Iterator<Map.Entry<BigInteger, List<String>>> iterator = map.entrySet().iterator(); iterator.hasNext() && !limitFlag; Map.Entry<BigInteger, List<String>> entry = iterator.next())합니다. 상대적으로 간단한 휴식보다 이해하기가 더 어려운 드문 충분한 패턴입니다.
James_pic

@James_pic 내 자바는 약간 녹슬지 만 맵을 사용하는 경우 수집기를 사용하여 항목 수를 요약하고 한도 뒤의 항목을 필터링합니다. 그러나 예제가 "나쁘지 않다"고 말하면 코드 냄새는 잠재적 인 문제를 경고하는 일반적인 규칙입니다. 성스러운 법이 아니라 항상 순종해야합니다
Ewan

1
"큐"가 아닌 "큐"를 의미하지 않습니까?
psmears

0

실제로 확인중인 것을 알려주는 limitFlag 이외의 이름 만 사용하십시오. 그리고지도가 없거나 비어있을 때 왜 아무것도 기록하지 않습니까? limtFlag는 거짓 일 것입니다. 맵이 비어 있으면 루프가 정상이므로 확인할 필요가 없습니다.


0

이미 가지고있는 정보를 전달하기 위해 부울 값을 설정하는 것은 제 생각에 나쁜 습관입니다. 쉬운 대안이 없다면 캡슐화 불량과 같은 더 큰 문제를 나타내는 것일 수 있습니다.

한계에 도달하면 for 루프 로직을 fillUpList 메소드로 이동하여 중단되도록해야합니다. 그런 다음 바로 목록의 크기를 확인하십시오.

그게 코드를 어기면 왜?


0

먼저 일반적인 경우 : 플래그를 사용하여 컬렉션의 일부 요소가 특정 조건을 충족하는지 확인하는 것은 드문 일이 아닙니다. 그러나이 문제를 해결하기 위해 가장 자주 본 패턴은 추가 방법으로 검사를 이동하고 직접 반환합니다 (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, CompleteVerifiable 예제 를 제공하는 것이 매우 중요합니다 .

따라서 실제 코드는 전류 entry를 메소드에 전달한다고 가정합니다 .

그러나 더 많은 질문이 있습니다 :

  • 이 코드에 도달하기 전에지도의 목록이 비어 있습니까? 그렇다면 왜 목록이나 BigInteger키 세트가 아닌 이미 맵이 있습니까? 비어 있지 않으면 왜 목록 을 작성 해야 합니까? 목록에 이미 요소가있는 경우이 경우 업데이트 또는 다른 계산이 아닙니까?
  • 목록이 한계보다 커지게하는 원인은 무엇입니까? 이것은 오류 상태입니까, 아니면 자주 발생할 것으로 예상됩니까? 잘못된 입력으로 인한 것입니까?
  • 한계보다 큰 목록에 도달 할 때까지 계산 된 목록이 필요합니까?
  • " 뭔가 "부분은 무엇을합니까?
  • 이 부분 후에 충전재를 다시 시작합니까?

이것은 코드 조각을 이해하려고 할 때 떠오른 몇 가지 질문입니다. 제 생각에는 이것이 실제 코드 냄새입니다 . 코드가 의도를 명확하게 전달하지 않습니다.

다음을 의미 할 수 있습니다 ( "모두 또는 아무것도 없음". 한계에 도달하면 오류를 나타냄).

/**
 * 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);
}

아니면 완전히 다른 것을 의미 할 수도 있습니다 ... ;-)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.