Czy używanie przerwy w pętli for jest złą praktyką? [Zamknięte]


123

Czy używanie breakinstrukcji wewnątrz forpętli jest złą praktyką ?

Powiedz, szukam wartości w tablicy. Porównaj wewnątrz pętli for i gdy zostanie znaleziona wartość, break;aby wyjść z pętli for.

Czy to zła praktyka? Widziałem alternatywę używany: zdefiniować zmienną vFoundi ustawić go na true, gdy wartość jest znaleźć i sprawdzić vFoundw forwarunku instrukcji. Ale czy konieczne jest utworzenie nowej zmiennej tylko w tym celu?

Pytam w kontekście normalnej pętli for w C lub C ++.

PS: Wytyczne kodowania MISRA odradzają stosowanie przerwy.


28
Nie breakgoto
plasuj

2
Eee, nie umieściłem ich w tej samej lidze co ... Zasady MISRA określają przerwę, jeśli dobrze pamiętam.
kiki

1
Jedynym powodem, dla którego mogę wymyślić, aby nie używać przerwania wewnątrz pętli, jest to, że nadal musisz przetwarzać więcej elementów, które mogą zmienić wynik tego, co robisz ...
Younes,

4
Zasady MISRA zostały złagodzone: misra.org.uk/forum/viewtopic.php?f=75&t=298
Johan

Odpowiedzi:


122

Wiele odpowiedzi tutaj, ale jeszcze tego nie widziałem:

Większość „niebezpieczeństw” związanych z używaniem breaklub continuew pętli for jest negowana, jeśli piszesz uporządkowane, łatwe do odczytania pętle. Jeśli treść twojej pętli obejmuje kilka długości ekranu i ma wiele zagnieżdżonych podbloków, tak, możesz łatwo zapomnieć, że jakiś kod nie zostanie wykonany po przerwie. Jeśli jednak pętla jest krótka i trafna, cel instrukcji break powinien być oczywisty.

Jeśli pętla staje się zbyt duża, zamiast tego użyj jednego lub więcej dobrze nazwanych wywołań funkcji w pętli. Jedynym prawdziwym powodem, aby tego uniknąć, są wąskie gardła przetwarzania.


11
Całkiem prawdziwe. Oczywiście, jeśli pętla jest tak duża i złożona, że ​​trudno jest zobaczyć, co się w niej dzieje, jest to problem, niezależnie od tego, czy masz przerwę, czy nie.
Jay

1
Inną kwestią jest to, że przerwy i kontynuacja powodują problemy z refaktoryzacją. Może to oznaczać, że jest to zła praktyka. Intencja kodu jest również wyraźniejsza, gdy używa się instrukcji if.
Ed Greaves,

Te „dobrze nazwane wywołania funkcji” mogą być lokalnie zdefiniowanymi funkcjami lambda zamiast zewnętrznie zdefiniowanymi funkcjami prywatnymi, które nie będą używane gdzie indziej.
DavidRR,

135

Nie, przerwa to właściwe rozwiązanie.

Dodanie zmiennej boolowskiej utrudnia czytanie kodu i dodaje potencjalne źródło błędów.


2
Zgoda. Zwłaszcza jeśli chcesz wyjść z pętli pod więcej niż jednym warunkiem. Wartość logiczna opuszczająca pętlę może być wtedy naprawdę myląca.
Rontologist

2
Dlatego używam gotodo wyrwania się z zagnieżdżonych pętli poziomu 2+ - ponieważ nie mabreak(level)
Петър Петров

3
Wolałbym raczej umieścić kod pętli w funkcji i po prostu wrócić. Pozwala to uniknąć goto i rozbić kod na mniejsze fragmenty.
Dave Branton,

53

Możesz znaleźć wszelkiego rodzaju profesjonalny kod z instrukcjami „break”. Używanie tego w razie potrzeby ma sens. W twoim przypadku ta opcja jest lepsza niż tworzenie osobnej zmiennej tylko w celu wyjścia z pętli.


47

Używanie breakjak również continuew forpętli jest w porządku.

Upraszcza kod i poprawia jego czytelność.


Tak… Przez pewien czas nie podobał mi się ten pomysł i kodowałem go wokół niego, ale szybko zdałem sobie sprawę, że… „obejście” było często koszmarem związanym z konserwacją. Cóż, nie koszmar, ale bardziej podatny na błędy.
MetalMikester

24

Daleko od złej praktyki, Python (i inne języki?) Rozszerzył strukturę forpętli , tak że jej część zostanie wykonana tylko wtedy, gdy pętla tego nie zrobi break .

for n in range(5):
    for m in range(3):
        if m >= n:
            print('stop!')
            break
        print(m, end=' ')
    else:
        print('finished.')

Wynik:

stop!
0 stop!
0 1 stop!
0 1 2 finished.
0 1 2 finished.

Równoważny kod bez breaki to przydatne else:

for n in range(5):
    aborted = False
    for m in range(3):
        if not aborted:
            if m >= n:
                print('stop!')
                aborted = True
            else:            
                print(m, end=' ')
    if not aborted:
        print('finished.')

2
Oooh, podoba mi się to i czasami marzyłem o tym w C. Ładna składnia, którą można by zaadaptować do przyszłego języka podobnego do C bez dwuznaczności.
supercat

„Język podobny do języka C”: P
nirvanaswap

15

Nie ma nic złego w używaniu instrukcji break, ale zagnieżdżone pętle mogą być mylące. Aby poprawić czytelność, wiele języków ( przynajmniej Java ) obsługuje łamanie etykiet, co znacznie poprawi czytelność.

int[] iArray = new int[]{0,1,2,3,4,5,6,7,8,9};
int[] jArray = new int[]{0,1,2,3,4,5,6,7,8,9};

// label for i loop
iLoop: for (int i = 0; i < iArray.length; i++) {

    // label for j loop
    jLoop: for (int j = 0; j < jArray.length; j++) {

        if(iArray[i] < jArray[j]){
            // break i and j loops
            break iLoop;
        } else if (iArray[i] > jArray[j]){  
            // breaks only j loop
            break jLoop;
        } else {
            // unclear which loop is ending
            // (breaks only the j loop)
            break;
        }
    }
}

Powiem, że instrukcje break (and return) często zwiększają cykliczną złożoność, co utrudnia udowodnienie, że kod działa poprawnie we wszystkich przypadkach.

Jeśli rozważasz użycie przerwy podczas iteracji sekwencji dla określonego elementu, możesz ponownie rozważyć strukturę danych używaną do przechowywania danych. Używanie czegoś takiego jak zestaw lub mapa może zapewnić lepsze wyniki.


4
+1 za cyklomatyczną złożoność.
sp00m

Programiści .NET mogą chcieć zbadać użycie LINQ jako potencjalnej alternatywy dla złożonych forpętli.
DavidRR

14

break jest całkowicie akceptowalnym poleceniem do użycia (tak samo jest continue , btw). Chodzi o czytelność kodu - o ile nie masz zbyt skomplikowanych pętli i tym podobnych, wszystko jest w porządku.

To nie tak, że byli w tej samej lidze co goto . :)


Widziałem, jak argumentował, że stwierdzenie przerwy jest w rzeczywistości goto .
glenatron

3
Problem z goto polega na tym, że możesz wskazać go w dowolnym miejscu. Zarówno przerywaj, jak i kontynuuj, zachowując modułowość kodu. Ten argument jest na tym samym poziomie, co posiadanie jednego punktu wyjścia dla każdej funkcji.
Zachary Yates

1
Słyszałem, jak argumentował, że posiadanie wielu punktów wyjścia dla funkcji jest w rzeczywistości goto . ; D
glenatron

2
@glenatron Problem z goto nie jest instrukcją goto, jest to wprowadzenie etykiety, do której skacze goto. To jest problem. Jeśli czytasz instrukcję goto, nie ma wątpliwości co do przepływu kontroli. Kiedy czytasz etykietę, jest dużo niepewności co do przepływu kontroli (gdzie są te wszystkie rzeczy, które przenoszą kontrolę na tę etykietę?). Instrukcja break nie wprowadza etykiety, więc nie wprowadza żadnych nowych komplikacji.
Stephen C. Steel

1
„Przerwa” to wyspecjalizowany element goto, który może opuszczać tylko bloki. Historyczne problemy z „goto” polegały (1) na tym, że mogło ono być i często było używane do konstruowania nakładających się bloków pętli w sposób, który nie jest możliwy przy użyciu odpowiednich struktur sterujących; (2) powszechnym sposobem wykonania konstrukcji „jeśli-to”, która zwykle była „fałszem”, było rozgałęzienie prawdziwej wielkości do niepowiązanego miejsca w kodzie, a następnie rozgałęzienie z powrotem. Kosztowałoby to dwie gałęzie w rzadkim przypadku i zero w zwykłym przypadku, ale sprawiło, że kod wyglądał okropnie.
supercat

14

Zasada ogólna: jeśli przestrzeganie zasady wymaga zrobienia czegoś bardziej niezręcznego i trudnego do przeczytania, a następnie złamania reguły, należy ją złamać.

W przypadku zapętlania się, dopóki czegoś nie znajdziesz, napotkasz problem rozróżnienia znalezionego i nie znalezionego, gdy wyjdziesz. To jest:

for (int x=0;x<fooCount;++x)
{
  Foo foo=getFooSomehow(x);
  if (foo.bar==42)
    break;
}
// So when we get here, did we find one, or did we fall out the bottom?

Więc w porządku, możesz ustawić flagę lub zainicjować wartość „znalezioną” na null. Ale

Dlatego generalnie wolę przekierowywać moje wyszukiwania do funkcji:

Foo findFoo(int wantBar)
{
  for (int x=0;x<fooCount;++x)
  {
    Foo foo=getFooSomehow(x);
    if (foo.bar==wantBar)
      return foo;
  }
  // Not found
  return null;
}

Pomaga to również uporządkować kod. W głównym wierszu „znajdź” staje się pojedynczą instrukcją, a gdy warunki są złożone, są zapisywane tylko raz.


1
Dokładnie to, co chciałem powiedzieć. Często, gdy używam break, próbuję znaleźć sposób na refaktoryzację kodu w funkcję, aby móc użyć returnzamiast tego.
Rene Saarsoo

W 100% zgadzam się z Tobą, nie ma nic złego w używaniu przerwy. Jednak ogólnie wskazuje, że kod, w którym się znajduje, może wymagać wyodrębnienia do funkcji, w której break zostanie zastąpiony zwrotem. Powinien być raczej traktowany jako „zapach kodu” niż zwykły błąd.
vascowhite

Twoja odpowiedź może skłonić niektórych do zbadania celowości stosowania wielu instrukcji powrotu .
DavidRR

13

To zależy od języka. Chociaż możesz tutaj sprawdzić zmienną boolowską:

for (int i = 0; i < 100 && stayInLoop; i++) { ... }

nie można tego zrobić podczas iterowania po tablicy:

for element in bigList: ...

W każdym razie breaksprawiłoby, że oba kody byłyby bardziej czytelne.


7

W twoim przykładzie nie znasz liczby iteracji pętli for . Dlaczego nie używać while pętli zamiast, który pozwala liczba iteracji być nieokreślona na początku?

Nie jest zatem konieczne stosowanie przerwy statemement w ogóle, jak pętla może być lepiej zaznaczono jako while pętli.


1
Pętla „for” jest często dobra, jeśli istnieje znana maksymalna liczba iteracji. To powiedziawszy, pętla while (1) jest czasami lepsza w scenariuszu, w którym ktoś szuka przedmiotu i planuje go utworzyć, jeśli nie istnieje. W takim przypadku, jeśli kod znajdzie element, dokonuje bezpośredniej przerwy, a jeśli trafi na koniec tablicy, tworzy nowy element, a następnie przerywa.
supercat

2
W podanym przykładzie - przeszukiwaniu tablicy w poszukiwaniu klucza, pętla for jest właściwą konstrukcją idiomatyczną. Nie używasz pętli while do przechodzenia przez tablicę. Używasz pętli for. (lub pętla for-each, jeśli jest dostępna). Robisz to z uprzejmości dla innych programistów, ponieważ rozpoznają oni konstrukcję "for (int i = 0; i <ra.length; i ++) {}" jako "Spacer po tablicy" Dodanie przerwy do tej konstrukcji jest po prostu stwierdzenie „Zakończ wcześniej, jeśli potrafisz”.
Chris Cudmore

7

Zgadzam się z innymi, którzy zalecają używanie break. Oczywiste pytanie brzmi: dlaczego ktoś miałby zalecać inaczej? Cóż ... kiedy używasz break, pomijasz resztę kodu w bloku i pozostałe iteracje. Czasami powoduje to błędy, na przykład:

  • zasób uzyskany na górze bloku może zostać zwolniony na dole (jest to prawdą nawet dla bloków wewnątrz forpętli), ale ten krok zwolnienia może zostać przypadkowo pominięty, gdy „przedwczesne” wyjście jest spowodowane breakinstrukcją (w „nowoczesnym” C ++, „RAII” jest używany do obsługi tego w niezawodny i bezpieczny sposób: w zasadzie destruktory obiektów niezawodnie zwalniają zasoby bez względu na sposób zamykania zakresu)

  • ktoś może zmienić test warunkowy w forinstrukcji, nie zauważając, że istnieją inne zdelokalizowane warunki wyjścia

  • Odpowiedź ndim wskazuje, że niektórzy ludzie mogą unikać breaks, aby utrzymać względnie spójny czas wykonywania pętli, ale porównujesz breakz użyciem boolowskiej zmiennej kontrolnej wczesnego wyjścia, gdzie to się nie sprawdza

Od czasu do czasu ludzie obserwujący takie błędy zdają sobie sprawę, że można im zapobiec / złagodzić tę zasadę „bez przerw”… rzeczywiście, istnieje cała powiązana strategia „bezpieczniejszego” programowania zwanego „programowaniem strukturalnym”, gdzie każda funkcja powinna mieć pojedynczy punkt wejścia i wyjścia (tj. brak goto, brak wcześniejszego powrotu). Może wyeliminować niektóre błędy, ale niewątpliwie wprowadza inne. Dlaczego oni to robią?

  • mają strukturę programistyczną, która zachęca do określonego stylu programowania / kodu i mają dowody statystyczne, że daje to korzyści netto w tej ograniczonej strukturze, lub
  • byli pod wpływem wytycznych programowania lub doświadczenia w takich ramach, lub
  • są po prostu dyktatorskimi idiotami, albo
  • dowolna z powyższych + bezwładność historyczna (istotna, ponieważ uzasadnienia mają większe zastosowanie do C niż współczesnego C ++).

Cóż, tak, ale z drugiej strony widziałem błędy w kodzie ludzi, którzy religijnie próbowali tego uniknąć break. Uniknęli tego, ale nie przemyśleli warunków, które zastąpiły użycie przerwy.
Tomáš Zato - Przywróć Monikę

5

Jest całkowicie ważny w użyciu break- jak zauważyli inni, nigdzie nie jest w tej samej lidze co goto.

Chociaż możesz chcieć użyć vFoundzmiennej, gdy chcesz sprawdzić poza pętlą, czy wartość została znaleziona w tablicy. Również z punktu widzenia łatwości konserwacji, posiadanie wspólnej flagi sygnalizującej kryteria wyjścia może być przydatne.


5

Dokonałem analizy kodu źródłowego, nad którym obecnie pracuję (40 000 linii JavaScript).

Znalazłem tylko 22 breakstwierdzenia, z których:

  • 19 zostało użytych w switchinstrukcjach (w sumie mamy tylko 3 instrukcje switch!).
  • 2 były używane wewnątrz forpętli - kod, który od razu sklasyfikowałem jako do ponownego przekształcenia w osobne funkcje i zastąpiony returninstrukcją.
  • A jeśli chodzi o ostatnią pętlę breakwewnętrzną while... Pobiegłem, git blameżeby zobaczyć, kto napisał to gówno!

Więc według moich statystyk: jeśli breakjest używany na zewnątrz switch, jest to zapach kodu.

Szukałem też continuewypowiedzi. Nie znaleziono.


4

Nie widzę żadnego powodu, dla którego byłaby to zła praktyka POD WZGLĘDEM, że chcesz zakończyć przetwarzanie w tym momencie.


4

W świecie osadzonych jest dużo kodu, który wykorzystuje następującą konstrukcję:

    while(1)
    { 
         if (RCIF)
           gx();
         if (command_received == command_we_are_waiting_on)
           break;
         else if ((num_attempts > MAX_ATTEMPTS) || (TickGet() - BaseTick > MAX_TIMEOUT))
           return ERROR;
         num_attempts++;
    }
    if (call_some_bool_returning_function())
      return TRUE;
    else
      return FALSE;

To bardzo ogólny przykład, wiele rzeczy dzieje się za kurtyną, w szczególności przerw. Nie używaj tego jako standardowego kodu, po prostu próbuję zilustrować przykład.

Osobiście uważam, że nie ma nic złego w pisaniu pętli w ten sposób, o ile zostanie podjęta odpowiednia uwaga, aby nie pozostawać w pętli w nieskończoność.


1
Czy mógłbyś to rozwinąć? Wydaje mi się, że pętla absolutnie nic nie robi ... chyba że continuew logice aplikacji znajdują się instrukcje. Są tam?
Rene Saarsoo

1
Instrukcje continue są niepotrzebne, ponieważ jesteś już związany nieskończoną pętlą. Zasadniczo wykonujesz niezbędną logikę wymaganą do osiągnięcia celu, a jeśli ten cel nie zostanie osiągnięty w n liczbie prób lub upłynie limit czasu, opuszczasz pętlę za pomocą konstrukcji przerywającej i podejmujesz działanie naprawcze lub informujesz kod wywołujący o błędzie. Widzę ten typ kodu często w uC, które komunikują się przez wspólną magistralę (RS-485 itp.) Zasadniczo próbujesz przejąć linię i komunikować się bez kolizji, jeśli zdarzają się kolizje, czekasz czas określony przez losową liczbę i próbujesz jeszcze raz.
Nate

1
I odwrotnie, jeśli wszystko pójdzie dobrze, możesz również użyć instrukcji break do zakończenia, gdy warunek zostanie spełniony. W tym przypadku kod następujący po pętli jest wykonywany i wszyscy są zadowoleni.
Nate,

"if (call_some_bool_returning_function ()) return TRUE; else return FALSE;" może być po prostu „return call_some_bool_returning_function ()”
frankster

3

Zależy od twojego przypadku użycia. Istnieją aplikacje, w których czas wykonywania pętli for musi być stały (np. W celu spełnienia pewnych ograniczeń czasowych lub ukrycia wewnętrznych danych przed atakami opartymi na czasie).

W takich przypadkach ma sens nawet ustawienie flagi i sprawdzanie wartości flagi tylko PO wykonaniu wszystkich foriteracji pętli. Oczywiście wszystkie iteracje pętli for muszą uruchamiać kod, który nadal zajmuje mniej więcej tyle samo czasu.

Jeśli nie zależy Ci na czasie wykonywania ... użyj break;i, continue;aby kod był łatwiejszy do odczytania.


1
Jeśli ważne jest wyczucie czasu, lepiej zrobić kilka uśpień i oszczędzić zasoby systemowe, niż pozwolić programowi wykonywać operacje, o których wiadomo, że są bezużyteczne.
Bgs,

2

Na regułach MISRA 98, które są używane w mojej firmie w C dev, instrukcja break nie powinna być używana ...

Edycja: przerwa jest dozwolona w MISRA '04


2
Właśnie dlatego zadałem to pytanie! Nie znajduję żadnego dobrego powodu, dla którego taka zasada istnieje jako wskazówka dotycząca kodowania. Poza tym, jak wszyscy mówili, złamać; wygląda lepiej.
kiki

1
Nie wiem dokładnie, dlaczego jest to zabronione (mądrzejsza osoba niż ja myśli o tym ...), ale przerwy (i wielokrotne powroty) są lepsze dla czytelności (moim zdaniem, ale moje opinie nie są regułami korporacyjnymi ... )
Benoît

1
Uh, zasady MISRA zezwalają na stosowanie instrukcji przerwania: misra.org.uk/forum/viewtopic.php?f=75&t=298
luis.espinal

Użyliśmy zasad MISRA 98 ... Dokładnie tak, MISRA 2004 zmienia zasady
Benoît

0

Oczywiście break;rozwiązaniem jest zatrzymywanie pętli for lub foreach. Użyłem go w php w pętli foreach i for i stwierdziłem, że działa.


0

Nie zgadzam się!

Dlaczego miałbyś ignorować wbudowaną funkcjonalność pętli for, aby stworzyć własną? Nie musisz wymyślać koła na nowo.

Myślę, że bardziej sensowne jest umieszczanie czeków na górze pętli for

for(int i = 0; i < myCollection.Length && myCollection[i].SomeValue != "Break Condition"; i++)
{
//loop body
}

lub jeśli najpierw musisz przetworzyć wiersz

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
//loop body
}

W ten sposób możesz napisać funkcję, która wykona wszystko i stworzy znacznie bardziej przejrzysty kod.

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

Lub jeśli twój stan jest skomplikowany, możesz też przenieść ten kod!

for(int i = 0; i < myCollection.Length && BreakFunctionCheck(i, myCollection); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

„Kod Profesjonalny”, pełen przerw, nie brzmi dla mnie jak kod profesjonalny. Brzmi jak leniwe kodowanie;)


4
leniwe kodowanie to profesjonalny kod :)
Jason

Tylko jeśli nie jest to
upierdliwe w

2
Zwykle ćwiczę „GTFO ASAP”. Oznacza to, że jeśli mogę czysto wyjść ze struktury, powinienem to zrobić tak szybko, jak to możliwe, i jak najbliżej warunku określającego wyjście.
Chris Cudmore

Cóż, to też działa. Myślę, że argumentem, który staram się przedstawić, jest to, że jeśli używasz pętli for, nie zaszkodzi używać jej wbudowanej funkcjonalności, aby kod był czytelny i modułowy. Jeśli absolutnie potrzebujesz instrukcji przerwania w swojej pętli, usiadłbym i zastanowił się, dlaczego ten poziom złożoności jest potrzebny.
Biff MaGriff

1
Ha ha. Weź pod uwagę fakt, że większość zniechęcających ludzi breaktwierdzi, że jest to dla czytelności kodu. Teraz spójrz ponownie na szalony stan długości 5 mil w pętlach powyżej ...
Tomáš Zato - Przywróć Monikę
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.