판독 루프를 종료하는 방법은 선호되는 방법입니까?


13

읽을 항목 수를 알 수없는 판독기를 반복해야하는 경우 끝까지 닿을 때까지 계속 읽으십시오.

이것은 종종 끝없는 루프가 필요한 곳입니다.

  1. 블록 안에 어딘가에 또는 문 true이 있어야 함을 나타내는 항상 있습니다 .breakreturn

    int offset = 0;
    while(true)
    {
        Record r = Read(offset);
        if(r == null)
        {
            break;
        }
        // do work
        offset++;
    }
  2. 더블 루프 방법 읽기.

    Record r = Read(0);
    for(int offset = 0; r != null; offset++)
    {
        r = Read(offset);
        if(r != null)
        {
            // do work
        }
    }
  3. 단일 읽기 while 루프가 있습니다. 모든 언어가이 방법을 지원하지는 않습니다 .

    int offset = 0;
    Record r = null;
    while((r = Read(++offset)) != null)
    {
        // do work
    }

어떤 접근 방식이 가장 읽기 쉽고 일반적으로 사용되는 버그를 도입 할 가능성이 가장 적은지 궁금합니다.

이 중 하나를 작성해야 할 때마다 "더 나은 방법이 있어야 한다 " 고 생각 합니다.


2
왜 오프셋을 유지하고 있습니까? 대부분의 스트림 리더가 단순히 "다음 읽기"를 허용하지는 않습니까?
Robert Harvey

현재의 @RobertHarvey는 독자에게 오프셋을 사용하여 결과를 페이지 매김하는 기본 SQL 쿼리가 필요합니다. 빈 결과를 반환 할 때까지 쿼리 결과가 얼마나 오래 지속되는지 모르겠습니다. 그러나 문제는 실제로 요구 사항이 아닙니다.
Reactgular

3
질문 제목은 끝없는 루프에 관한 것이지만 질문 텍스트는 모두 루프 종료에 관한 것입니다. 고전적인 솔루션은 (구조적 프로그래밍 시대부터) 데이터를 가지고있는 동안 사전 읽기, 루프를 수행하고 루프의 마지막 동작으로 다시 읽는 것입니다. 간단합니다 (버그 요구 사항 충족). 가장 일반적입니다 (50 년 동안 작성되었으므로). 읽기 쉬운 것은 의견의 문제입니다.
andy256

@ andy256 혼란은 커피 사전 상태입니다.
Reactgular

1
아, 올바른 절차는 1) 키보드를 피하면서 커피를 마신다. 2) 코딩 루프를 시작한다.
andy256

답변:


49

여기서 물러서겠습니다 코드의 까다로운 세부 사항에 집중하고 있지만 더 큰 그림이 누락되었습니다. 예제 루프 중 하나를 살펴 보겠습니다.

int offset = 0;
while(true)
{
    Record r = Read(offset);
    if(r == null)
    {
        break;
    }
    // do work
    offset++;
}

이 코드 의 의미 는 무엇입니까 ? 의미는 "파일의 각 레코드에 일부 작업을 수행합니다"입니다. 그러나 그것은 코드의 모습 이 아닙니다 . 코드는 "오프셋을 유지합니다. 파일을 엽니 다. 종료 조건없이 루프를 입력하십시오. 레코드를 읽습니다. null을 테스트하십시오."와 같은 코드입니다. 우리가 일하기 전에 모든 것! " 이 코드의 모양이 의미와 일치하도록하려면 어떻게 해야 합니까? "입니다.이 코드는 다음과 같아야합니다.

foreach(Record record in RecordsFromFile())
    DoWork(record);

이제 코드는 의도 한대로 읽습니다. 의미론에서 메커니즘을 분리하십시오 . 원래 코드에서는 매커니즘 (루프의 세부 사항)과 시맨틱 (semantics)을 각 레코드에 대해 수행 한 작업을 혼합합니다.

이제 구현해야합니다 RecordsFromFile(). 그것을 구현하는 가장 좋은 방법은 무엇입니까? 무슨 상관이야? 그것은 누군가가 볼 코드가 아닙니다. 기본 메커니즘 코드이며 10 줄 길이입니다. 그러나 원하는대로 작성하십시오. 이건 어때?

public IEnumerable<Record> RecordsFromFile()
{
    int offset = 0;
    while(true)
    {
        Record record = Read(offset);
        if (record == null) yield break;
        yield return record;
        offset += 1;
    }
}

이제 우리는 느리게 계산 된 레코드 시퀀스를 조작하여 모든 종류의 시나리오가 가능해졌습니다.

foreach(Record record in RecordsFromFile().Take(10))
    DoWork(record);

foreach(Record record in RecordsFromFile().OrderBy(r=>r.LastName))
    DoWork(record);

foreach(Record record in RecordsFromFile().Where(r=>r.City == "London")
    DoWork(record);

등등.

루프를 작성할 때마다 "이 루프를 메커니즘이나 코드의 의미처럼 읽습니까?" 대답이 "메커니즘과 같다"면 해당 메카니즘을 자체 메서드로 옮기고 의미를 더 잘 보이도록 코드를 작성하십시오.


3
+1 마침내 합리적인 답변. 이것이 바로 내가 할 일입니다. 감사.
Reactgular

1
"해당 메카니즘을 독자적인 방법으로 옮기려고 노력하십시오" -이것은 추출 방법 리팩토링 과 비슷 하지 않습니까? "조각의 목적을 설명하는 이름을 가진 방법으로 조각을 돌리십시오."
gnat

2
@ gnat : 내가 제안하는 것은 "추출 방법"보다 약간 더 복잡합니다. 코드의 한 덩어리를 다른 곳으로 옮기는 것으로 생각합니다. 메서드 추출은 의미 체계와 유사하게 코드를 읽을 수 있도록하는 좋은 단계입니다. 정책과 메커니즘을 분리하여 유지하기 위해 분석법 추출을 신중하게 수행 할 것을 제안합니다.
Eric Lippert

1
@gnat : 정확히! 이 경우 추출하려는 세부 사항은 정책을 유지하면서 파일에서 모든 레코드를 읽는 메커니즘입니다. 정책은 "우리는 모든 기록에 대해 약간의 작업을 수행해야합니다".
Eric Lippert

1
내가 참조. 그렇게하면 쉽게 읽고 유지 관리 할 수 ​​있습니다. 이 코드를 연구하면서, 나는 정책과 메커니즘에 개별적으로 집중할 수 있습니다. 그것은주의를 분열시키지 않습니다.
gnat

19

무한 루프가 필요하지 않습니다. C # 읽기 시나리오에서는 필요하지 않습니다. 오프셋을 유지해야한다고 가정하면 이것이 내가 선호하는 접근법입니다.

Record r = Read(0);
offset=1;
while(r != null)
{
    // Do work
    r = Read(offset);
    offset++
}

이 방법은 독자를위한 설정 단계가 있다는 사실을 인정하므로 두 가지 읽기 메소드 호출이 있습니다. while조건 대비해 리더에 전혀 데이터가없는, 루프의 상부에있다.


6

글쎄, 그것은 당신의 상황에 달려 있습니다. 그러나 제가 생각할 수있는 "C # -ish"솔루션 중 하나는 내장 IEnumerable 인터페이스와 foreach 루프를 사용하는 것입니다. IEnumerator 인터페이스는 true 또는 false로 MoveNext 만 호출하므로 크기를 알 수 없습니다. 그런 다음 종료 논리가 열거 자에 한 번 작성되므로 둘 이상의 지점에서 반복 할 필요가 없습니다.

MSDN은 IEnumerator <T>예를 제공합니다 . IEnumerator <T>를 반환하려면 IEnumerable <T>을 만들어야합니다.


코드 예제를 제공 할 수 있습니까?
Reactgular

고마워, 이것이 내가하기로 결정한 것입니다. 끝없는 루프가있을 때마다 새 클래스를 작성한다고 생각하지 않지만 질문을 해결합니다.
Reactgular

네, 무슨 말인지 알 겠어요-많은 오버 헤드. 반복자의 실제 천재 / 문제는 두 가지를 동시에 컬렉션에서 반복 할 수 있도록 설정되어 있다는 것입니다. 따라서 여러 번 해당 기능이 필요하지 않으므로 객체 주위의 래퍼가 IEnumerable 및 IEnumerator를 모두 구현 하도록 할 있습니다. 보는 또 다른 방법은 반복하는 기본 항목 모음이 허용되는 C # 패러다임을 염두에두고 설계되지 않았다는 것입니다. 그리고 괜찮습니다. 반복자의 추가 보너스는 모든 병렬 LINQ 자료를 무료로 얻을 수 있다는 것입니다!
J Trana

2

초기화, 조건 및 증가 작업이있을 때 C, C ++ 및 C #과 같은 언어의 for 루프를 사용하고 싶습니다. 이처럼 :

for (int offset = 0, Record r = Read(offset); r != null; r = Read(++offset)){
    // loop here!
}

또는 이것이 더 읽기 쉽다고 생각되면 말입니다. 나는 개인적으로 첫 번째 것을 선호합니다.

for (int offset = 0, Record r = Read(offset); r != null; offset++, r = Read(offset)){
    // loop here!
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.