Wpatruję się w jakiś stary kod z 2001 roku i natknąłem się na to stwierdzenie:
else {
do {
int c = XMLDocumentFragmentScannerImpl.this.scanContent();
if (c == 60) {
XMLDocumentFragmentScannerImpl.this.fEntityScanner.scanChar();
XMLDocumentFragmentScannerImpl.this.setScannerState(1);
break label913;
}
Nigdy wcześniej tego nie widziałem i odkryłem tutaj oznaczone przerwy:
http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Czy to zasadniczo nie działa goto
? Czy to w ogóle dobra praktyka? To mnie niepokoi.
label913
zdefiniowane?Odpowiedzi:
Nie, to nie jest goto, ponieważ nie można „przejść” do innej części przepływu sterowania.
Ze strony, do której jesteś podłączony:
Oznacza to, że możesz przerywać tylko pętle, które są aktualnie wykonywane.
Rozważmy ten przykład:
first: for( int i = 0; i < 10; i++) { second: for(int j = 0; j < 5; j ++ ) { break xxx; } } third: for( int a = 0; a < 10; a++) { }
Można wymienić
xxx
zfirst
lubsecond
(złamać zewnętrznej lub wewnętrznej pętli), ponieważ obie pętle są wykonywane, gdy trafisz nabreak
oświadczenie, ale zastępującxxx
zethird
nie będzie skompilować.źródło
break first;
, czy kompilator wróciłby do linii natychmiast pofirst:
i ponownie wykonał te pętle?break first;
by złamać (koniec) pętla oznaczonyfirst
, tj byłoby kontynuowaćthird
. Z drugiej stronycontinue first;
zakończy bieżącą iteracjęfirst
i zerwiesecond
z nią i będzie kontynuował z następną wartościąi
infirst
.goto
mniej więcej w tym samym sensie, coif-else
stwierdzenie. Goto są znacznie bardziej „elastyczne” jak w „idź gdziekolwiek”, np. Do pozycji przed pętlą (w rzeczywistości tak starsze języki używały pętli: „jeśli warunek nie zostanie spełniony, przejdź do początku treści pętli (ponownie)”) . Miałem z nimi ostatnio do czynienia i uwierz mi:goto
to prawdziwy wrzód na tyłku, podczas gdy oznaczone jako przerwy nie.goto
w ogóle, nawet jeśli C ++ to obsługuje. Prawie zawsze jest lepszy sposób na rozwiązanie problemów, które później oszczędzą Ci wielu bólów głowy. Może nadejść pokusa ... oprzyj się!Nie jest tak straszne, jak
goto
tylko dlatego, że wysyła sterowanie tylko na koniec oznaczonej instrukcji (zwykle jest to konstrukcja pętli). Rzeczą, która sprawiagoto
, że nie da się lubić, jest to, że jest to dowolna gałąź do dowolnego miejsca, w tym etykiety znalezione wyżej w źródle metody, dzięki czemu można uzyskać zachowanie zapętlone w domu. Funkcja dzielenia etykiet w Javie nie pozwala na tego rodzaju szaleństwa, ponieważ kontrola idzie tylko do przodu.Użyłem go tylko raz, około 12 lat temu, w sytuacji, w której musiałem wyrwać się z zagnieżdżonych pętli, bardziej zorganizowana alternatywa mogłaby zrobić bardziej skomplikowane testy w pętlach. Nie radziłbym go często używać, ale nie oznaczałbym go jako automatyczny zapach złego kodu.
źródło
Zawsze można zastąpić
break
nową metodą.Rozważmy kod sprawdzający wszelkie wspólne elementy na dwóch listach:
List list1, list2; boolean foundCommonElement = false; for (Object el : list1) { if (list2.contains(el)) { foundCommonElement = true; break; } }
Możesz to przepisać w ten sposób:
boolean haveCommonElement(List list1, List list2) { for (Object el : list1) { if (list2.contains(el)) { return true; } } return false; }
Oczywiście, aby sprawdzić wspólne elementy między dwiema listami, lepiej jest użyć
list1.retainAll(new HashSet<>(list2))
metody, aby to zrobićO(n)
zO(n)
dodatkową pamięcią, lub posortować obie listy w,O(n * log n)
a następnie znaleźć wspólne elementy wO(n)
.źródło
goto
jest (potencjalnie) szkodliwy, ponieważ może wysłać tak wiele miejsc - wcale nie jest oczywiste, co zostało osiągnięte; tworzenie funkcji pomocniczej daje następnemu facetowi jasne granice co do tego, co próbujesz zrobić i gdzie zamierzasz to zrobić, a także daje szansę na nazwanie tego elementu funkcjonalności. W ten sposób wyraźniej.break
, a niegoto
. Jasne, pokazana tutaj refaktoryzacja jest dobrym pomysłem w obu przypadkach. Ale etykietowaniebreak
nie jest tak niemoderowane i podatne na spaghetti, jakgoto
jest.Przed przeczytaniem pozostałej części tej odpowiedzi przeczytaj Przejdź do oświadczenia uważanego za szkodliwy . Jeśli nie chcesz czytać go w całości, oto, co uważam za kluczowy punkt:
Inaczej mówiąc, problem
goto
polega na tym, że program może pojawić się w środku bloku kodu, bez zrozumienia przez programistę stanu programu w tym miejscu. Standardowe konstrukcje zorientowane na bloki są zaprojektowane tak, aby jasno określać przejścia stanów, etykietabreak
ma na celu przeniesienie programu do określonego znanego stanu (poza zawierającym oznaczony blok).W programie imperatywnym w świecie rzeczywistym stan nie jest wyraźnie wyznaczony granicami bloków, więc wątpliwe jest, czy oznaczenie
break
jest dobrym pomysłem. Jeśli blok zmienia stan, który jest widoczny z zewnątrz bloku i istnieje wiele punktów wyjścia z bloku, to oznaczeniebreak
jest równoważne prymitywugoto
. Jedyna różnica polega na tym, że zamiast szansy wylądowania w środku bloku ze stanem nieokreślonym, zaczynasz nowy blok ze stanem nieokreślonym.Tak więc ogólnie uważałbym etykietę
break
za niebezpieczną. Moim zdaniem to znak, że blok należy przekształcić w funkcję, z ograniczonym dostępem do otaczającego zakresu.Jednak ten przykładowy kod był wyraźnie produktem generatora parserów (OP skomentował, że był to kod źródłowy Xerces). Generatory parserów (lub generatory kodu w ogóle) często zachowują swobodę w kwestii generowanego kodu, ponieważ mają doskonałą wiedzę o stanie, a ludzie nie muszą go rozumieć.
źródło
break
jest bardzo przydatne w kilku przypadkach. Na przykład, gdy szukasz jakiejś wartości, która może pochodzić z kilku zamówionych źródeł. Próbujesz jeden po drugim, a kiedy zostanie znaleziony pierwszy, tybreak
. Jest to bardziej elegancki kod niżif / else if / else if / else if
. (Przynajmniej jest mniej linii). Przeniesienie wszystkiego do kontekstu metody może nie zawsze być praktyczne. Innym przypadkiem jest to, że warunkowo łamiesz kilka zagnieżdżonych poziomów. Krótko mówiąc, jeśli nie lubiszbreak
, nie lubiszreturn
nigdzie indziej, z wyjątkiem końca metody.do
iwhile
nie istniały, aif(.. ) goto
zamiast tego wszyscy używali wszędzie. Jego celem było zachęcenie projektantów języków do dodania obsługi takich rzeczy jakdo
iwhile
. Niestety, gdy modą zaczęli się kręcić, przerodziło się w hype pociąg napędzany przez ignorantów, umieszczających rzeczy w rodzajubreak
pętli „wykonywanych tylko raz” i rzucających wyjątki nie wiadomo co, we wszystkich (rzadkich) przypadkach, któregoto
są czystsze i mniej szkodliwe.To nie jest jak
goto
instrukcja, w której przeskakujesz sterowanie przepływem wstecz. Etykieta po prostu pokazuje Tobie (programiście), gdzie nastąpiła przerwa. Ponadto sterowanie przepływem jest przenoszone do następnej instrukcji pobreak
.Jeśli chodzi o używanie go, osobiście uważam, że nie ma to większego pożytku, ponieważ gdy zaczniesz pisać kod wart tysiące wierszy, staje się on nieistotny. Ale znowu zależałoby to od przypadku użycia.
źródło
for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }
.Czystszym sposobem wyrażenia zamiaru mogłoby być, przynajmniej moim zdaniem, umieszczenie fragmentu kodu zawierającego pętlę w oddzielnej metodzie i po prostu
return
z niej.Na przykład zmień to:
someLabel: for (int j = 0; j < 5; j++) { // do something if ... break someLabel; }
zaangażowany w to:
private void Foo() { for (int j = 0; j < 5; j++) { // do something if ... return; } }
Jest to również bardziej idiomatyczne dla programistów biegłych w innych językach, które mogą współpracować z Twoim kodem w przyszłości (lub w przyszłości ).
źródło