명시 적 불변 및 사후 조건을 갖는 중단 / 반복을 갖는 Foreach 루프와 while 루프


17

이것은 값이 배열에 있는지 확인 하는 가장 보편적 인 방법입니다 .

for (int x : array)
{
    if (x == value)
        return true;
}
return false;        

그러나 Wirth 또는 Dijkstra가 몇 년 전에 읽은 책 에서이 스타일이 더 좋다고 말했습니다 (출구가있는 while 루프와 비교할 때).

int i = 0;
while (i < array.length && array[i] != value)
    i++;
return i < array.length;

이런 식으로 추가 종료 조건이 루프 불변의 명시 적 부분이되고 숨겨진 조건이없고 루프 내부에서 종료됩니다. 모든 것이보다 명확하고 구조화 된 프로그래밍 방식입니다. 나는 일반적으로 가능할 때마다이 후자의 패턴을 선호했고 for-loop를 사용하여 에서까지만 반복 a했다 b.

그러나 첫 번째 버전이 덜 명확하다고 말할 수는 없습니다. 어쩌면 초보자에게는 최소한 더 명확하고 이해하기 쉽습니다. 그래서 나는 여전히 어느 쪽이 더 나은지 스스로에게 묻고 있습니다 .

어쩌면 누군가 방법 중 하나를 선호하는 좋은 근거를 제시 할 수 있습니까?

업데이트 : 이것은 여러 함수 반환 지점, 람다 또는 배열 자체에서 요소를 찾는 문제가 아닙니다. 단일 불평등보다 더 복잡한 불변량으로 루프를 작성하는 방법에 관한 것입니다.

업데이트 : 좋아, 나는 대답하고 의견을 말하는 사람들의 요점을 본다 : 나는 foreach 루프를 혼합했다. 여기서는 while 루프보다 훨씬 명확하고 읽기 쉽다. 나는 그렇게하지 않아야했다. 그러나 이것은 또한 흥미로운 질문입니다. foreach-loop와 여분의 조건, 또는 명시적인 루프 불변과 post-condition을 가진 while-loop를 그대로 두겠습니다. 조건과 종료 / 브레이크가있는 foreach-loop가이기는 것 같습니다. foreach-loop (연결된 목록)없이 추가 질문을 작성합니다.


2
여기에 인용 된 코드 예제는 여러 가지 다른 문제를 혼합합니다. 조기 및 다중 반환 (메서드의 크기로 이동합니다 (표시되지 않음)), 배열 검색 (람다와 관련된 토론을 요구하는), foreach vs. 직접 색인 생성 ...이 질문은 더 명확하고 쉽습니다. 한 번에 이러한 문제 중 하나에 만 초점을 맞추면 답변하십시오.
Erik Eidt


1
나는 이것이 예제라는 것을 알고 있지만 그 유스 케이스를 정확하게 처리하는 API가있는 언어가 있습니다. 즉collection.contains(foo)
베린 Loritsch

2
책을 찾아서 다시 읽으면 실제로 무엇을 말했는지 알 수 있습니다.
Thorbjørn Ravn Andersen

1
"더 나은"은 매우 주관적인 단어입니다. 즉, 첫 번째 버전이 무엇을하고 있는지 한 눈에 알 수 있습니다. 두 번째 버전은 똑같은 작업을 수행하므로 약간의 조사가 필요합니다.
David Hammen

답변:


19

나는 이것과 같은 간단한 루프를 위해 표준 첫 구문이 훨씬 명확하다고 생각합니다. 어떤 사람들은 여러 번의 반환 혼동이나 코드 냄새를 고려하지만이 작은 코드 조각에 대해서는 이것이 실제 문제라고 생각하지 않습니다.

더 복잡한 루프에 대해서는 좀 더 논쟁의 여지가 있습니다. 루프의 내용이 화면에 맞지 않고 루프에 여러 개의 리턴이있는 경우 여러 개의 종료점으로 인해 코드를 유지하기가 더 어려워 질 수 있다는 주장이 있습니다. 예를 들어, 함수를 종료하기 전에 일부 상태 유지 보수 메소드를 실행해야하는 경우 리턴 문 중 하나에 함수를 추가하지 않으면 버그가 발생할 수 있습니다. while 루프에서 모든 종료 조건을 점검 할 수있는 경우, 하나의 종료점 만 있고이 코드를 뒤에 추가 할 수 있습니다.

즉, 루프를 사용하면 가능한 한 많은 논리를 별도의 메서드에 넣으려고 시도하는 것이 좋습니다. 이것은 두 번째 방법이 유리한 많은 경우를 피합니다. 명확하게 분리 된 로직이있는 린 루프는 사용하는 스타일보다 더 중요합니다. 또한 대부분의 응용 프로그램 코드 기반이 하나의 스타일을 사용하는 경우 해당 스타일을 사용해야합니다.


56

이것은 쉬워요.

독자에게 명확성 이상의 것은 거의 없습니다. 내가 찾은 첫 번째 변종은 매우 간단하고 명확합니다.

두 번째 '개선 된'버전은 여러 번 읽고 모든 에지 조건이 올바른지 확인해야했습니다.

더 나은 코딩 스타일 인 ZERO DOUBT가 있습니다 (첫 번째가 훨씬 낫습니다).

이제 사람들에게 명확한 것은 사람마다 다를 수 있습니다. 이에 대한 객관적인 표준이 있는지 확실하지 않습니다 (이와 같은 포럼에 게시하고 다양한 사람들의 의견을 얻는 것이 도움이 될 수 있음).

그러나이 특별한 경우 첫 번째 알고리즘이 더 명확한 이유를 알 수 있습니다. 컨테이너 구문을 반복하는 C ++의 모양과 동작을 알고 있습니다. 나는 그것을 내면화했다. 해당 구문을 사용하는 UNFAMILIAR (새로운 구문)는 두 번째 변형을 선호 할 수 있습니다.

그러나 일단 새로운 구문을 알고 이해하면 기본 개념 만 사용할 수 있습니다. 루프 반복 (두 번째) 접근 방식을 사용하면 사용자가 전체 에지를 반복하기 위해 모든 에지 조건을 올바르게 점검하고 있는지 (예 : 테스트에 사용 된 동일하지 않은 동일 인덱스 대신보다 적음) 색인 생성 등).


4
이미 2011 표준에 있었던 것처럼 새로운 것은 상대적입니다. 또한 두 번째 데모는 분명히 C ++이 아닙니다.
중복 제거기

또 다른 솔루션은이 플래그를 설정하는 것입니다 하나의 출구 지점을 사용하려면 longerLength = true다음 return longerLength.
Cullub

@Deduplicator 왜 두번째 데모 C ++가 아닌가? 왜 안보이거나 뭔가 분명한 것을 놓치고 있습니까?
Rakete1111

2
@ Rakete1111 원시 배열에는와 같은 속성이 없습니다 length. 실제로 포인터가 아닌 배열로 선언 된 경우을 사용할 수 sizeof있거나 std::array올바른 멤버 함수 size()인 경우 length속성 이 없습니다 .
IllusiveBrian

@IllusiveBrian : sizeof바이트로 표시 될 것입니다 ... C ++ 17 이후로 가장 일반적인 것은 std::size()입니다.
중복 제거기

9
int i = 0;
while (i < array.length && array[i] != value)
    i++;
return i < array.length;

[...] 모든 것이 더 분명이며 구조화 된 프로그래밍 방식으로.

좀 빠지는. 변수 i는 while 루프 외부에 존재하므로 외부 범위의 일부인 반면 -loop x의 (pun 의도 된) for는 루프 범위 내에서만 존재합니다. 범위는 프로그래밍에 구조를 도입하는 매우 중요한 방법 중 하나입니다.


1

1
@ruakh 나는 당신의 의견에서 무엇을 제거 해야할지 잘 모르겠습니다. 내 대답이 위키 페이지에 작성된 내용에 반대하는 것처럼 다소 수동적입니다. 정교하게 작성하십시오.
null

"구조화 된 프로그래밍"은 특정 의미를 가진 예술 용어이며 버전 # 1은 구조화 된 프로그래밍 규칙을 준수하지만 버전 # 1은 그렇지 않다는 OP는 객관적으로 정확합니다. 당신의 대답에서, 당신은 예술 용어에 익숙하지 않은 것으로 보였고, 그 용어를 문자 그대로 해석하고있었습니다. 내 의견이 왜 수동적이거나 공격적인지 잘 모르겠습니다. 나는 그것이 유익한 정보라는 것을 의미했습니다.
ruakh

@ruakh 나는 # 2 버전이 모든면에서 규칙을 더 준수한다는 것에 동의하지 않으며 내 대답에서 그것을 설명했습니다.
null

마치 주관적인 것처럼 "나는 동의하지 않는다"고 말하지만 그렇지 않습니다. 루프 내부에서 돌아 오는 것은 구조적 프로그래밍 규칙을 범주 적으로 위반하는 것입니다. 나는 많은 구조적 프로그래밍 애호가가 최소 범위 변수의 팬이라고 확신하지만 구조적 프로그래밍에서 벗어나 변수의 범위를 줄이면 구조적 프로그래밍, 기간을 벗어나 변수의 범위를 줄이면 실행 취소되지 않습니다. 그.
ruakh

2

두 루프는 다른 의미를 갖습니다.

  • 첫 번째 루프는 간단하게 예 / 아니오로 대답합니다. "배열에 원하는 객체가 포함되어 있습니까?" 가능한 가장 간단한 방식으로 수행합니다.

  • 두 번째 루프는 다음과 같은 질문에 답합니다. "배열에 원하는 객체가 포함되어 있으면 첫 번째 일치 인덱스는 무엇입니까?" 다시 말하지만, 가능한 가장 간단한 방식으로 그렇게합니다.

두 번째 질문에 대한 답변은 첫 번째 질문에 대한 답변보다 더 많은 정보를 제공하므로 두 번째 질문에 답변 한 다음 첫 번째 질문에 대한 답변을 도출하도록 선택할 수 있습니다. 그것은 return i < array.length;어쨌든 그 선 이하는 일입니다.

기존의보다 유연한 도구를 재사용 할 수 없다면 목적에 맞는 도구를 사용하는 것이 가장 좋습니다 . 즉 :

  • 루프의 첫 번째 변형을 사용하는 것이 좋습니다.
  • bool변수를 설정 하고 나누기 위해 첫 번째 변형을 변경하는 것도 좋습니다. (두 번째 return문장을 피하고 함수 반환 대신 변수에서 답변을 사용할 수 있습니다.)
  • 사용 std::find은 괜찮습니다 (코드 재사용!).
  • 그러나 찾기를 명시 적으로 코딩 한 다음 답변을 a로 줄이는 bool것은 아닙니다.

downvoters가 의견을 남길 경우 좋을 것입니다 ...
cmaster-monica reinstate monica

2

세 번째 옵션을 제안하겠습니다.

return array.find(value);

배열을 반복하는 데는 여러 가지 이유가 있습니다. 특정 값이 있는지 확인하고 배열을 다른 배열로 변환하고 집계 값을 계산하고 배열에서 일부 값을 필터링합니다. 일반 for 루프를 사용하는 경우 명확하지 않습니다. for 루프가 어떻게 사용되는지 한눈에 알 수 있습니다. 그러나 대부분의 현대 언어는 배열 데이터 구조에 풍부한 API를 사용하여 이러한 다른 의도를 매우 명확하게 만듭니다.

for 루프를 사용하여 한 배열을 다른 배열로 변환하는 것을 비교하십시오.

int[] doubledArray = new int[array.length];
for (int i = 0; i < array.length; i++) {
  doubledArray[i] = array[i] * 2;
}

JavaScript 스타일의 map함수를 사용하여 :

array.map((value) => value * 2);

또는 배열을 합산 :

int sum = 0;
for (int i = 0; i < array.length; i++) {
  sum += array[i];
}

대:

array.reduce(
  (sum, nextValue) => sum + nextValue,
  0
);

이것이 무엇을 이해하는 데 얼마나 걸립니까?

int[] newArray = new int[array.length];
int numValuesAdded = 0;

for (int i = 0; i < array.length; i++) {
  if (array[i] >= 0) {
    newArray[numValuesAdded] = array[i];
    numValuesAdded++;
  }
}

array.filter((value) => (value >= 0));

세 가지 경우 모두 for 루프를 읽을 수는 있지만 for 루프가 어떻게 사용되는지 파악하고 모든 카운터와 종료 조건이 올바른지 확인하는 데 잠시 시간을 투자해야합니다. 현대적인 람다 스타일 함수는 루프의 목적을 매우 명확하게하며, 호출되는 API 함수가 올바르게 구현되었음을 확실히 알고 있습니다.

JavaScript , Ruby , C #Java를 포함한 대부분의 최신 언어 는 이러한 스타일의 기능적 상호 작용을 배열 및 유사한 모음과 함께 사용합니다.

일반적으로 for 루프를 사용하는 것이 반드시 잘못이라고 생각하지는 않지만 개인적인 취향의 문제이지만 배열을 사용하는이 스타일을 사용하는 것이 좋습니다. 이것은 특히 각 루프가 수행하는 작업을 결정하는 선명도가 증가했기 때문입니다. 언어가 표준 라이브러리에 비슷한 기능이나 도구를 가지고 있다면이 스타일을 채택하는 것이 좋습니다!


2
권장 사항 array.find은 구현하는 가장 좋은 방법에 대해 논의해야하므로 질문을 제기합니다 array.find. 내장 된 find작동으로 하드웨어를 사용하지 않는 한 루프를 작성해야합니다.
Barmar

2
@Barmar 동의하지 않습니다. 내 대답에 표시된 것처럼, 많이 사용되는 많은 언어는 find표준 라이브러리 와 같은 기능을 제공합니다 . 의심 할 여지없이, 이러한 라이브러리는 findfor 루프를 사용하여 구현 하고 그 친족이지만, 이것이 좋은 기능입니다. 함수 소비자로부터 기술적 인 세부 사항을 추상화하여 프로그래머가 이러한 세부 사항에 대해 생각할 필요가 없습니다. 따라서 findfor 루프를 사용하여 구현할 가능성이 있어도 코드를 더 읽기 쉽게 만들 수 있으며 표준 라이브러리에서 자주 사용되므로 의미있는 오버 헤드 나 위험이 없습니다.
Kevin

4
그러나 소프트웨어 엔지니어는 이러한 라이브러리를 구현해야합니다. 라이브러리 작성자에게 응용 프로그램 프로그래머와 동일한 소프트웨어 엔지니어링 원칙이 적용되지 않습니까? 문제는 일반적으로 루프를 작성하는 것입니다. 특정 언어로 배열 요소를 검색하는 가장 좋은 방법은 아닙니다
Barmar

4
다시 말해서 배열 요소를 검색하는 것은 다른 루핑 기술을 보여주기 위해 사용한 간단한 예일뿐입니다.
Barmar

-2

그것은 모두 '더 나은'의 의미로 정확하게 요약됩니다. 실제 프로그래머에게는 일반적으로 효율적임을 의미합니다. 즉,이 경우 루프에서 직접 빠져 나가면 한 번의 추가 비교를 피할 수 있으며 부울 상수를 반환하면 중복 비교를 피할 수 있습니다. 사이클이 절약됩니다. Dijkstra는보다 쉽게 증명할 수있는 코드를 만드는 데 더 관심이 있습니다. [유럽의 CS 교육은 경제력이 코딩 관행을 지배하는 경향이있는 미국의 CS 교육보다 훨씬 더 '코드 정확성을 증명하는'것으로 보임]


3
PMar, 성능 측면에서 두 루프는 거의 동일합니다. 둘 다 두 가지 비교가 있습니다.
Danila Piatov

1
실제로 성능에 관심이 있다면 더 빠른 알고리즘을 사용합니다. 예를 들어 배열을 정렬하고 이진 검색을 수행하거나 해시 테이블을 사용하십시오.
user949300

Danila-이 뒤에 어떤 데이터 구조가 있는지 모릅니다. 반복자는 항상 빠릅니다. 인덱스 된 액세스는 선형 시간 일 수 있으며 길이도 존재하지 않아도됩니다.
gnasher729
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.