Czy „złam” i „kontynuuj” złe praktyki programowania?

191

Mój szef utrzymuje wspomnieć nonszalancko, że złe programiści używać breaki continuew pętli.

Używam ich cały czas, ponieważ mają sens; pozwól, że pokażę ci inspirację:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

Chodzi o to, że najpierw funkcja sprawdza, czy warunki są poprawne, a następnie wykonuje rzeczywistą funkcjonalność. IMO to samo dotyczy pętli:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Myślę, że to ułatwia czytanie i debugowanie; ale nie widzę też wad.

Michaił
źródło
2
Nie trzeba wiele zapominać, który co robi.
57
Nie. Nie ma też problemu. Kluczem jest wiedza, kiedy ich używać. Są narzędziami w przyborniku. Korzystasz z nich, gdy zawierają przejrzysty i zwięzły kod.
orj
67
Nie mogę wystarczająco mocno wyrazić swojego poparcia dla tego stylu kodowania. Wiele poziomów zagnieżdżonych warunków jest o wiele gorszych niż to podejście. Zwykle nie jestem wojowniczką w stylu kodowania, ale jest to dla mnie prawie przełom.
Emil H
9
Oczywiście twój szef nie pisze (wystarczająco) kodu. Gdyby to zrobił, wiedziałby, że wszystkie słowa kluczowe (tak, nawet goto) są przydatne w niektórych przypadkach.
sakisk
57
Źli programiści używają przerwania i kontynuacji nie oznacza, że ​​dobrzy programiści nie. Źli programiści używają, jeśli i jednocześnie.
mouviciel

Odpowiedzi:

240

Kiedy są używane na początku bloku, gdy wykonywane są pierwsze kontrole, działają jak warunki wstępne, więc dobrze.

Kiedy są używane w środku bloku, z pewnym kodem, działają jak ukryte pułapki, więc jest źle.

Klaim
źródło
6
@Klaim: Można argumentować, że jakakolwiek procedura, która ma kilka punktów wyjścia, jest rutynowo niefakturowaną. Właściwie rutynowa rutyna powinna robić tylko jedną rzecz.
bit-twiddler 16.03.11
79
@ bit-twiddler: jest to sposób myślenia w stylu C-ish. Zmienna tymczasowa może być później zmodyfikowana, więc pojedyncza literówka 20 linii stąd może skasować ten starannie spreparowany wynik. Natychmiastowy powrót (lub przerwa lub kontynuacja) jest jednak niezwykle jasny: mogę teraz przestać czytać, ponieważ wiem, że nie można go dalej modyfikować . To jest dobre dla mojego małego mózgu, naprawdę ułatwia przeszukiwanie kodu.
Matthieu M.
15
@Matthieu Zgadzam się. Wyjdź z bloku, gdy otrzymasz wynik spełniający cel bloku.
Evan Plaice,
8
@ bit-twiddler - zasada pojedynczego punktu wyjścia jest odrębna od zasady pojedynczej odpowiedzialności. Nie zgadzam się z tym, że sprawdzanie jest zawsze oddzielne od działania WRT jako jedynej odpowiedzialności. W rzeczywistości „pojedyncza odpowiedzialność” zawsze wydaje mi się subiektywnym terminem. Na przykład, w rozwiązaniu kwadratowym, czy obliczanie dyskryminatora powinno być osobną odpowiedzialnością? A może cała formuła kwadratowa może być pojedynczą odpowiedzialnością? Twierdzę, że zależy to od tego, czy masz odrębne zastosowanie dla dyskryminującego - w przeciwnym razie traktowanie go jako oddzielnej odpowiedzialności jest prawdopodobnie nadmierne.
Steve314,
10
Ta odpowiedź jest ogólną zasadą, a nie twardą zasadą. Działa w większości przypadków, możesz go złamać, jeśli ma to sens w twoim kontekście.
Klaim
87

Możesz przeczytać artykuł Donalda Knutha Programowanie strukturalne z 1974 r., Przejdź do Oświadczeń , w których omawia różne zastosowania tych, go toktóre są strukturalnie pożądane. Obejmują one odpowiednik instrukcji breaki continue(wiele z tamtejszych zastosowań go tozostało opracowanych w bardziej ograniczone konstrukcje). Czy twój szef nazywa Knutha złym programistą?

(Podane przykłady mnie interesują. Zazwyczaj breaki continuenie podobają im się osoby, które lubią jedno wejście i jedno wyjście z dowolnego fragmentu kodu, a taka osoba również marszczy brwi w związku z wieloma returnstwierdzeniami.)

David Thornley
źródło
8
Większość osób, które lubią funkcje i procedury mające pojedyncze punkty wejścia i wyjścia, wychowała się na Pascalu. Pascal nie był pierwszym językiem, którego się nauczyłem, ale miał ogromny wpływ na sposób, w jaki tworzę kod do dziś. Ludzie zawsze komentują, jak łatwo odczytać mój kod Java. To dlatego, że unikam wielu punktów wyjścia, a także mieszania deklaracji z kodem. Dokładam wszelkich starań, aby zadeklarować każdą zmienną lokalną używaną w metodzie na początku metody. Ta praktyka unika wędrowania kodu, zmuszając mnie do zwięzłości metod.
bit-twiddler 15.03.11
5
Pascal miał także funkcje zagnieżdżone. Tylko
mówię
6
Z tego co pamiętam, głównym powodem, dla którego ludzie nie lubili wielu instrukcji return w funkcjach, było to, że debuggery nie obsługiwały ich poprawnie. Ustalenie punktu przerwania na końcu funkcji było prawdziwym bólem, ale nigdy nie trafiłeś go z powodu wcześniejszej instrukcji return. Dla każdego kompilatora, którego obecnie używam, nie stanowi to już problemu.
Dunk
28
@ bit-twiddler: Nie jestem tego taki pewien. Nadal używam Pascala i ogólnie uważam, że „pojedyncze wejście jedno wyjście” lub przynajmniej jego część jedno wyjście, to program kultowego ładunku. Właśnie rozważenia Break, Continuea Exitjako narzędzia w moim przybornika; Używam ich tam, gdzie ułatwia to podążanie za kodem, i nie używam ich tam, gdzie byłoby to trudniejsze do odczytania.
Mason Wheeler,
2
@ bit-twiddler: amen to. Dodam też, że gdy przejdziesz do bloków, które łatwo mieszczą się na ekranie, wiele punktów wyjścia staje się o wiele mniej kłopotliwych.
Shog9
44

Nie wierzę, że są źli. Pomysł, że są źli, pochodzi z dni programowania strukturalnego. Jest to związane z pojęciem, że funkcja musi mieć jeden punkt wejścia i jeden punkt wyjścia, tj. Tylko jeden returnna funkcję.

Ma to sens, jeśli twoja funkcja jest długa i jeśli masz wiele zagnieżdżonych pętli. Jednak twoje funkcje powinny być krótkie i powinieneś zawijać pętle i ich ciała w ich krótkie funkcje. Ogólnie rzecz biorąc, wymuszenie, aby funkcja miała jeden punkt wyjścia, może spowodować bardzo skomplikowaną logikę.

Jeśli twoja funkcja jest bardzo krótka, jeśli masz pojedynczą pętlę lub, w najgorszym wypadku, dwie zagnieżdżone pętle, a jeśli treść pętli jest bardzo krótka, to jest bardzo jasne, co robi a breaklub a continue. Oczywiste jest również, co robi wiele returninstrukcji.

Zagadnienia te zostały omówione w „Czystym kodzie” Roberta C. Martina oraz w „Refaktoryzacji” Martina Fowlera.

Dima
źródło
12
„Zmniejsz swoje funkcje. Następnie zmniejsz je” - Robert C. Martin. Przekonałem się, że działa to zaskakująco dobrze. Za każdym razem, gdy zobaczysz blok kodu w funkcji, która wymaga komentarza wyjaśniającego, co robi, zawiń go w osobną funkcję o opisowej nazwie. Nawet jeśli jest to tylko kilka wierszy i nawet jeśli jest używany tylko raz. Ta praktyka eliminuje większość problemów z przerwaniem / kontynuowaniem lub wielokrotnymi zwrotami.
Dima,
2
@Mikhail: Złożoność cyklomatyczna jest zasadniczo dość silnie skorelowana z SLOC, co oznacza, że ​​można uprościć porady, aby „nie pisać długich funkcji”.
John R. Strohm
3
Idea pojedynczego punktu wyjścia jest powszechnie błędnie interpretowana. Dawno, dawno temu funkcje nie musiały zwracać osoby dzwoniącej. Mogą wrócić do innego miejsca. Zwykle robiono to w asemblerze. Fortran miał do tego specjalną konstrukcję; możesz przekazać numer instrukcji poprzedzony znakiem ampersand CALL P(X, Y, &10), a w przypadku błędu funkcja może przekazać kontrolę do tej instrukcji, zamiast wracać do punktu wywołania.
kevin cline
@kevincline jak na przykład Lua.
Qix
1
@cjsimon masz. Nie tylko funkcje powinny być małe. Klasy też powinny być małe.
Dima
39

Źli programiści mówią absolutnie (tak jak Sith). Dobrzy programiści używają najczystszych możliwych rozwiązań ( wszystkie inne rzeczy są równe ).

Używanie często przerywania i kontynuowania utrudnia przestrzeganie kodu. Ale jeśli ich zastąpienie sprawia, że ​​kod jest jeszcze trudniejszy do naśladowania, to jest to zła zmiana.

Podany przykład jest zdecydowanie sytuacją, w której przerwy i kontynuacje powinny zostać zastąpione czymś bardziej eleganckim.

Matthew Read
źródło
Tak, przesadziłem z liczbą warunków, aby podać przykłady przypadków wyjścia.
Michaił
9
Jaki jest przykład zaproponowanego przez Ciebie kodu zastępczego? Myślałem, że to dość rozsądny przykład wypowiedzi straży.
simgineer
Wydaje mi się, że „najczystsze możliwe rozwiązanie” jest zawsze… możliwe? Jak może nie być „najczystszego” rozwiązania? Ale nie jestem zwolennikiem absolutów, więc może masz rację.
@nocomprende Nie jestem pewien, o co ci chodzi. „Możliwe” tutaj nie oznacza, że ​​najlepsze rozwiązanie nie istnieje - tylko idealna, najwyższa klarowność nie jest rzeczą. W końcu to subiektywne.
Matthew przeczytał
22

Większość ludzi uważa, że ​​to zły pomysł, ponieważ zachowanie nie jest łatwe do przewidzenia. Jeśli czytasz ten kod i widzisz, while(x < 1000){}że zakładasz, że będzie on działał do x> = 1000 ... Ale jeśli są przerwy w środku, to nie jest to prawdą, więc nie możesz naprawdę ufać swojemu zapętlenie ...

To ten sam powód, dla którego ludzie nie lubią GOTO: pewnie, można go dobrze używać, ale może także prowadzić do godawful kodu spaghetti, w którym kod przeskakuje losowo z sekcji do sekcji.

Dla mnie, gdybym miał zrobić pętlę, która przerwała więcej niż jeden warunek, zrobiłbym while(x){}wtedy X przełączając na false, gdy musiałem się wyrwać. Ostateczny wynik byłby taki sam, a każdy czytający kod wiedziałby, aby przyjrzeć się bliżej rzeczom, które zmieniły wartość X.

Satanicpuppy
źródło
2
+1 bardzo dobrze powiedziane i +1 (gdybym mógł zrobić inną) za while(notDone){ }podejście.
FrustratedWithFormsDesigner
5
Michaił: Problem z przerwą polega na tym, że ostateczny warunek pętli nigdy nie jest po prostu określony w jednym miejscu. Utrudnia to przewidzenie stanu po pętli. W tym trywialnym przypadku (> = 1000) nie jest to trudne. Dodanie wielu instrukcji if i różnych poziomów zagnieżdżania może być bardzo, bardzo trudne do określenia warunku końcowego pętli.
S.Lott,
S.Lott uderzył prosto w paznokieć w głowę. Wyrażenie kontrolujące iterację powinno obejmować wszystkie warunki, które muszą zostać spełnione, aby można było kontynuować iterację.
bit-twiddler 17.03.11
6
Wymiana break;z x=false;nie czyni kod bardziej jasne. Nadal musisz przeszukać ciało pod kątem tego oświadczenia. A w przypadku x=false;musisz sprawdzić, czy nie x=true;spadnie to bardziej.
Sjoerd,
14
Kiedy ludzie mówią: „Widzę x i zakładam y, ale jeśli zrobisz to, to założenie nie ma znaczenia”, myślę „więc nie rób tego głupiego założenia”. Wiele osób uprościłoby to do „kiedy widzę while (x < 1000), zakładam, że będzie działać 1000 razy”. Jest wiele powodów, dla których jest to fałsz, nawet jeśli xpoczątkowo wynosi zero. Na przykład, kto mówi, że xjest zwiększany dokładnie raz podczas pętli i nigdy nie jest modyfikowany w żaden inny sposób? Nawet dla twojego własnego założenia, tylko dlatego, że coś ustawia x >= 1000nie oznacza, że ​​pętla się skończy - może zostać ustawiona z powrotem w zakresie, zanim warunek zostanie sprawdzony.
Steve314,
14

Tak, możesz [ponownie] pisać programy bez instrukcji break (lub wraca ze środka pętli, które robią to samo). Ale może być konieczne wprowadzenie dodatkowych zmiennych i / lub powielanie kodu, które zwykle utrudniają zrozumienie programu. Z tego powodu Pascal (język programowania) był bardzo zły, szczególnie dla początkujących programistów. Twój szef zasadniczo chce, żebyś programował w strukturach kontrolnych Pascala. Gdyby Linus Torvalds był w twoich butach, prawdopodobnie pokazałby szefowi środkowy palec!

Jest wynik informatyki zwany hierarchią struktur kontrolnych Kosaraju, która sięga 1973 roku i jest wspomniana w (bardziej) znanym artykule Knutha na temat gotos z 1974 roku. ). S. Rao Kosaraju udowodnił w 1973 r., Że nie jest możliwe przepisanie wszystkich programów, które mają wielopoziomowe podziały głębokości n na programy o głębokości przerwania mniejszej niż n bez wprowadzenia dodatkowych zmiennych. Powiedzmy jednak, że jest to wynik czysto teoretyczny. (Po prostu dodaj kilka dodatkowych zmiennych ?! Z pewnością możesz to zrobić, aby zadowolić swojego szefa ...)

O wiele ważniejsze z punktu widzenia inżynierii oprogramowania jest najnowszy artykuł Erica S. Robertsa z 1995 r. Zatytułowany Loop Exits and Structured Programming: Reopening the Debate ( http://cs.stanford.edu/people/eroberts/papers/SIGCSE- 1995 / LoopExits.pdf ). Roberts podsumowuje kilka badań empirycznych przeprowadzonych przez innych przed nim. Na przykład, gdy grupa studentów typu CS101 została poproszona o napisanie kodu dla funkcji realizującej wyszukiwanie sekwencyjne w tablicy, autor badania powiedział o tych studentach, którzy użyli break / return / goto do wyjścia z sekwencyjna pętla wyszukiwania, gdy element został znaleziony:

Nie znalazłem jeszcze ani jednej osoby, która spróbowała użyć programu przy użyciu [tego stylu], która stworzyła nieprawidłowe rozwiązanie.

Roberts mówi również, że:

Uczniowie, którzy próbowali rozwiązać problem bez wyraźnego powrotu z pętli for, radzili sobie znacznie gorzej: tylko siedmiu z 42 uczniów próbujących zastosować tę strategię udało się wypracować prawidłowe rozwiązania. Liczba ta stanowi wskaźnik sukcesu mniejszy niż 20%.

Tak, możesz być bardziej doświadczony niż studenci CS101, ale bez użycia instrukcji break (lub równoważnie return / goto ze środka pętli), w końcu napiszesz kod, który przy nominalnej ładnej strukturze jest wystarczająco włochaty pod względem dodatkowej logiki zmienne i powielanie kodu, że ktoś, prawdopodobnie ty, wprowadzi do niego błędy logiczne, starając się podążać za stylem twojego szefa.

Powiem też tutaj, że artykuł Roberta jest o wiele bardziej dostępny dla przeciętnego programisty, więc lepiej najpierw przeczytać niż Knutha. Jest także krótszy i obejmuje węższy temat. Prawdopodobnie mógłbyś nawet polecić to swojemu szefowi, nawet jeśli jest to kierownictwo, a nie typ CS.

użytkownik3588161
źródło
2
Mimo że przez wiele lat stosowałem programowanie strukturalne, w ostatnich kilku przypadkach całkowicie wykorzystałem jawne wyjścia przy pierwszej możliwej okazji. To powoduje szybsze wykonanie i prawie eliminuje błędy logiczne, które kiedyś powodowały niekończące się pętle (zawieszanie się).
DocSalvager,
9

Nie rozważam zastosowania żadnej z tych złych praktyk, ale użycie ich zbyt wiele w tej samej pętli powinno uzasadnić przemyślenie logiki używanej w tej pętli. Używaj ich oszczędnie.

Bernard
źródło
:) tak, podany przeze mnie przykład był konceptualny
Michaił
7

Podany przykład nie wymaga przerw ani kontynuacji:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

Mój „problem” z 4 liniami w twoim przykładzie polega na tym, że wszystkie są na tym samym poziomie, ale robią różne rzeczy: niektóre przerwy, niektóre kontynuują ... Musisz przeczytać każdą linię.

W moim podejściu zagnieżdżonym, im głębiej idziesz, tym bardziej „użyteczny” staje się kod.

Ale jeśli głęboko w środku znajdziesz powód, aby zatrzymać pętlę (inny niż warunek podstawowy), skorzystaj z przerwy lub powrotu. Wolę to niż użycie dodatkowej flagi, która ma zostać przetestowana w stanie najwyższego poziomu. Przerwa / powrót jest bardziej bezpośrednia; lepiej określa intencję niż ustawienie kolejnej zmiennej.

Peter Frings
źródło
+1 Ale tak naprawdę twoje <porównania muszą <=pasować do rozwiązania OP
El Ronnoco
3
W większości przypadków, jeśli ktoś jest tak głęboki, że do zarządzania kontrolą przepływu należy użyć break / return, jego funkcja / metoda jest zbyt złożona.
bit-twiddler
6

„Zło” zależy od tego, jak z nich korzystasz. Zazwyczaj przerwy w konstrukcjach zapętlonych używam TYLKO wtedy, gdy pozwoli mi to zaoszczędzić cykle, których nie można zapisać przez refaktoryzację algorytmu. Na przykład przeglądanie kolekcji w poszukiwaniu elementu o wartości w określonej właściwości ustawionej na true. Jeśli wszystko, co musisz wiedzieć, to że jeden z elementów miał tę właściwość ustawioną na true, po osiągnięciu tego wyniku dobrze jest przerwać, aby odpowiednio zakończyć pętlę.

Jeśli użycie przerwy nie sprawi, że kod będzie łatwiejszy do odczytania, krótszy do uruchomienia lub zapisania cykli w przetwarzaniu w znaczący sposób, najlepiej ich nie używać. Zazwyczaj koduję do „najniższego wspólnego mianownika”, gdy jest to możliwe, aby upewnić się, że każdy, kto mnie śledzi, może z łatwością spojrzeć na mój kod i dowiedzieć się, co się dzieje (nie zawsze mi się to udaje). Przerwy zmniejszają to, ponieważ wprowadzają nieparzyste punkty wejścia / wyjścia. Nadużywane mogą zachowywać się bardzo podobnie jak nieudane oświadczenie „goto”.

Joel Etherton
źródło
Zgadzam się z obydwoma punktami! Jeśli chodzi o drugi punkt - myślę, że łatwiej jest śledzić mój oryginalny post, ponieważ brzmi on jak angielski. Jeśli masz kombinację warunków w instrukcji 1 if, to jest prawie rozszyfrowanie, aby dowiedzieć się, co musi się stać, aby wykonać warunek true.
Michaił
@Mikhail: Dostarczone przez ciebie próbki są trochę altruistyczne dla specyficznego realizmu. Moim zdaniem próbki te są jasne, zwięzłe, łatwiejsze do odczytania. Większość pętli nie jest taka. Większość pętli ma jakąś inną logikę, którą wykonują i potencjalnie znacznie bardziej skomplikowane warunki warunkowe. Jest to w tych przypadkach, w których przerwa / kontynuacja może nie być najlepszym zastosowaniem, ponieważ zaburza logikę podczas odczytu.
Joel Etherton,
4

Absolutnie nie ... Tak, użycie gotojest złe, ponieważ psuje strukturę twojego programu, a także bardzo trudno jest zrozumieć przepływ sterowania.

Ale korzystanie z takich stwierdzeń jest breaki continuejest absolutnie konieczne w dzisiejszych czasach i wcale nie jest uważane za złą praktykę programowania.

A także niezbyt trudne do zrozumienia przepływu kontroli w użyciu breaki continue. W konstrukcjach takich jak switchna breakrachunku jest to absolutnie konieczne.

Radheshyam Nayak
źródło
2
Nie użyłem „kontynuuj”, odkąd po raz pierwszy nauczyłem się języka C w 1981 roku. Jest to niepotrzebna funkcja językowa, ponieważ kod, który jest omijany przez instrukcję kontynuacji, może być zawarty w instrukcji warunkowej kontroli.
bit-twiddler 15.03.11
12
Wolę używać kontynuacji w tych przypadkach, ponieważ zapewnia to, że mój kod nie stanie się kodem strzałki. Nienawidzę kodu strzałkowego bardziej niż instrukcji typu goto. Przeczytałem też: „jeśli to stwierdzenie jest prawdziwe, pomiń resztę tej pętli i przejdź do następnej iteracji”. Bardzo przydatne, gdy znajduje się na samym początku pętli for (mniej przydatne w pętlach while).
jsternberg,
@jsternberg For for the win! :-)
Notinlist
3

Zasadnicze pojęcie wynika z możliwości semantycznej analizy programu. Jeśli masz jeden wpis i jedno wyjście, matematyka potrzebna do oznaczenia możliwych stanów jest znacznie łatwiejsza niż w przypadku zarządzania ścieżkami rozwidlenia.

Po części ta trudność odzwierciedla koncepcyjne uzasadnienie twojego kodu.

Szczerze mówiąc, twój drugi kod nie jest oczywisty. Co to robi? Czy kontynuuje „kontynuuj”, czy „kontynuuje” pętlę? Nie mam pojęcia. Przynajmniej twój pierwszy przykład jest jasny.

Paul Nathan
źródło
Kiedy pracuję w projekcie, który menedżer zobowiązuje się do ich użycia, musiałem użyć schematu blokowego, aby zapamiętać jego użycie, a kilka ścieżek wyjścia sprawiło, że kod był bardziej mylący ...
umlcat
twój „szczery” komentarz jest składniowy - „next” to perl; zwykłe języki używają słowa „kontynuuj”, co oznacza „pomiń tę pętlę”
Michaił
@Mik, może lepiej byłoby pominąć tę iterację. Z poważaniem, Dr. IM Pedantic
Pete Wilson
@Mikhail: Jasne. Ale gdy ktoś pracuje w wielu językach, może to prowadzić do błędów, gdy składnia nie jest wspólna dla różnych języków.
Paul Nathan
Ale matematyka nie jest programowanie. Kod jest bardziej wyrazisty niż matematyka. I dostać że jedno-wejście / wyjście pojedynczej mogą schematy wyglądają ładniej, ale na to, co wydatek (Tj., Gdzie przerwa / zwrot może lepiej wykonywać kod)?
Evan Plaice,
3

Zamieniłbym twój drugi fragment kodu na

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

nie z powodu zwięzłości - tak naprawdę uważam, że jest to łatwiejsze do odczytania i dla kogoś, kto rozumie, co się dzieje. Ogólnie rzecz biorąc, warunki dla twoich pętli powinny być zawarte wyłącznie w tych warunkach pętli, które nie są zaśmiecone w całym ciele. Jednak istnieją pewne sytuacje, w których breaki continuemogą pomóc czytelności. breakwięcej niż continuemógłbym dodać: D

El Ronnoco
źródło
Chociaż nie zgadzam się z tym, że „wydaje mi się, że łatwiej to odczytać”, osiąga dokładnie ten sam cel, co powyższy kod. Do tej pory nigdy nie myślałem o „przerwie” jako o operatorze zwarciowym, ale ma to sens. Jeśli chodzi o: „Ogólnie mówiąc, warunki dla twoich pętli powinny być zawarte wyłącznie w tych warunkach pętli”, co robisz, gdy przetwarzasz pętlę foreach, ponieważ tak naprawdę nie ma sposobu na wstawienie logiki warunkowej do definicji pętli?
Evan Plaice
@Evan Ten warunek nie dotyczy foreachpętli, ponieważ spowoduje to iterację każdego elementu w kolekcji. forPętli jest podobny w tym, że nie powinien mieć warunkowego punkt końcowy. Jeśli potrzebujesz warunkowego punktu końcowego, musisz użyć whilepętli.
El Ronnoco
1
@Evan Widzę jednak twój punkt widzenia - tzn. „Co jeśli chcesz wyrwać się z pętli foreach?” - breakMoim zdaniem powinno być tylko jedno maksimum z pętli.
El Ronnoco
2

Nie zgadzam się z twoim szefem. Są odpowiednie dla miejsc breaki continuema być używany. W rzeczywistości przyczyną wykonania i obsługi wyjątków we współczesnych językach programowania jest to, że nie można rozwiązać każdego problemu za pomocą just structured techniques.

Na marginesie nie chcę tutaj rozpoczynać dyskusji religijnej, ale możesz zmienić strukturę kodu, aby był jeszcze bardziej czytelny:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

Po drugiej stronie notatki

Osobiście nie podoba mi się stosowanie ( flag == true )warunkowe, ponieważ jeśli zmienna jest już wartością logiczną, to wprowadzasz dodatkowe porównanie, które musi się zdarzyć, gdy wartość logiczna ma odpowiedź, której chcesz - chyba że jesteś pewien, że twój kompilator zoptymalizuj to dodatkowe porównanie.

Zeke Hansell
źródło
Wahałem się w tej kwestii od lat. Twój sposób („if (flag) {...}”) jest o wiele bardziej zwięzły i może wygląda bardziej „profesjonalnie” lub „ekspertem”. Ale, wiesz, często oznacza to, że czytelnik / opiekun musi na chwilę przerwać, aby przypomnieć sobie, co oznacza konstrukcja; i dla pewności co do sensu testu. Obecnie używam „if (flag == true) {...}” tylko dlatego, że wydaje się, że jest to lepsza dokumentacja. W następnym miesiącu? Quien Sabe?
Pete Wilson,
2
@Pete - Termin nie jest zwięzły, ale elegancki . Możesz gofrować, ile chcesz, ale jeśli martwisz się, że czytelnik / opiekun nie rozumie, co to booleanjest a co oznacza zwięzła / elegancka terminologia, to może lepiej zatrudnij mądrzejszych opiekunów ;-)
Zeke Hansell
@Pete, stoję też przy moim oświadczeniu o wygenerowanym kodzie. Robisz jeszcze jedno porównanie, porównując flagę ze stałą wartością przed oszacowaniem wartości logicznej wyrażenia. Dlaczego jest trudniej, niż musi być, zmienna flagowa ma już żądaną wartość!
Zeke Hansell
+1 dobra robota. Zdecydowanie o wiele bardziej elegancki niż poprzedni przykład.
Evan Plaice,
2

Zgadzam się z twoim szefem. Są złe, ponieważ wytwarzają metody o dużej złożoności cyklomatycznej. Takie metody są trudne do odczytania i trudne do przetestowania. Na szczęście istnieje łatwe rozwiązanie. Wyodrębnij treść pętli do osobnej metody, w której „kontynuacja” staje się „powrotem”. „Powrót” jest lepszy, ponieważ po „powrocie” to koniec - nie ma obaw o stan lokalny.

Aby „przerwać”, wypakuj samą pętlę do osobnej metody, zastępując „przerwanie” słowem „powrót”.

Jeśli wyodrębnione metody wymagają dużej liczby argumentów, jest to wskazanie do wyodrębnienia klasy - albo zbierz je do obiektu kontekstu.

Kevin Cline
źródło
0

Myślę, że to problem tylko wtedy, gdy jest głęboko osadzony w wielu pętlach. Trudno wiedzieć, do której pętli się zrywasz. Kontynuacja może być również trudna, ale myślę, że prawdziwy ból pochodzi z przerw - logika może być trudna do naśladowania.

davidhaskins
źródło
2
W rzeczywistości łatwo jest sprawdzić, czy prawidłowo wcinasz, czy semantyka tych instrukcji jest zinternalizowana i czy nie są zbyt senne.
@delnan - to wiele założeń;)
davidhaskins 15.03.11
2
Tak, szczególnie ostatni.
Michael K
No cóż, # 1 jest konieczny do poważnego programowania, # 2 należy się spodziewać od wszystkich zwanych programistami, a # 3 jest ogólnie całkiem przydatne;)
Niektóre języki (tylko skrypty, które znam) obsługują break 2; dla innych wydaje się, że używane są tymczasowe flagi bool
Michaił
0

O ile nie są używane jako ukryte goto, jak w poniższym przykładzie:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Nic mi nie jest. (Przykład widoczny w kodzie produkcyjnym, meh)

SuperBloup
źródło
Czy poleciłbyś takim przypadkom tworzenie funkcji?
Michaił
Tak, a jeśli nie jest to możliwe, zwykłe goto. Kiedy widzę „do”, myślę, że powtórzenie, a nie kontekst symulujący skok.
SuperBloup,
och, zgadzam się, tylko pytałem, jak byś to zrobił :) Informatycy robią to wielokrotnie
Michaił
0

Nie lubię żadnego z tych stylów. Oto co wolałbym:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Naprawdę nie lubię używać returndo przerywania funkcji. To jest jak nadużyciereturn .

Za pomocą break również nie zawsze jest czytelne.

Jeszcze lepiej może być:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

mniej zagnieżdżania, a złożone warunki są przekształcane w zmienne (w prawdziwym programie trzeba by mieć lepsze nazwy, oczywiście ...)

(Cały powyższy kod jest pseudokodem)

FrustratedWithFormsDesigner
źródło
11
Naprawdę wolisz 3 poziomy zagnieżdżenia niż to, co wpisałem powyżej?
Michaił
@Mikhail: Tak, lub przypisałbym wynik warunku zmiennej. Uważam, że jest to dużo łatwiejsze do zrozumienia niż logika breaki continue. Nienormalne zakończenie pętli jest po prostu dziwne i nie podoba mi się to.
FrustratedWithFormsDesigner
1
Jak na ironię, źle odczytałeś moje warunki. continueoznacza pominięcie funkcji przejścia do następnej pętli; nie „kontynuuj egzekucję”
Michaił
@Mikhail: Ah. Nie używam go często, a kiedy go czytam, mylę go znaczenie. Kolejny powód, że mi się nie podoba. : P Daj mi minutę na aktualizację ...
FrustratedWithFormsDesigner
7
Zbyt duże zagnieżdżanie niszczy czytelność. A czasem unikanie przerwy / kontynuacji wprowadza konieczność odwrócenia logiki na testach warunkowych, co może prowadzić do błędnej interpretacji tego, co robi Twój kod - mówię tylko
Zeke Hansell
0

Nie. To sposób na rozwiązanie problemu i istnieją inne sposoby jego rozwiązania.

Wiele obecnych języków głównego nurtu (Java, .NET (C # + VB), PHP, pisz własne) używa „break” i „kontynuuj”, aby pominąć pętle. Oba zdania „ustrukturyzowane goto (s)”.

Bez nich:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

Z nimi:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Zauważ, że kod „przerwa” i „kontynuuj” jest krótszy i zwykle zamienia „a” w „zdania” na „for” lub „foreach”.

Oba przypadki są kwestią stylu kodowania. Wolę ich nie używać , ponieważ pełny styl pozwala mi mieć większą kontrolę nad kodem.

Właściwie pracuję w niektórych projektach, w których użycie tych zdań było obowiązkowe.

Niektórzy programiści mogą myśleć, że nie są to konieczne, ale hipotetyczne, gdybyśmy musieli je usunąć, musielibyśmy usunąć „while” i „do while” („powtarzaj do”, chłopaki pascal) ;-)

Wniosek, nawet jeśli wolę ich nie używać, uważam, że jest to opcja, a nie zła praktyka programistyczna.

umlcat
źródło
Przykro mi, że jestem wybredny, ale w drugim przykładzie brakuje danych wyjściowych, gdy klucz nie został znaleziony (więc oczywiście wygląda na znacznie krótszy).
FrustratedWithFormsDesigner
2
nie wspominając, że pierwszy przykład działa tylko wtedy, gdy klucz jest ostatnim na liście.
Michaił
@FrustratedWithFormsDesigner DOKŁADNIE. Włączyłem cel, aby wskazać, dlaczego ta metoda jest lepsza ;-)
umlcat
jednak masz dwie procedury o różnej semantyce; dlatego nie są one logicznie równoważne.
bit-twiddler 17.03.11
2
twój drugi przykład zawiera dwa błędy, jeden składniowy i jeden logiczny. 1. Nie będzie się kompilować, ponieważ iterator nie jest zadeklarowany poza zakresem pętli for (dlatego nie jest dostępny w wynikowym ciągu). 2. Nawet jeśli iterator został zadeklarowany poza pętlą, jeśli klucz nie zostanie znaleziony w kolekcji, dane wyjściowe ciągu wydrukują klucz ostatniego elementu na liście.
Evan Plaice,
0

Nie jestem przeciwko continueibreak zasady, ale myślę, że są one bardzo niskiego poziomu konstruktów, które bardzo często mogą być zastąpione przez coś jeszcze lepszego.

Używam C # jako przykładu, rozważmy przypadek iteracji nad kolekcją, ale chcemy tylko elementów spełniających pewne predykaty i nie chcemy robić więcej niż maksymalnie 100 iteracji.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

Wygląda to POWAŻNIE czysto. Nie jest to trudne do zrozumienia. Sądzę jednak, że wiele zyskałoby na byciu bardziej deklaratywnym. Porównaj to z następującymi:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Może połączenia Gdzie i Odbierz nie powinny nawet być w tej metodzie. Może to filtrowanie należy wykonać PRZED przekazaniem kolekcji do tej metody. W każdym razie, odchodząc od niskiego poziomu i skupiając się bardziej na rzeczywistej logice biznesowej, staje się bardziej jasne, czym faktycznie jesteśmy zainteresowani. Łatwiej jest podzielić nasz kod na spójne moduły, które bardziej odpowiadają dobrym praktykom projektowym i tak dalej na.

Niski poziom nadal będzie istniał w niektórych częściach kodu, ale chcemy to ukryć tak bardzo, jak to możliwe, ponieważ wymaga to energii mentalnej, którą moglibyśmy wykorzystać do uzasadnienia problemów biznesowych.

kai
źródło
-1

Code Complete ma fajną sekcję na temat używaniagoto i wielokrotnych zwrotów z procedury lub pętli.

Ogólnie rzecz biorąc, nie jest to zła praktyka. breaklub continuepowiedz dokładnie, co będzie dalej. I zgadzam się z tym.

Steve McConnell (autor Code Complete) używa prawie takich samych przykładów, jak ty, aby pokazać zalety używania różnych gotoinstrukcji.

Jednak nadużywanie breaklub continuemoże prowadzić do złożonego i niemożliwego do utrzymania oprogramowania.

Grzegorz Gierlik
źródło