Czy w miarę możliwości wewnątrz pętli for należy przenieść warunek przerwania do pola warunku? [Zamknięte]


48

Czasami potrzebuję pętli, które wymagają takiej przerwy:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

Czuję się nieswojo z pisania

if(condition){
    break;
}

ponieważ zużywa 3 linie kodu. Odkryłem, że pętlę można przepisać jako:

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

Moje pytanie brzmi: czy dobrą praktyką jest przeniesienie warunku do pola warunku, aby w miarę możliwości zmniejszyć liczbę kodów?


7
Zależy od tego, co jest conditionw szczególności.
Bergi

4
Istnieje powód, dla którego misra zabrania używania „przerwania” i „kontynuowania” w pętli. Zobacz także: stackoverflow.com/q/3922599/476681
BЈовић

35
Myślę, że sama liczba linii nie jest prawdziwym problemem. Można go zapisać w jednym wierszu. if (warunek) przerwa; Moim zdaniem problemem jest czytelność. Osobiście nie lubię przerywania pętli innej niż używanie warunku pętli.
Joe

97
ponieważ zużywa 3 wiersze kodu . Bardzo, bardzo, bardzo zły powód, by nie lubić żadnego stylu. IMO „zużywa [linie] kodu” jest gdzieś pomiędzy nieistotnym a dobrym. Próba upakowania zbyt dużej logiki w zbyt małą liczbę wierszy powoduje nieczytelny kod i trudne do znalezienia błędy.
Andrew Henle

3
Być może opinia niepopularna: for(int i=0;i<array.length && !condition;i++)jest stosunkowo rzadka i może zostać przeoczona przez kogoś, kto tylko przegląda kod; może to być dobry przypadek użycia pętli whilelub do while, która częściej ma wiele warunków przerwania w definicji pętli.
Jules

Odpowiedzi:


154

Te dwa przykłady, które podałeś, nie są funkcjonalnie równoważne. W oryginale test warunków jest wykonywany po sekcji „jakiś inny kod”, podczas gdy w zmodyfikowanej wersji jest on wykonywany najpierw na początku treści pętli.

Kod nigdy nie powinien być przepisywany wyłącznie w celu zmniejszenia liczby wierszy. Oczywiście jest to miły bonus, gdy tak działa, ale nigdy nie należy tego robić kosztem czytelności lub poprawności.


5
Rozumiem twoje zdanie, ale zachowałem wystarczająco dużo starszego kodu tam, gdzie pętla ma kilkaset linii i jest wiele przerw warunkowych. To sprawia, że ​​debugowanie jest bardzo trudne, gdy oczekuje się, że kod dojdzie do oczekiwanej przerwy warunkowej, ale wcześniejszy kod wykonał najpierw przerwanie warunkowe. Jak radzisz sobie z tym kodem? W krótkich przykładach takich jak powyższe łatwo zapomnieć o tego rodzaju zbyt powszechnym scenariuszu.
Berin Loritsch

84
@BerinLoritsch: Prawidłowym sposobem radzenia sobie z tym jest brak pętli o długości kilkuset linii!
Chris

27
@Walfrat: Jeśli przepisywanie nie jest opcją, to tak naprawdę nie ma już pytania. Powiedziawszy, że zwykle całkiem bezpiecznie możesz użyć refaktora typu „Wyodrębnij metodę”, aby przenosić bloki kodu, co powinno skrócić główną pętlę metody i, miejmy nadzieję, uczynić logikę bardziej oczywistą. To naprawdę przypadek. Będę przestrzegać mojej ogólnej zasady krótkich metod i zdecydowanie krótszej logiki następnej pętli, ale nie chcę próbować mówić nic więcej w ogólnym sensie ...
Chris

4
@AndrewHenle zwięzłość to czytelność, przynajmniej w tym sensie, że zwiększenie szczegółowości zawsze zmniejsza czytelność i łatwość konserwacji. Zmniejszenie LOC osiąga się poprzez zwiększenie gęstości i podniesienie poziomu abstrakcji i jest bardzo ściśle skorelowane z utrzymywalnością, co potwierdzają inne pary Q / A w tym miejscu.
Leushenko

5
@Joe konstrukcja Do-While jest tak rzadka, że ​​jest podatna na wyzwalanie programistów, którzy muszą później zachować ten kod. Kilku deweloperów nigdy ich nie widziało! Nie jestem pewien, czy wykonanie polecenia w ogóle poprawiłoby czytelność - w ogóle zwiększyłoby trudność w zrozumieniu kodu. Jako przykład - moja obecna baza kodów ma około 15 lat i ma około 2 milionów LOC. Dotknęło go już około pięćdziesięciu programistów. Ma dokładnie zero pętli „do-while”!
T. Sar - Przywróć Monikę

51

Nie kupuję argumentu, że „zużywa 3 linie kodu” i dlatego jest zły. W końcu możesz po prostu napisać to jako:

if (condition) break;

i po prostu zużyj jedną linię.

Jeśli ifpojawia się w połowie pętli, to oczywiście musi istnieć jako osobny test. Jeśli jednak ten test pojawi się na końcu bloku, utworzono pętlę, która ma test kontynuacji na obu końcach pętli, co może zwiększyć złożoność kodu. Pamiętaj jednak, że czasami if (condition)test może mieć sens dopiero po wykonaniu bloku.

Ale zakładając, że tak nie jest, przyjmując inne podejście:

for(int i=0;i<array.length && !condition;i++){
    //some other code
}

warunki wyjścia są utrzymywane razem, co może uprościć rzeczy (zwłaszcza jeśli „ jakiś inny kod ” jest długi).

Oczywiście nie ma tu właściwej odpowiedzi. Więc pamiętaj o idiomach języka, jakie są konwencje kodowania twojego zespołu itp. Ale w sumie przyjąłbym podejście z pojedynczym testem, gdy ma to sens funkcjonalny.


14
@ jpmc26, Better? Nie. Prawidłowy styl alternatywny? Oczywiście.
David Arno

6
Lepiej, jak trudniej jest zepsuć, gdy kod jest później edytowany, i nie ma żadnych istotnych wad.
jpmc26

4
Trzymanie ich razem, ponieważ „zawartość pętli może być długa” wydaje się słabym argumentem. Jeśli treść jest trudna do odczytania, masz inne problemy niż zapisanie dwóch wierszy kodu.
BlueWizard

12
@ jpmc26 Nie zgadzam się. Nawiasy klamrowe są zbędnym bałaganem w najkrótszych, jednowarstwowych warunkach warunkowych zgodnie z tą odpowiedzią. Bez jest bardziej elegancki i łatwiejszy dla oka. Są jak trójskładnikowy operator warunkowy. Nigdy nie zapomniałem dodać nawiasów klamrowych, dzieląc je na większe bloki wypowiedzi; W końcu już dodałem nowe linie.
benxyzzy

3
To wszystko preferencja stylu, ale jeśli ktoś argumentuje, że jest bezpieczniejszy, nie zgodziłbym się z tym rozumowaniem. Ja sam wolę bez nawiasów klamrowych i w następnym wierszu, ale to nie znaczy, że jest mniej lub bardziej poprawny niż inne style
phflack

49

Tego rodzaju pytanie wywołało debatę prawie tak długo, jak trwa programowanie. Aby rzucić kapelusz na ring, wybrałbym wersję z breakwarunkiem i oto dlaczego (w tym konkretnym przypadku):

forPętla jest właśnie tam, aby określić wartości iteracji w pętli. Kodowanie warunku zerwania, które po prostu zamazuje logikę IMO.

Oddzielne breaksprawia, że ​​staje się jasne, że podczas normalnego przetwarzania wartości są takie, jak określono w forpętli i przetwarzanie powinno być kontynuowane, z wyjątkiem sytuacji, w której wystąpi warunek.

Jako trochę tła zacząłem kodować a break, potem przez lata wprowadzałem ten stan w forpętlę (pod kierunkiem wyjątkowego programisty), a potem wróciłem do breakstylu, ponieważ wydawał się on znacznie czystszy (i był dominującym stylem w różne tony kodu, które konsumowałem).

To kolejna z tych świętych wojen pod koniec dnia - niektórzy twierdzą, że nigdy nie powinieneś sztucznie wychodzić z pętli, w przeciwnym razie nie jest to prawdziwa pętla. Rób, co uważasz za czytelne (zarówno dla siebie, jak i innych). Jeśli naprawdę wolisz ograniczać liczbę naciśnięć klawiszy, zagraj w golfa w weekend - piwo opcjonalne.


3
Jeśli warunek ma dobrą nazwę, na przykład „znaleziono” (np. Szukasz czegoś w tablicy), dobrym pomysłem jest umieszczenie go na początku pętli for.
user949300

1
Powiedziano mi, że forpętle są używane, gdy znana jest liczba iteracji (np. Gdy nie ma warunku zerwania, ale koniec tablicy / listy), a whilekiedy nie. Wydaje się, że tak jest w przypadku whilepętli. Jeśli chodzi o czytelność, idx+=1wewnętrzna pętla jest znacznie czytelniejsza niż if/elseblok
Laiv

Nie możesz po prostu umieścić warunku przerwania w pętli, jeśli następuje po nim kod, więc przynajmniej będziesz musiał napisać „if (warunek) {znaleziono = prawda; kontynuuj;}
gnasher729

Jesteśmy tak przyzwyczajeni do for(int i=0; i<something;i++)pętli, że wydaje mi się, że połowa programistów tak naprawdę nie pamięta, że forpętle mogą być używane inaczej, a zatem trudno jest im zrozumieć tę &&conditionczęść.
Ralf Kleberhoff

@RalfKleberhoff Rzeczywiście. Widziałem wielu nowych użytkowników, którzy wyrazili niespodziankę, że wszystkie wyrażenia tripart są opcjonalne. Nawet nie pamiętam, kiedy ostatni raz widziałem for(;;)...
Robbie Dee

43

forpętle służą do iteracji nad czymś 1 - nie są to po prostu lol-pull-some-random-stuff-from-the-body-and-put-them-in-the-loop-header - trzy wyrażenia mają bardzo konkretne znaczenia:

  • Pierwszy służy do inicjowania iteratora . Może to być indeks, wskaźnik, obiekt iteracyjny lub cokolwiek innego - o ile jest używany do iteracji .
  • Drugi służy do sprawdzenia, czy doszliśmy do końca.
  • Trzeci dotyczy postępu iteratora.

Chodzi o to, aby oddzielić obsługę iteracji ( jak iterować) od logiki wewnątrz pętli ( co robić w każdej iteracji). Wiele języków zwykle ma pętlę dla każdego, która odciąża cię od szczegółów iteracji, ale nawet jeśli twój język nie ma takiej konstrukcji lub jeśli nie można jej użyć w twoim scenariuszu - nadal powinieneś ograniczyć nagłówek pętli do obsługi iteracji.

Musisz więc zadać sobie pytanie - czy Twój stan dotyczy obsługi iteracji czy logiki wewnątrz pętli? Możliwe, że chodzi o logikę wewnątrz pętli - dlatego należy to sprawdzić w pętli, a nie w nagłówku.

1 W przeciwieństwie do innych pętli, które „czekają”, aż coś się wydarzy. Pętla A for/ foreachpowinna mieć pojęcie „listy” elementów do iteracji - nawet jeśli ta lista jest leniwa, nieskończona lub abstrakcyjna.


5
To była moja pierwsza myśl, kiedy przeczytałem pytanie. Rozczarowany musiałem przewinąć pół tuzina odpowiedzi, aby zobaczyć, jak ktoś to powiedział
Nemo

4
Umm ... wszystkie pętle służą do iteracji - to znaczy słowo „iteracja” :-). Zakładam, że masz na myśli coś takiego jak „iterowanie po kolekcji” lub „iterowanie przy użyciu iteratora”?
psmears

3
@psmears OK, może wybrałem niewłaściwe słowo - angielski nie jest moim językiem ojczystym, a kiedy myślę o iteracji, myślę o „iteracji nad czymś”. Naprawię odpowiedź.
Idan Arye

Przypadkiem pośrednim jest sytuacja, w której warunek jest podobny someFunction(array[i]). Teraz obie części warunku dotyczą iteracji, i warto je połączyć &&w nagłówku pętli.
Barmar

Szanuję twoje stanowisko, ale się nie zgadzam. Myślę, że forpętle w sercu są licznikami, a nie pętlami nad listą, i jest to obsługiwane przez składnię forwe wcześniejszych językach (języki te często miały for i = 1 to 10 step 2składnię i steppolecenie - pozwalając nawet na wartość początkową, która nie jest indeksem pierwszego element - wskazówki w kontekście liczbowym, a nie w kontekście listy). Tylko przypadek użycia że myślę podpory forpętle jak tylko iteracji na listę jest parallelising, a to wymaga wyraźnej składni teraz w każdym razie, aby nie złamać kod robi, na przykład rodzaj.
Logan Pickup

8

Polecam używać wersji z if (condition). Jest bardziej czytelny i łatwiejszy do debugowania. Napisanie 3 dodatkowych wierszy kodu nie złamie ci palców, ale ułatwi kolejnej osobie zrozumienie twojego kodu.


1
Tylko jeśli (jak zostało to skomentowane) blok pętli nie ma setek linii kodu, a logika nie jest nauką rakietową. Myślę, że twój argument jest w porządku, ale brakuje mi czegoś takiego naming things, aby zrozumieć, co się dzieje i kiedy warunek złamania jest spełniony.
Laiv

2
@Laiv Jeśli blok pętli zawiera setki linii kodu, to są większe problemy niż pytanie, gdzie napisać warunek przerwania.
Aleksander

Dlatego zasugerowałem kontekstualność odpowiedzi, ponieważ nie zawsze jest to prawda.
Laiv

8

Jeśli iterujesz w kolekcji, w której niektóre elementy powinny zostać pominięte, polecam nową opcję:

  • Filtruj swoją kolekcję przed iteracją

Zarówno Java, jak i C # sprawiają, że jest to stosunkowo trywialne. Nie zdziwiłbym się, gdyby C ++ stworzył fantazyjny iterator, który pomija pewne elementy, które nie mają zastosowania. Ale tak naprawdę jest to opcja tylko wtedy, gdy twój język to obsługuje, a powodem tego jest breakto, że istnieją warunki, czy przetwarzasz element, czy nie.

W przeciwnym razie istnieje bardzo dobry powód, aby uczynić twój stan częścią pętli for - zakładając, że jego początkowa ocena jest poprawna.

  • Łatwiej jest zobaczyć, kiedy pętla kończy się wcześnie

Pracowałem nad kodem, w którym twoja pętla for zajmuje kilkaset wierszy z kilkoma warunkowymi i skomplikowaną matematyką. Problemy, z którymi się spotkasz to:

  • break polecenia ukryte w logice są trudne do znalezienia, a czasem zaskakujące
  • Debugowanie wymaga przejścia przez każdą iterację pętli, aby naprawdę zrozumieć, co się dzieje
  • Wiele kodu nie ma łatwo odwracalnych refaktoryzacji lub testów jednostkowych, aby pomóc w równoważności funkcjonalnej po przepisaniu

W takiej sytuacji zalecam następujące wytyczne w kolejności preferencji:

  • Przefiltruj kolekcję, aby wyeliminować potrzebę, breakjeśli to możliwe
  • Ustaw warunkową część warunków pętli for
  • Umieść warunek tak, aby breakw górnej części pętli, jeśli to w ogóle możliwe
  • Dodaj komentarz wielowierszowy, zwracając uwagę na przerwę i dlaczego

Te wytyczne zawsze podlegają poprawności twojego kodu. Wybierz opcję, która najlepiej oddaje intencję i poprawia przejrzystość kodu.


1
Większość tej odpowiedzi jest w porządku. Ale ... Widzę, jak filtrowanie kolekcji może wyeliminować potrzebę continuewyrażeń, ale nie widzę, w czym bardzo pomagają break.
user949300

1
@ user949300 takeWhileFiltr może zastąpić breakinstrukcje. Jeśli masz taki - na przykład Java dostaje je tylko w Jave 9 (nie dlatego, że samemu trudno jest go wdrożyć)
Idan Arye

1
Ale w Javie nadal możesz filtrować przed iteracją. Korzystanie ze strumieni (1.8 lub nowszych) lub przy użyciu kolekcji Apache (1.7 lub wcześniejszych).
Laiv

@IdanArye - istnieją biblioteki dodatków, które dodają takie funkcje do interfejsu API strumieni Java (np. JOO-lambda )
Jules

@IdanArye nigdy nie słyszał o TakeHhile przed komentarzem. Jawne powiązanie filtra z porządkiem wydaje mi się niezwykłe i hackerskie, ponieważ w „normalnym” filtrze każdy element zwraca wartość prawda lub fałsz, niezależnie od tego, co nastąpi przed czy po. W ten sposób możesz działać równolegle.
user949300,

8

Zdecydowanie zalecam podejście najmniej zaskakujące, chyba że uzyskasz znaczącą korzyść, robiąc inaczej.

Ludzie nie czytają każdej litery podczas czytania słowa i nie czytają każdego słowa podczas czytania książki - jeśli są biegli w czytaniu, patrzą na kontury słów i zdań i pozwalają mózgowi wypełnić resztę .

Dlatego od czasu do czasu programista zakłada, że ​​jest to tylko standard pętli, a nawet na to nie patrzy:

for(int i=0;i<array.length&&!condition;i++)

Jeśli chcesz używać tego stylu niezależnie od tego, zalecam zmianę części, for(int i=0;i<a ;i++)to powie mózgowi czytelnika, że ​​jest to standard pętli.


Kolejnym powodem, dla którego warto iść if- breakjest to, że nie zawsze możesz zastosować swoje podejście z forwarunkiem. Musisz użyć if- breakgdy warunek przerwania jest zbyt skomplikowany, aby ukryć się w forinstrukcji, lub opiera się na zmiennych, które są dostępne tylko w forpętli.

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

Więc jeśli zdecydujesz się pójść z if- breakjesteś objęty ubezpieczeniem. Ale jeśli zdecydujesz się iść z for-conditions, trzeba użyć kombinacji if- breaki for-conditions. Aby zachować spójność, będziesz musiał przesuwać warunki do przodu i do tyłu między forwarunkami i if- breakilekroć warunki się zmienią.


4

Twoja transformacja zakłada, że ​​cokolwiek conditionzostanie ocenione truepo wejściu do pętli. Nie możemy komentować poprawności tego w tym przypadku, ponieważ nie jest on zdefiniowany.

Po osiągnięciu sukcesu w „zmniejszeniu linii kodu” możesz przejść do przeglądania

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

i zastosuj tę samą transformację, dochodząc do

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

Co jest nieprawidłową transformacją, ponieważ teraz bezwarunkowo robisz further codeto, czego wcześniej nie robiłeś.

Znacznie bezpieczniejszym schematem transformacji jest wyodrębnienie some other codei ewaluacja conditiondo oddzielnej funkcji

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}

4

TL; DR Rób to, co najlepiej czyta w danych okolicznościach

Weźmy ten przykład:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

W tym przypadku nie chcemy, aby jakikolwiek kod w pętli for był wykonywany, jeśli somethingjest prawdziwy, więc przeniesienie testu somethingdo pola warunku jest rozsądne.

for ( int i = 1; i < array.length && !something; i++ )

Z drugiej strony, w zależności od tego, czy somethingmożna ustawić wartość trueprzed pętlą, ale nie w jej obrębie, może to zapewnić większą przejrzystość:

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

Teraz wyobraź sobie:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

Wysyłamy tam bardzo wyraźną wiadomość. Jeśli stanie somethingsię prawdą podczas przetwarzania tablicy, porzuć w tym momencie całą operację. Aby przenieść czek do pola warunku, musisz:

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

Prawdopodobnie „wiadomość” stała się mętna, a my sprawdzamy stan awarii w dwóch miejscach - co, jeśli stan awarii zmieni się i zapomnimy jedno z tych miejsc?

Istnieje oczywiście wiele innych kształtów i warunków dla pętli for, wszystkie z własnymi niuansami.

Napisz kod, aby dobrze czytał (jest to oczywiście nieco subiektywne) i zwięźle. Poza drakońskim zbiorem wytycznych dotyczących kodowania wszystkie „reguły” mają wyjątki. Pisz, co czujesz, najlepiej wyraża podejmowanie decyzji i minimalizuj szanse na przyszłe błędy.


2

Chociaż może to być atrakcyjne, ponieważ widzisz wszystkie warunki w nagłówku pętli i breakmoże być mylące w dużych blokach, forpętla jest wzorcem, w którym większość programistów oczekuje pętli w pewnym zakresie.

Zwłaszcza, że ​​to robisz, dodanie dodatkowego warunku przerwania może powodować zamieszanie i trudniej jest sprawdzić, czy jest funkcjonalnie równoważny.

Często dobrą alternatywą jest użycie pętli whilelub do {} while, która sprawdza oba warunki, zakładając, że tablica zawiera co najmniej jeden element.

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

Korzystając z pętli, która sprawdza tylko warunek i nie uruchamia kodu z nagłówka pętli, bardzo jasno określasz, kiedy warunek jest sprawdzany oraz jaki jest faktyczny stan i jaki kod ma skutki uboczne.


Pomimo tego, że mam już kilka dobrych odpowiedzi, chciałem szczególnie głosować za tym, ponieważ stanowi to ważną kwestię: dla mnie posiadanie czegoś innego niż proste sprawdzanie ograniczeń w pętli for ( for(...; i <= n; ...)) faktycznie utrudnia odczytanie kodu, ponieważ mam zatrzymać się i pomyśleć o tym, co się tutaj dzieje, i może całkowicie spudłować ten warunek przy pierwszym przejeździe, ponieważ nie oczekuję, że tak się stanie. Dla mnie forpętla oznacza, że ​​zapętlasz się w pewnym zakresie, jeśli istnieją specjalne warunki wyjścia, należy to wyraźnie zaznaczyć za pomocą if, lub możesz użyć while.
CompuChip

1

Jest to złe, ponieważ kod ma być odczytywany przez ludzi - nieidiomatyczne forpętle są często mylące z czytaniem, co oznacza, że ​​błędy prawdopodobnie będą się tutaj ukrywać, ale oryginalny kod mógł zostać poprawiony przy jednoczesnym zachowaniu zarówno krótki i czytelny.

Jeśli chcesz znaleźć wartość w tablicy (podany przykładowy kod jest nieco ogólny, ale równie dobrze może to być ten wzorzec), możesz po prostu jawnie spróbować użyć funkcji zapewnianej przez język programowania specjalnie do tego celu. Na przykład (pseudo-kod, ponieważ nie określono języka programowania, rozwiązania różnią się w zależności od konkretnego języka).

array.find(item -> item.valid)

Powinno to wyraźnie skrócić rozwiązanie, jednocześnie upraszczając je, ponieważ kod mówi konkretnie, czego potrzebujesz.


1

Moim zdaniem kilka powodów mówi, że nie powinieneś.

  1. Zmniejsza to czytelność.
  2. Wynik może nie być taki sam. Można to jednak zrobić za pomocą do...whilepętli (nie while), ponieważ warunek jest sprawdzany po wykonaniu kodu.

Ale oprócz tego rozważ to,

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

Czy naprawdę możesz to osiągnąć, dodając te warunki do tego forwarunku?


Co to jest „ otwarcie ”? Masz na myśli „ opinię ”?
Peter Mortensen

Co rozumiesz przez „kilka otwartych powodów, które mówią, że nie powinieneś” ? Czy możesz rozwinąć?
Peter Mortensen

0

Myślę, że usunięcie breakmoże być dobrym pomysłem, ale nie zapisywanie wierszy kodu.

Zaletą pisania go bez breakjest to, że warunek końcowy pętli jest oczywisty i wyraźny. Po zakończeniu pętli i wykonaniu następnego wiersza programu warunek pętli i < array.length && !conditionmusi być fałszywy. Dlatego albo ibyła większa lub równa długości tablicy, albo conditionjest prawdziwa.

W wersji z breakjest ukryta gotcha. Warunek pętli mówi, że pętla kończy się, gdy uruchomisz jakiś inny kod dla każdego poprawnego indeksu tablicy, ale w rzeczywistości istnieje breakinstrukcja, która mogłaby wcześniej zakończyć pętlę i nie zobaczysz jej, dopóki nie przejrzysz kodu pętli bardzo ostrożnie. Zautomatyzowane analizatory statyczne, w tym optymalizujące kompilatory, mogą mieć problemy z wydedukowaniem, jakie są końcowe warunki pętli.

To był pierwotny powód, dla którego Edgar Dijkstra napisał „Goto uważane za szkodliwe”. Nie była to kwestia stylu: zerwanie z pętlą utrudnia formalne uzasadnienie stanu programu. break;W swoim życiu napisałem wiele stwierdzeń, a gdy byłem dorosły, napisałem nawet goto(aby przełamać wiele poziomów wewnętrznej pętli krytycznej pod względem wydajności, a następnie przejść do kolejnej gałęzi drzewa wyszukiwania). Jestem jednak znacznie bardziej prawdopodobne, aby napisać warunkowe returnoświadczenie pętli (która nigdy nie działa kod po pętli i dlatego nie może zepsuć jego post-warunki) niż break.

Postscriptum

W rzeczywistości refaktoryzacja kodu w celu uzyskania takiego samego zachowania może wymagać większej liczby wierszy kodu. Twoje refaktoryzacja może być ważna dla konkretnego kodu lub jeśli możemy udowodnić, że conditionzacznie się fałsz. Ale twój przykład jest równoważny w ogólnym przypadku:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

Jeśli jakiś inny kod nie jest jednowierszowy, możesz przenieść go do funkcji, aby się nie powtarzać, a następnie prawie przerobiłeś pętlę na funkcję rekurencyjną.

Rozumiem, że to nie był główny punkt twojego pytania i upraszczałeś to.


Problemem nie jest to, że przerwa utrudnia kłótnie o kod, problem polega na tym, że przerwa w losowych miejscach powoduje, że kod jest skomplikowany. Jeśli jest to rzeczywiście potrzebne, nie jest trudno argumentować z powodu przerwy, ale ponieważ kod musi robić skomplikowane rzeczy.
gnasher729

Oto dlaczego się nie zgadzam: jeśli wiesz, że nie ma breakinstrukcji, to wiesz, jaki jest warunek pętli i że pętla zatrzymuje się, gdy warunek ten jest fałszywy. Jeśli istnieje break? Następnie istnieje wiele sposobów zakończenia pętli, aw ogólnym przypadku stwierdzenie, kiedy jeden z nich może zatrzymać pętlę, dosłownie rozwiązuje problem zatrzymania. W praktyce bardziej obawiam się, że pętla wygląda tak, jakby robiła jedną rzecz, ale w rzeczywistości ktokolwiek napisał kod po tym, jak pętla przeoczyła przypadek krawędzi zakopany w jego złożonej logice. Rzeczy w pętli są trudne do przeoczenia.
Davislor

0

Tak, ale twoja składnia jest niekonwencjonalna i dlatego zła (tm)

Mam nadzieję, że Twój język obsługuje coś takiego

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}

2
może powinienem był użyć innego słowa „warunek”, nie miałem na myśli dosłownie tego samego warunku w przykładzie
Ewan

1
Pętla for nie tylko nie jest wcale niekonwencjonalna, nie ma absolutnie żadnego powodu, dla którego pętla while tej formy jest lepsza niż jej odpowiednik. W rzeczywistości większość ludzi powiedziałaby, że jest gorzej, na przykład autorzy głównych wytycznych CPP
Sean Burton

2
Niektórzy ludzie nienawidzą twórczych alternatyw. Lub patrzenie na problem w inny sposób niż ten, który pojawia się w ich głowach. Lub bardziej ogólnie, inteligentne osły. Czuję twój ból, stary!
Martin Maat

1
@sean przepraszam stary, wiem, że ludzie nie lubią mówić, że się mylą, ale odsyłam do es.77
Ewan

2
To whilepodkreśla potwora w kodzie - wyjątek inaczej ukryte w rutynowej / code przyziemne. Logika biznesowa jest z przodu i na środku, mechanika zapętlania jest uwzględniona. Unika reakcji „poczekaj minutę ...” na foralternatywną pętlę. Ta odpowiedź rozwiązuje problem. absolutnie głosuj.
radarbob

0

Jeśli obawiasz się, że zbyt skomplikowane forstwierdzenie jest trudne do odczytania, jest to z pewnością uzasadniona obawa. Problemem jest także marnowanie przestrzeni pionowej (choć mniej krytycznej).

(Osobiście nie podoba mi się zasada, że ifinstrukcje muszą zawsze mieć nawiasy klamrowe, co jest w dużej mierze przyczyną zmarnowanej przestrzeni pionowej tutaj. Zauważ, że problemem jest marnowanie przestrzeni pionowej. Korzystanie z przestrzeni pionowej ze znaczącymi liniami nie powinno stanowić problemu.)

Jedną z opcji jest podzielenie go na wiele linii. To jest zdecydowanie to, co powinieneś zrobić, jeśli używasz naprawdę złożonych forinstrukcji, z wieloma zmiennymi i warunkami. (Jest to dla mnie lepsze wykorzystanie przestrzeni pionowej, dając jak najwięcej linii, co ma sens).

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

Lepszym podejściem może być próba użycia bardziej deklaratywnej i mniej imperatywnej formy. Różni się to w zależności od języka lub konieczne może być napisanie własnych funkcji, aby włączyć tego rodzaju funkcje. To zależy również od tego, co conditionfaktycznie jest. Prawdopodobnie musi być w stanie się jakoś zmienić, w zależności od tego, co znajduje się w tablicy.

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void

Podzielenie złożonego lub niezwykłego forstwierdzenia na wiele wierszy jest najlepszym pomysłem w całej tej dyskusji
user949300,

0

Jedna rzecz, o której zapomniano: kiedy zrywam z pętli (nie wykonuję wszystkich możliwych iteracji, bez względu na to, jak zaimplementowane), zwykle jest tak, że nie tylko chcę wyjść z pętli, ale chcę również wiedzieć, dlaczego tak się stało . Na przykład, jeśli szukam elementu X, nie tylko przerywam pętlę, ale chcę również wiedzieć, że X został znaleziony i gdzie on jest.

W takiej sytuacji stan poza pętlą jest całkiem naturalny. Więc zaczynam od

bool found = false;
size_t foundIndex;

a w pętli napiszę

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

z pętlą for

for (i = 0; i < something && ! found; ++i)

Teraz sprawdzenie stanu w pętli jest całkiem naturalne. To, czy istnieje instrukcja „break”, zależy od tego, czy w pętli jest dalsze przetwarzanie, którego chcesz uniknąć. Jeśli potrzebne jest pewne przetwarzanie, a inne nie, możesz mieć coś takiego

if (! found) { ... }

gdzieś w twojej pętli.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.