while (true) 및 루프 브레이킹-안티 패턴?


32

다음 코드를 고려하십시오.

public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

이 프로세스에는 유한하지만 입력 종속적 인 단계가 포함되어 있다고 가정합니다. 루프는 알고리즘의 결과로 자체적으로 종료되도록 설계되었으며 무한정 실행되도록 설계되지 않았습니다 (외부 이벤트에 의해 취소 될 때까지). 루프가 끝나야하는지 확인하는 테스트가 논리적 단계 집합의 중간에 있기 때문에 while 루프 자체는 현재 의미있는 것을 확인하지 않습니다. 대신 검사는 개념적 알고리즘 내의 "적절한"위치에서 수행됩니다.

루프 구조로 확인되지 않는 종료 조건으로 인해 버그가 발생하기 쉽기 때문에 이것이 나쁜 코드라고 들었습니다. 루프를 종료하는 방법을 파악하기가 더 어려우며, 향후 변경으로 인해 실수로 우회되거나 생략 될 수 있으므로 버그가 발생할 수 있습니다.

이제 코드는 다음과 같이 구성 될 수 있습니다.

public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

그러나 이것은 코드에서 메소드에 대한 호출을 복제하여 DRY를 위반합니다. TransformInSomeWay나중에 다른 방법으로 대체 된 경우 두 호출을 모두 찾아서 변경해야합니다 (더 복잡한 코드에서는 두 가지가 있다는 사실이 덜 분명 할 수 있음).

다음과 같이 작성할 수도 있습니다.

public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

...하지만 이제 조건 확인을 루프 구조로 전환하는 유일한 목적을 가진 변수가 있으며 원래 논리와 동일한 동작을 제공하기 위해 여러 번 확인해야합니다.

필자는이 코드가 실제 환경에서 구현하는 알고리즘을 고려할 때 원본 코드가 가장 읽기 쉽다고 말합니다. 당신이 그것을 직접 겪고 있다면, 이것은 당신이 그것에 대해 생각하는 방식이므로 알고리즘에 익숙한 사람들에게는 직관적입니다.

그렇다면 "더 나은"것은 무엇입니까? 루프 주변의 논리를 구성하여 while 루프에 조건 확인의 책임을 부여하는 것이 더 낫습니까? 또는 루프의 내장 기능을 우회 할 수 있음에도 불구하고 요구 사항 또는 알고리즘에 대한 개념적 설명으로 표시된대로 "자연적인"방식으로 논리를 구성하는 것이 더 낫습니까?


12
아마도 코드 샘플에도 불구하고 질문이 제기되는 질문은 IMO가 더 개념적이며 SE 의이 영역과 더 일치합니다. 여기서는 패턴이나 반 패턴이 무엇인지 자주 논의하는데, 이것이 실제 질문입니다. 루프가 무한정 실행되도록 구성한 다음 나쁜 점을 없애고 있습니까?
KeithS

3
이러한 do ... until구조는 "프라이밍"할 필요가 없기 때문에 이러한 종류의 루프에도 유용합니다.
Blrfl

2
루프 앤 하프 사례에는 여전히 좋은 대답이 없습니다.
Loren Pechtel

1
"그러나 이것은 DRY를 위반하는 코드의 메소드 호출을 복제합니다."나는 그 주장에 동의하지 않으며 원칙을 오해하는 것처럼 보입니다.
Andy

1
그렇다 나 (;;)하는 C-스타일을 선호에서 explictely 수단 "나는 루프를 가지고 있고, 나는 루프 문에서 확인하지 않는"첫 번째 접근 방식은 절대적 권리입니다. 루프가 있습니다. 종료 여부를 결정하기 전에해야 할 일이 있습니다. 그런 다음 일부 조건이 충족되면 종료합니다. 그런 다음 실제 작업을 수행하고 다시 시작하십시오. 그것은 절대적으로 훌륭하고 이해하기 쉽고 논리적이며 중복되지 않으며 쉽게 얻을 수 있습니다. 두 번째 변형은 끔찍한 반면, 세 번째 변형은 의미가 없습니다. if에 들어가는 나머지 루프가 매우 길면 끔찍합니다.
gnasher729

답변:


14

첫 번째 해결책을 고수하십시오. 루프는 매우 복잡해질 수 있으며 정교한 if 문과 변수를 추가하는 것보다 코드, 최적화 프로그램 및 프로그램 성능을 보는 사람이 훨씬 쉬울 수 있습니다. 내 일반적인 접근 방식은 "while (true)"와 같은 루프를 설정하고 가능한 최고의 코딩을 얻는 것입니다. 종종 브레이크를 표준 ​​루프 컨트롤로 대체 할 수 있습니다. 대부분의 루프는 결국 매우 간단합니다. 종료 조건은 해야 당신이 언급 한 이유에 대해 루프 구조에 의해 확인되지만 되지 도 모두있는 경우 - 문 및 반복 코드, 추가, 새로운 변수를 추가하는 비용으로 버그가 발생하기 쉬운.


"if 문과 반복 코드는 모두 버그가 발생하기 쉽다" -그래 사람들을 시도 할 때 나는 "미래 버그"라고 부른다
Pat

13

루프 본문이 사소한 경우에만 의미있는 문제입니다. 몸이 너무 작 으면 이와 같이 분석하는 것은 사소한 것이며 전혀 문제가되지 않습니다.


7

휴식이있는 솔루션보다 다른 솔루션을 찾는 데 어려움을 겪었습니다. 글쎄, 나는 할 수있는 Ada 방식을 선호합니다.

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

그러나 브레이크 솔루션은 가장 깨끗한 렌더링입니다.

내 POV는 누락 된 제어 구조가있을 때 가장 깨끗한 솔루션은 데이터 흐름을 사용하지 않고 다른 제어 구조로 에뮬레이션하는 것입니다. (그리고 마찬가지로, 제어 구조와 관련된 것보다 순수한 데이터 흐름 솔루션을 선호하는 데이터 흐름 문제가있을 때 *).

루프를 종료하는 방법을 파악하기가 더 어렵습니다.

착각하지 마십시오. 어려움이 거의 같지 않은 경우 토론이 이루어지지 않습니다. 조건이 동일하고 같은 장소에 있습니다.

어느 것

while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

의도 한 구조를 더 잘 렌더링합니까? 첫 번째로 투표하고 있는데 조건이 일치하는지 확인할 필요는 없습니다. (그러나 내 들여 쓰기 참조).


1
오, 모양, 언어 않습니다 직접 중반 종료 루프를 지원! :)
mjfgates

제어 구조로 누락 된 제어 구조를 에뮬레이트하는 것에 대한 귀하의 요점을 좋아합니다. 그것은 아마도 GOTO 관련 질문에 대한 좋은 대답 일 것입니다. 의미 요구 사항에 맞을 때마다 언어의 제어 구조를 사용해야하지만 알고리즘에 다른 것이 필요한 경우 알고리즘에 필요한 제어 구조를 사용해야합니다.
supercat

3

while (true) 및 break는 일반적인 관용구입니다. C와 같은 언어로 중간 출구 루프를 구현하는 정식 방법입니다. 종료 조건을 저장하기위한 변수와 루프의 후반부에 if ()를 추가하는 것이 적절한 대안입니다. 일부 상점은 공식적으로 표준화 될 수 있지만 어느 쪽도 우수하지는 않습니다. 그것들은 동등합니다.

이 언어로 일하는 좋은 프로그래머는 자신이 볼 수있는 일이 무엇인지 한 눈에 알 수 있어야합니다. 좋은 인터뷰 질문이 될 수 있습니다. 누군가 하나의 각 관용구를 사용하여 두 개의 루프를 건네고, 그 차이점이 무엇인지 물어보십시오.


2

당신의 대안은 생각만큼 나쁘지 않습니다. 좋은 코드는 :

  1. 루프가 종료되어야하는 조건에서 변수 이름 / 주석으로 명확하게 표시하십시오. (파단 조건을 숨겨서는 안됩니다)

  2. 작업 순서가 무엇인지 명확하게 표시하십시오. 선택적인 3 번째 연산 ( DoSomethingElseTo(input))을 if 안에 넣는 것이 좋습니다.

즉, 완벽 주의자, 황동 연마 본능이 많은 시간을 소비하게하는 것은 쉽습니다. 위에서 언급 한대로 if를 조정합니다. 코드에 주석을 달고 계속 ​​진행하십시오.


완벽주의적인 황동 연마 본능은 장기적으로 시간을 절약한다고 생각합니다. 실제로 연습을 통해 그러한 습관을 개발하여 코드가 완벽하고 많은 시간 소비 하지 않고 황동을 연마하기 를 바랍니다. 그러나 물리적 구조와 달리 소프트웨어는 영원히 지속되며 무한한 장소에서 사용될 수 있습니다. 0.00000001 % 더 빠르고, 더 안정적이고, 사용 가능하며 유지 보수가 쉬운 코드로 인한 비용을 크게 절약 할 수 있습니다. (물론, 내 많이 연마 대부분의 코드는 오랫동안 사용되지 않는 또는 요즘 다른 사람을 위해 돈을 버는 언어이지만, 그것은 않았다 내게 좋은 습관을 도움을 개발할 수 있습니다.)
RalphChapin

그럼 내가 잘못을 호출 할 수 없습니다 :-)이 의견 질문과 좋은 기술은 황동 연마 나온 경우 : 좋아요.
Pat

어쨌든 고려할 가능성이 있다고 생각합니다. 확실히 어떤 보증도 제공하고 싶지 않습니다. 내 황동 연마가 내 기술을 향상 시켰다고 생각하는 편이 좋으므로이 시점에서 편견이있을 수 있습니다.
RalphChapin

2

사람들은 사용하는 것이 나쁜 습관 while (true)이며 많은 시간이 옳다고 말합니다 . 귀하의 경우 while문은 복잡한 코드를 많이 포함하고있는 경우에, 다음 코드를 유지하기 어려운 남아 있습니다 간단한 버그가 무한 루프가 발생할 수 돌발 할 때 그것은 명확하지 않다.

귀하의 예에서는 while(true)코드가 명확하고 이해하기 쉽기 때문에 사용하는 것이 정확합니다 . 비효율적이며 코드를 읽기 어렵게 만드는 요점은 없습니다. 일부 사람들은 항상 사용하는 것이 좋지 않다고 생각하기 때문 while(true)입니다.

나는 비슷한 문제에 직면했다.

$i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

를 사용 do ... while(true)하면 isset($array][$key]불필요하게 두 번 호출되는 것을 피할 수 있습니다 . 코드는 짧고 깨끗하며 읽기 쉽습니다. else break;끝에 무한 루프 버그가 발생할 위험은 전혀 없습니다 .

$i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

좋은 관행 을 따르고 나쁜 관행을 피하는 것이 좋지만 항상 예외가 있으며 열린 마음을 유지하고 그러한 예외를 인식하는 것이 중요합니다.


0

특정 제어 흐름이 자연스럽게 중단 명령문으로 표현되면 본질적으로 다른 흐름 제어 메커니즘을 사용하여 중단 명령문을 에뮬레이트하는 것이 더 나은 선택이 아닙니다.

(루프 시작이 조건부 테스트와 일치하도록 루프를 부분적으로 풀고 루프 본체를 아래로 미끄러지는 것을 포함합니다)

루프 시작 부분에 조건부 검사를 수행하여 제어 흐름을 자연스럽게 표현할 수 있도록 루프를 재구성 할 수 있다면 좋습니다. 그것이 가능한지 생각하는 데 시간을 투자하는 것이 좋습니다. 그러나 둥근 구멍에 사각형 못을 끼 우려 고하지 마십시오.


0

당신은 또한 이것을 갈 수 있습니다 :

public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

또는 덜 혼란스럽게 :

bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}

1
어떤 사람들은 그것을 좋아하지만 루프 조건은 일반적으로 일을하는 진술보다는 조건부 테스트를 위해 예약되어야한다고 주장합니다.

5
루프 조건에서 쉼표 식 ... 끔찍합니다. 당신은 너무 영리 해요 그리고 TransformInSomeWay를 표현식으로 표현할 수있는 사소한 경우에만 작동합니다.
gnasher729

0

논리의 복잡성이 다루기 어려워지면 흐름 제어를 위해 중간 루프 브레이크와 함께 무한 루프를 사용하는 것이 실제로 가장 적합합니다. 다음과 같은 코드가있는 프로젝트가 있습니다. (루프 끝에 예외를 통지하십시오.)

L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

무한 루프 중단 없이이 논리를 수행하려면 다음과 같은 결과가 발생합니다.

void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

따라서 도우미 메소드에서 예외가 발생합니다. 또한 상당히 많은 if ... else중첩이 있습니다. 이 방법에 만족하지 않습니다. 따라서 첫 번째 패턴이 최고라고 생각합니다.


사실, 나는 SO에이 질문을하고 화염에 격추 ... stackoverflow.com/questions/47253486/...은
intrepidis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.