Rozważ następujący kod:
public void doSomething(int input)
{
while(true)
{
TransformInSomeWay(input);
if(ProcessingComplete(input))
break;
DoSomethingElseTo(input);
}
}
Załóżmy, że proces ten obejmuje skończoną, ale zależną od danych wejściowych liczbę kroków; pętla została zaprojektowana tak, aby zakończyć się sama w wyniku działania algorytmu i nie jest zaprojektowana do działania w nieskończoność (dopóki nie zostanie anulowana przez zdarzenie zewnętrzne). Ponieważ test sprawdzający, czy pętla powinna się zakończyć, znajduje się w środku logicznego zestawu kroków, sama pętla while obecnie nie sprawdza niczego znaczącego; zamiast tego sprawdzanie odbywa się w „właściwym” miejscu algorytmu koncepcyjnego.
Powiedziano mi, że jest to zły kod, ponieważ jest bardziej podatny na błędy ze względu na to, że warunek końcowy nie jest sprawdzany przez strukturę pętli. Trudniej jest wymyślić, jak wyjść z pętli, i może zapraszać błędy, ponieważ warunek przerwania może zostać ominięty lub pominięty przypadkowo, biorąc pod uwagę przyszłe zmiany.
Teraz kod może mieć następującą strukturę:
public void doSomething(int input)
{
TransformInSomeWay(input);
while(!ProcessingComplete(input))
{
DoSomethingElseTo(input);
TransformInSomeWay(input);
}
}
Powoduje to jednak duplikowanie wywołania metody w kodzie, co narusza DRY; gdyby TransformInSomeWay
zostały później zastąpione inną metodą, oba wywołania musiałyby zostać znalezione i zmienione (a fakt, że są dwa, może być mniej oczywisty w bardziej złożonym fragmencie kodu).
Możesz również napisać to tak:
public void doSomething(int input)
{
var complete = false;
while(!complete)
{
TransformInSomeWay(input);
complete = ProcessingComplete(input);
if(!complete)
{
DoSomethingElseTo(input);
}
}
}
... ale masz teraz zmienną, której jedynym celem jest przeniesienie sprawdzania warunków do struktury pętli, a także musi być sprawdzana wiele razy, aby zapewnić takie samo zachowanie jak oryginalna logika.
Ze swojej strony mówię, że biorąc pod uwagę algorytm, jaki ten kod implementuje w świecie rzeczywistym, oryginalny kod jest najbardziej czytelny. Gdybyś sam to przeszedł, tak byś o tym pomyślał, dlatego byłoby to intuicyjne dla osób zaznajomionych z algorytmem.
Więc co jest „lepsze”? czy lepiej jest powierzyć odpowiedzialność za sprawdzanie stanu pętli while, konstruując logikę wokół pętli? Czy może lepiej jest ustrukturyzować logikę w „naturalny” sposób, na co wskazują wymagania lub opis koncepcyjny algorytmu, nawet jeśli może to oznaczać ominięcie wbudowanych możliwości pętli?
do ... until
Konstrukt jest również przydatna dla tych rodzajów pętli, ponieważ nie muszą być „zalane”.Odpowiedzi:
Trzymaj się pierwszego rozwiązania. Pętle mogą być bardzo zaangażowane, a jedna lub więcej czystych przerw może być dla ciebie o wiele łatwiejsze, każdy inny, kto patrzy na twój kod, optymalizator i wydajność programu, niż opracowywać instrukcje if i dodawać zmienne. Moje zwykłe podejście polega na skonfigurowaniu pętli z czymś w rodzaju „while (true)” i uzyskaniu najlepszego kodowania, jakie mogę. Często mogę, a następnie zastępuję przerwę (s) standardową kontrolą pętli; w końcu większość pętli jest dość prosta. Warunek końcowy powinien zostać sprawdzony przez strukturę pętli z podanych powodów, ale nie kosztem dodawania całych nowych zmiennych, dodawania instrukcji if i powtarzania kodu, z których wszystkie są jeszcze bardziej podatne na błędy.
źródło
Jest to znaczący problem, jeśli ciało pętli nie jest trywialne. Jeśli ciało jest tak małe, wówczas analizowanie go w ten sposób jest trywialne i wcale nie stanowi problemu.
źródło
Mam problem ze znalezieniem innych rozwiązań lepszych niż rozwiązanie z przerwą. Cóż, wolę sposób Ada, który pozwala na to
ale rozwiązanie break jest najczystszym renderingiem.
Moim POV jest to, że gdy brakuje struktury kontrolnej, najczystszym rozwiązaniem jest emulacja jej za pomocą innych struktur kontrolnych, nie wykorzystujących przepływu danych. (I podobnie, gdy mam problem z przepływem danych, wolę rozwiązania o czystym przepływie danych niż te obejmujące struktury kontrolne *).
Nie pomyl się, dyskusja nie odbyłaby się, gdyby trudność nie była w większości taka sama: warunek jest taki sam i występuje w tym samym miejscu.
Które z
i
lepiej renderować zamierzoną strukturę? Głosuję za pierwszym, nie musisz sprawdzać, czy warunki się zgadzają. (Ale zobacz moje wcięcie).
źródło
while (true) i break to powszechny idiom. To kanoniczny sposób implementacji pętli mid-exit w językach podobnych do C. Dodanie zmiennej do przechowywania warunku wyjścia i if () wokół drugiej połowy pętli jest rozsądną alternatywą. Niektóre sklepy mogą oficjalnie ustandaryzować się w jednym lub drugim, ale żaden z nich nie jest lepszy; są równoważne.
Dobry programista pracujący w tych językach powinien na pierwszy rzut oka wiedzieć, co się dzieje. To może być dobre pytanie do rozmowy kwalifikacyjnej; podaj komuś dwie pętle, z których każda używa każdego idiomu, i zapytaj ich, jaka jest różnica (poprawna odpowiedź: „nic”, może z dodatkiem „ale zwykle tego używam”).
źródło
Twoje alternatywy nie są tak złe, jak myślisz. Dobry kod powinien:
Wyraźnie wskaż za pomocą dobrych nazw / komentarzy zmiennych, w jakich warunkach pętla powinna zostać zakończona. (Warunki zerwania nie powinny być ukryte)
Wyraźnie wskaż kolejność wykonywania operacji. W tym miejscu
DoSomethingElseTo(input)
dobrym pomysłem jest umieszczenie opcjonalnej trzeciej operacji ( ) wewnątrz if.To powiedziawszy, łatwo jest pozwolić, aby perfekcjonistyczne, mosiężne instynkty pochłaniały dużo czasu. Po prostu poprawiłbym, jeśli, jak wspomniano powyżej; skomentuj kod i przejdź dalej.
źródło
Ludzie twierdzą, że jest to zła praktyka
while (true)
i często mają rację. Jeżeli twójwhile
instrukcja zawiera dużo skomplikowanego kodu i nie wiadomo, kiedy się z niego wyrwiesz, wtedy masz trudny do utrzymania kod, a prosty błąd może spowodować nieskończoną pętlę.W twoim przykładzie możesz używać poprawnie,
while(true)
ponieważ kod jest przejrzysty i łatwy do zrozumienia. Nie ma sensu tworzyć nieefektywnego i trudniejszego do odczytania kodu, po prostu dlatego, że niektórzy uważają, że zawsze jest to zła praktykawhile(true)
.Miałem podobny problem:
Użycie
do ... while(true)
pozwala uniknąćisset($array][$key]
niepotrzebnego podwójnego wywoływania, a kod jest krótszy, czystszy i łatwiejszy do odczytania. Nie ma absolutnie żadnego ryzyka, żeelse break;
na końcu zostanie wprowadzony błąd nieskończonej pętli .Dobrze jest przestrzegać dobrych praktyk i unikać złych praktyk, ale zawsze są wyjątki i ważne jest, aby zachować otwarty umysł i zdawać sobie sprawę z tych wyjątków.
źródło
Jeśli określony przepływ sterowania jest naturalnie wyrażony jako instrukcja break, zasadniczo nigdy nie jest lepszym wyborem, aby użyć innych mechanizmów kontroli przepływu do emulacji instrukcji break.
(zauważ, że obejmuje to częściowe rozwinięcie pętli i zsunięcie korpusu pętli w dół, aby pętla zaczęła się pokrywać z testem warunkowym)
Jeśli możesz zreorganizować swoją pętlę, aby przepływ kontroli był naturalnie wyrażony poprzez sprawdzenie warunkowe na początku pętli, świetnie. Warto nawet poświęcić trochę czasu na zastanowienie się, czy to możliwe. Ale nie, nie próbuj wpasowywać kwadratowego kołka w okrągły otwór.
źródło
Możesz również iść z tym:
Lub mniej myląco:
źródło
Kiedy złożoność logiki staje się niewygodna, wówczas użycie kontroli nieograniczonej pętli z przerwami w środkowej pętli do kontroli przepływu ma naprawdę sens. Mam projekt z kodem, który wygląda następująco. (Zwróć uwagę na wyjątek na końcu pętli).
Aby wykonać tę logikę bez nieograniczonego przerywania pętli, musiałbym uzyskać coś takiego:
Tak więc wyjątek jest teraz zgłaszany metodą pomocniczą. Ma też całkiem sporo
if ... else
zagnieżdżania. Nie jestem zadowolony z tego podejścia. Dlatego uważam, że pierwszy wzór jest najlepszy.źródło