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


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

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

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

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, Complete 및 Verifiable 예제 를 제공하는 것이 매우 중요합니다 .

따라서 실제 코드는 전류 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.