Aby uniknąć wszystkich standardowych odpowiedzi, na które mógłbym Googled, podam przykład, który wszyscy możecie atakować do woli.
C # i Java (i zbyt wiele innych) mają wiele typów zachowań polegających na przepełnieniu, których w ogóle nie lubię (np. type.MaxValue + type.SmallestValue == type.MinValue
Na przykład int.MaxValue + 1 == int.MinValue
:).
Ale, widząc moją okrutną naturę, dodam zniewagę do tej kontuzji, rozszerzając to zachowanie na, powiedzmy, DateTime
typ Overridden . (Wiem, że DateTime
jest zamknięty w .NET, ale na potrzeby tego przykładu używam pseudo-języka, który jest dokładnie taki jak C #, z wyjątkiem faktu, że DateTime nie jest zamknięty).
Przesłonięta Add
metoda:
/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan.
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and
/// continue from DateTime.MinValue.
/// </returns>
public DateTime override Add(TimeSpan ts)
{
try
{
return base.Add(ts);
}
catch (ArgumentOutOfRangeException nb)
{
// calculate how much the MaxValue is exceeded
// regular program flow
TimeSpan saldo = ts - (base.MaxValue - this);
return DateTime.MinValue.Add(saldo)
}
catch(Exception anyOther)
{
// 'real' exception handling.
}
}
Oczywiście i może rozwiązać to równie łatwo, ale faktem jest, że po prostu nie rozumiem, dlaczego nie można użyć wyjątków (logicznie to znaczy, widzę, że gdy wydajność jest problemem, w niektórych przypadkach należy unikać wyjątków ).
Myślę, że w wielu przypadkach są one bardziej przejrzyste niż struktury if i nie łamią żadnych umów zawartych przez metodę.
IMHO reakcja „Nigdy nie używaj ich do regularnego przebiegu programu” wydaje się, że wszyscy nie są tak słabo rozwinięci, jak siła tej reakcji może to uzasadnić.
Czy się mylę?
Czytałem inne posty dotyczące wszelkiego rodzaju specjalnych przypadków, ale moim zdaniem nie ma w tym nic złego, jeśli oboje:
- Jasny
- Honoruj kontrakt swojej metody
Zastrzel mnie.
źródło
if
oświadczenie. Znajdziesz to bardzo trudne. Innymi słowy: twoje założenie jest błędne, a wnioski, które z niego wyciągasz, są błędne.Odpowiedzi:
Czy kiedykolwiek próbowałeś debugować program generujący pięć wyjątków na sekundę w normalnym trybie działania?
Mam.
Program był dość złożony (był to rozproszony serwer obliczeniowy), a niewielka modyfikacja po jednej stronie programu mogła łatwo zepsuć coś w zupełnie innym miejscu.
Chciałbym móc uruchomić program i czekać na wystąpienie wyjątków, ale było około 200 wyjątków podczas uruchamiania w normalnym toku działalności
Moja uwaga: jeśli używasz wyjątków w normalnych sytuacjach, w jaki sposób lokalizujesz nietypowe (tj. Wyjątki al) sytuacje?
Oczywiście istnieją inne ważne powody, aby zbyt często nie używać wyjątków, szczególnie pod względem wydajności
źródło
Wyjątek stanowią zasadniczo nielokalne
goto
stwierdzenia ze wszystkimi konsekwencjami tych ostatnich. Używanie wyjątków do kontroli przepływu narusza zasadę najmniejszego zdziwienia , utrudniaj czytanie programów (pamiętaj, że programy są najpierw pisane dla programistów).Co więcej, nie tego oczekują dostawcy kompilatorów. Oczekują, że wyjątki będą zgłaszane rzadko i zwykle pozwalają na to, by
throw
kod był dość nieefektywny. Zgłaszanie wyjątków jest jedną z najdroższych operacji w .NET.Jednak niektóre języki (zwłaszcza Python) używają wyjątków jako konstrukcji kontroli przepływu. Na przykład iteratory zgłaszają
StopIteration
wyjątek, jeśli nie ma żadnych innych elementów.for
Opierają się na tym nawet standardowe konstrukcje językowe (takie jak ).źródło
goto
do wyjątków, oczywiście, poprawnie współdziałają one ze stosem wywołań i zasięgiem leksykalnym i nie pozostawiają stosu ani zakresów w bałaganie.Moja ogólna zasada brzmi:
Problem, który widzę w wyjątkach, wynika z czysto składniowego punktu widzenia (jestem prawie pewien, że narzut wydajności jest minimalny). Nie lubię wszędzie try-blocków.
Weź ten przykład:
.. Innym przykładem może być sytuacja, gdy trzeba przypisać coś do uchwytu za pomocą fabryki, a ta fabryka może zgłosić wyjątek:
Więc osobiście uważam, że powinieneś zachować wyjątki dla rzadkich stanów błędów (brak pamięci itp.) I użyć wartości zwracanych (klasy wartości, struktury lub wyliczenia), aby zamiast tego sprawdzać błędy.
Mam nadzieję, że dobrze zrozumiałem twoje pytanie :)
źródło
Pierwsza reakcja na wiele odpowiedzi:
Oczywiście! Ale jeśli po prostu nie zawsze jest bardziej jasne.
To nie powinno być zadziwiające np .: divide (1 / x) catch (divisionByZero) jest bardziej przejrzysty niż jakikolwiek dla mnie (u Conrada i innych). Fakt, że tego rodzaju programowanie nie jest oczekiwane, jest czysto konwencjonalny i rzeczywiście nadal jest istotny. Może w moim przykładzie byłoby to jaśniejsze.
Ale DivisionByZero i FileNotFound są bardziej przejrzyste niż ifs.
Oczywiście, jeśli jest mniej wydajny i potrzebuje czasu zillion na sekundę, powinieneś go oczywiście unikać, ale nadal nie przeczytałem żadnego dobrego powodu, aby uniknąć ogólnego projektu.
Jeśli chodzi o zasadę najmniejszego zdziwienia: istnieje niebezpieczeństwo błędnego rozumowania: załóżmy, że cała społeczność stosuje zły projekt, ten projekt będzie oczekiwany! Dlatego zasada nie może być Graalem i należy ją uważnie pojmować.
W wielu reakcjach coś. jak to świeci koryta. Złap je, nie? Twoja metoda powinna być jasna, dobrze udokumentowana i oparta na umowie. Nie dostaję tego pytania, muszę przyznać.
Debugowanie wszystkich wyjątków: to samo, czasem tak się dzieje, ponieważ częste jest stosowanie wyjątków. Moje pytanie brzmiało: dlaczego jest to w ogóle powszechne?
źródło
x
przed dzwonieniem1/x
? 2) Czy zawijasz każdą operację podziału w blok typu try-catch, aby złapaćDivideByZeroException
? 3) Jaką logikę wkładasz w blok catch, aby się zregenerowaćDivideByZeroException
?openConfigFile();
po nim może wystąpić złapany FileNotFound z{ createDefaultConfigFile(); setFirstAppRun(); }
wyjątkiem FileNotFound obsługiwanym z wdziękiem; bez awarii, poprawmy wrażenia użytkownika końcowego, a nie gorzej. Możesz powiedzieć „Ale co jeśli to nie był tak naprawdę pierwszy bieg i dostają to za każdym razem?” Przynajmniej aplikacja działa za każdym razem i nie zawiesza się przy każdym uruchomieniu! W przypadku „1 do 10” jest to okropne ”:„ pierwsze uruchomienie ”przy każdym uruchomieniu = 3 lub 4, awaria przy każdym uruchomieniu = 10.x
przed rozmową telefoniczną1/x
, ponieważ zwykle jest w porządku. Wyjątkowym przypadkiem jest sytuacja, w której nie jest w porządku. Nie mówimy tutaj o wstrząsaniu ziemią, ale na przykład dla podstawowej liczby całkowitej podanej losowox
, tylko 1 na 4294967296 nie zrobiłby podziału. To wyjątek, a wyjątki to dobry sposób, aby sobie z tym poradzić. Można jednak użyć wyjątków, aby zaimplementować odpowiednikswitch
instrukcji, ale byłoby to dość głupie.Przed wyjątkami w C istniały
setjmp
ilongjmp
mogłyby zostać wykorzystane do podobnego rozwinięcia ramki stosu.Następnie ten sam konstrukt otrzymał nazwę: „Wyjątek”. Większość odpowiedzi opiera się na znaczeniu tej nazwy, aby spierać się o jej użycie, twierdząc, że wyjątki mają być stosowane w wyjątkowych warunkach. To nigdy nie było zamierzone w oryginale
longjmp
. Były tylko sytuacje, w których trzeba było przerwać przepływ kontroli w wielu ramkach stosu.Wyjątki są nieco bardziej ogólne, ponieważ można ich również używać w tej samej ramce stosu. Rodzi to analogie do
goto
tego, co moim zdaniem jest błędne. Gotos to ściśle powiązana para (podobnie jaksetjmp
ilongjmp
). Wyjątek stanowią luźno powiązane publikacje / subskrypcje, które są znacznie czystsze! Dlatego użycie ich w tej samej ramce stosu nie jest prawie tym samym, co użyciegoto
s.Trzecie źródło nieporozumień dotyczy tego, czy są sprawdzane, czy niesprawdzone wyjątki. Oczywiście niesprawdzone wyjątki wydają się szczególnie okropne w przypadku sterowania przepływem i być może wielu innych rzeczy.
Sprawdzone wyjątki są jednak świetne dla kontroli, gdy przejdziesz przez wszystkie wiktoriańskie zawieszenia i trochę żyjesz.
Moim ulubionym zastosowaniem jest sekwencja
throw new Success()
w długim fragmencie kodu, który próbuje jedna po drugiej, dopóki nie znajdzie tego, czego szuka. Każda rzecz - każda logika - może mieć dowolne zagnieżdżanie, więcbreak
nie ma ich również w testach warunkowych.if-else
Wzór jest kruche. Jeśli wyedytujęelse
zmienię składnię lub popsuję składnię w inny sposób, oznacza to włochaty błąd.Zastosowanie
throw new Success()
linearyzuje przepływ kodu. Korzystam zSuccess
klas zdefiniowanych lokalnie - oczywiście zaznaczonych - aby zapomnieć o złapaniu kodu, nie można go skompilować. I nie łapię innych metodSuccess
.Czasami mój kod sprawdza jedno po drugim i udaje się tylko wtedy, gdy wszystko jest w porządku. W tym przypadku korzystam z podobnej linearyzacji
throw new Failure()
.Korzystanie z oddzielnej funkcji łączy się z naturalnym poziomem podziału na przedziały. Więc
return
rozwiązanie nie jest optymalne. Wolę mieć stronę lub dwie strony kodu w jednym miejscu ze względów poznawczych. Nie wierzę w bardzo drobno podzielony kod.To, co robią JVM lub kompilatory, jest dla mnie mniej istotne, chyba że istnieje punkt aktywny. Nie mogę uwierzyć, że istnieje jakikolwiek fundamentalny powód, dla którego kompilatory nie wykrywają lokalnie zgłaszanych i wychwytywanych wyjątków i po prostu traktują je jako bardzo wydajne
goto
na poziomie kodu maszynowego.Jeśli chodzi o wykorzystanie ich w różnych funkcjach do sterowania przepływem - tj. W typowych przypadkach zamiast wyjątkowych - nie widzę, w jaki sposób byłyby mniej wydajne niż wielokrotne przerwanie, testy warunków, powrót do brodzenia przez trzy ramki stosu w przeciwieństwie do przywracania wskaźnik stosu.
Osobiście nie używam wzoru na ramkach stosu i widzę, jak wymagałoby to wyrafinowania projektu, aby zrobić to elegancko. Ale używane oszczędnie, powinno być w porządku.
Wreszcie, w odniesieniu do zaskakujących dziewiczych programistów, nie jest to istotny powód. Jeśli delikatnie wprowadzisz je do praktyki, nauczą się je kochać. Pamiętam, że C ++ zwykł zaskakiwać i straszyć cholernych programistów C.
źródło
return
-pattern wymagałaby dwóch funkcji dla każdej takiej funkcji. Zewnętrzna do przygotowania odpowiedzi serwletu lub innych takich działań, a wewnętrzna do wykonania obliczeń. PS: Angielski profesor prawdopodobnie sugerowałby, żebym użył „zadziwiającego” zamiast „zaskakującego” w ostatnim akapicie :-)Standardową odpowiedzią jest to, że wyjątki nie są regularne i powinny być stosowane w wyjątkowych przypadkach.
Jednym z ważnych dla mnie powodów jest to, że kiedy czytam
try-catch
strukturę kontrolną w oprogramowaniu, które utrzymuję lub debuguję, próbuję dowiedzieć się, dlaczego oryginalny koder używał obsługi wyjątków zamiastif-else
struktury. I oczekuję, że znajdę dobrą odpowiedź.Pamiętaj, że piszesz kod nie tylko dla komputera, ale także dla innych programistów. Z programem obsługi wyjątków istnieje semantyczny, którego nie można wyrzucić tylko dlatego, że maszyna nie ma nic przeciwko.
źródło
Co powiesz na wydajność? Podczas testowania obciążenia aplikacji internetowej .NET doszliśmy do 100 symulowanych użytkowników na serwer WWW, dopóki nie naprawiliśmy często występującego wyjątku i liczba ta wzrosła do 500 użytkowników.
źródło
Josh Bloch obszernie zajmuje się tym tematem w Effective Java. Jego sugestie są pouczające i powinny dotyczyć również .Net (z wyjątkiem szczegółów).
W szczególności należy stosować wyjątki w wyjątkowych okolicznościach. Przyczyny tego są głównie związane z użytecznością. Aby dana metoda była maksymalnie użyteczna, jej warunki wejściowe i wyjściowe powinny być maksymalnie ograniczone.
Na przykład druga metoda jest łatwiejsza w użyciu niż pierwsza:
W obu przypadkach należy sprawdzić, aby upewnić się, że osoba dzwoniąca prawidłowo używa interfejsu API. Ale w drugim przypadku jest to wymagane (domyślnie). Miękkie wyjątki będą nadal zgłaszane, jeśli użytkownik nie przeczyta javadoc, ale:
Najważniejsze jest to, że wyjątków nie należy używać jako kodów powrotu, głównie dlatego, że skomplikowałeś nie tylko TWOJE API, ale także API dzwoniącego.
Właściwe postępowanie jest oczywiście kosztowne. Koszt jest taki, że każdy musi zrozumieć, że musi przeczytać i postępować zgodnie z dokumentacją. Mam nadzieję, że i tak jest.
źródło
Myślę, że możesz użyć wyjątków do kontroli przepływu. Istnieje jednak druga strona tej techniki. Tworzenie wyjątków jest kosztowne, ponieważ muszą utworzyć ślad stosu. Jeśli więc chcesz używać wyjątków częściej niż tylko do sygnalizowania wyjątkowej sytuacji, musisz upewnić się, że tworzenie śladów stosu nie wpływa negatywnie na twoją wydajność.
Najlepszym sposobem na obniżenie kosztów tworzenia wyjątków jest zastąpienie metody fillInStackTrace () w następujący sposób:
Taki wyjątek nie będzie wypełniał żadnych śladów stosu.
źródło
Naprawdę nie widzę, jak kontrolujesz przepływ programu w cytowanym kodzie. Nigdy nie zobaczysz innego wyjątku oprócz wyjątku ArgumentOutOfRange. (Więc twoja druga klauzula połowowa nigdy nie zostanie trafiona). Wszystko, co robisz, to użycie niezwykle kosztownego rzutu, aby naśladować instrukcję if.
Ponadto nie wykonujesz bardziej złowrogich operacji, w których po prostu rzucasz wyjątek wyłącznie w celu złapania go gdzie indziej w celu kontroli przepływu. W rzeczywistości zajmujesz się wyjątkową sprawą.
źródło
Oto najlepsze praktyki, które opisałem w moim blogu :
źródło
Ponieważ kod jest trudny do odczytania, możesz mieć problemy z jego debugowaniem, wprowadzasz nowe błędy podczas usuwania błędów po długim czasie, jest droższy pod względem zasobów i czasu, i denerwuje cię, jeśli debugujesz swój kod i debugger zatrzymuje się przy każdym wyjątku;)
źródło
Oprócz podanych powodów jednym z powodów, dla których nie należy stosować wyjątków do kontroli przepływu, jest to, że może to znacznie skomplikować proces debugowania.
Na przykład, gdy próbuję wyśledzić błąd w VS, zwykle włączam „zerwanie ze wszystkimi wyjątkami”. Jeśli używasz wyjątków do kontroli przepływu, będę regularnie włamywał się do debuggera i będę musiał ignorować te wyjątkowe wyjątki, dopóki nie dojdę do prawdziwego problemu. To może doprowadzić kogoś do szału !!
źródło
Załóżmy, że masz metodę, która wykonuje pewne obliczenia. Istnieje wiele parametrów wejściowych, które należy sprawdzić, a następnie zwrócić liczbę większą niż 0.
Używanie wartości zwracanych do sygnalizowania błędu sprawdzania poprawności jest proste: jeśli metoda zwróciła liczbę mniejszą niż 0, wystąpił błąd. Jak więc powiedzieć, które parametr nie został sprawdzony?
Pamiętam z moich dni C wiele funkcji zwróciło kody błędów takie jak to:
itp.
Czy to naprawdę mniej czytelne niż rzucanie i łapanie wyjątku?
źródło
Możesz użyć pazura młota, aby obrócić śrubę, tak jak możesz użyć wyjątków dla kontroli przepływu. To nie znaczy, że jest to zamierzone użycie tej funkcji. Te
if
warunki wyraża oświadczenie, których wykorzystanie przeznaczone jest kontrolowanie przepływu.Jeśli korzystasz z funkcji w niezamierzony sposób, a jednocześnie nie chcesz korzystać z funkcji zaprojektowanej do tego celu, naliczany będzie koszt. W tym przypadku jasność i wydajność nie mają żadnej wartości dodanej. Co korzystanie z wyjątków kupuje w porównaniu z powszechnie akceptowanym
if
stwierdzeniem?Powiedział inaczej: tylko dlatego, że możesz , nie znaczy, że powinieneś .
źródło
if
do normalnego użycia lub wykonania nie jest zamierzone, ponieważ nie jest zamierzone (argument kołowy)?public class ExitHelper{ public static void cleanExit() { cleanup(); System.exit(1); } }
Następnie po prostu wywołaj to zamiast rzucać:ExitHelper.cleanExit();
Jeśli twój argument byłby trafny, byłoby to preferowane podejście i nie byłoby żadnych wyjątków. Mówisz w zasadzie „Jedynym powodem wyjątków jest awaria w inny sposób”.if
.Jak inni wspominali licznie, zasada najmniejszego zdziwienia zabrania nadmiernego wykorzystywania wyjątków wyłącznie do celów kontroli przepływu. Z drugiej strony żadna reguła nie jest w 100% poprawna i zawsze zdarzają się przypadki, w których wyjątkiem jest „właściwe narzędzie” - tak
goto
przy okazji, podobnie jak on sam, który jest dostarczany w formiebreak
iwcontinue
językach takich jak Java, które są często idealnym sposobem na wyskakiwanie z mocno zagnieżdżonych pętli, których nie zawsze można uniknąć.Poniższy post na blogu wyjaśnia raczej złożony, ale także interesujący przypadek użycia dla użytkownika nielokalnego
ControlFlowException
:Wyjaśnia, jak wewnątrz jOOQ (biblioteki abstrakcji SQL dla Javy) takie wyjątki są czasami używane do przerwania procesu renderowania SQL wcześniej, gdy spełniony zostanie jakiś „rzadki” warunek.
Przykładami takich warunków są:
Napotkano zbyt wiele wartości powiązań. Niektóre bazy danych nie obsługują dowolnej liczby wartości powiązań w instrukcjach SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). W takich przypadkach jOOQ przerywa fazę renderowania SQL i ponownie renderuje instrukcję SQL z wbudowanymi wartościami powiązania. Przykład:
Gdybyśmy jawnie wyodrębnili wartości powiązań z zapytania AST, aby je policzyć za każdym razem, marnowalibyśmy cenne cykle procesora dla tych 99,9% zapytań, które nie cierpią z powodu tego problemu.
Niektóre logiki są dostępne tylko pośrednio poprzez interfejs API, który chcemy wykonać tylko „częściowo”.
UpdatableRecord.store()
Metoda generujeINSERT
lubUPDATE
oświadczenia, w zależności odRecord
„s wewnętrznych flagami. Z „zewnątrz” nie wiemy, w jaki rodzaj logiki jest zawartystore()
(np. Optymistyczne blokowanie, obsługa detektora zdarzeń itp.), Więc nie chcemy powtarzać tej logiki, gdy przechowujemy kilka rekordów w instrukcji wsadowej, gdzie chcielibyśmystore()
wygenerować tylko instrukcję SQL, a nie wykonać ją. Przykład:Gdybyśmy przekazali
store()
logikę zewnętrzną do interfejsu API „wielokrotnego użytku”, który można dostosować tak, aby opcjonalnie nie uruchamiał SQL, chcielibyśmy stworzyć raczej trudny w utrzymaniu, trudny do ponownego użycia interfejs API.Wniosek
Zasadniczo nasze użycie tych nielokalnych
goto
jest zgodne z tym, co powiedział [Mason Wheeler] [5] w swojej odpowiedzi:Oba zastosowania
ControlFlowExceptions
były raczej łatwe do wdrożenia w porównaniu z ich alternatywami, co pozwoliło nam na ponowne użycie szerokiego zakresu logiki bez konieczności refaktoryzacji z odpowiednich elementów wewnętrznych.Ale wrażenie, że jest to trochę niespodzianka dla przyszłych opiekunów, pozostaje. Kod wydaje się dość delikatny i chociaż w tym przypadku był to właściwy wybór, zawsze woleliśmy nie używać wyjątków dla lokalnego przepływu sterowania, w którym łatwo jest uniknąć zwykłego rozgałęzienia
if - else
.źródło
Zwykle samo w sobie nie ma nic złego w obsłudze wyjątku na niskim poziomie. Wyjątek JEST poprawnym komunikatem, który zawiera wiele szczegółów uzasadniających, dlaczego nie można wykonać operacji. A jeśli sobie z tym poradzisz, powinieneś.
Ogólnie rzecz biorąc, jeśli wiesz, że istnieje duże prawdopodobieństwo niepowodzenia, które możesz sprawdzić ... powinieneś zrobić sprawdzenie ... tzn. Jeśli (obj! = Null) obj.method ()
W twoim przypadku nie jestem wystarczająco zaznajomiony z biblioteką C #, aby wiedzieć, czy data i godzina ma łatwy sposób sprawdzenia, czy znacznik czasu jest poza zakresem. Jeśli tak, po prostu wywołaj if (.isvalid (ts)), w przeciwnym razie twój kod będzie w porządku.
Zasadniczo sprowadza się to do tego, który z tych sposobów tworzy czystszy kod ... jeśli operacja ochrony przed oczekiwanym wyjątkiem jest bardziej złożona niż sama obsługa wyjątku; niż masz moje pozwolenie na obsługę wyjątku zamiast tworzenia złożonych strażników wszędzie.
źródło
Jeśli używasz procedur obsługi wyjątków do przepływu sterowania, jesteś zbyt ogólny i leniwy. Jak ktoś wspomniał, wiesz, że coś się stało, jeśli zajmujesz się przetwarzaniem w module obsługi, ale co dokładnie? Zasadniczo używasz wyjątku dla instrukcji else, jeśli używasz go do sterowania przepływem.
Jeśli nie wiesz, jaki możliwy stan może wystąpić, możesz użyć procedury obsługi wyjątków dla nieoczekiwanych stanów, na przykład gdy musisz użyć biblioteki innej firmy lub musisz złapać wszystko w interfejsie użytkownika, aby wyświetlić niezły błąd wiadomość i zaloguj wyjątek.
Jeśli jednak wiesz, co może pójść nie tak, i nie umieszczasz instrukcji if lub czegoś, aby to sprawdzić, oznacza to, że jesteś po prostu leniwy. Dopuszczenie, by procedura obsługi wyjątków była wszystkim dla rzeczy, o których wiesz, że może się zdarzyć, jest leniwa i wróci, by cię prześladować później, ponieważ będziesz próbował naprawić sytuację w module obsługi wyjątków na podstawie prawdopodobnie fałszywego założenia.
Jeśli umieścisz logikę w module obsługi wyjątków, aby ustalić, co dokładnie się wydarzyło, byłbyś głupi, gdybyś nie umieścił tej logiki w bloku try.
Procedury obsługi wyjątków są ostatecznością, ponieważ gdy zabraknie Ci pomysłów / sposobów, aby zapobiec sytuacji, w której coś pójdzie nie tak lub coś jest poza twoją kontrolą. Serwer nie działa i upłynął limit czasu i nie można zapobiec zgłoszeniu wyjątku.
Wreszcie, wykonanie wszystkich kontroli z wyprzedzeniem pokazuje, co wiesz lub czego oczekujesz, i czyni to jawnym. Kod powinien być wyraźny w zamiarze. Co wolisz czytać?
źródło
Możesz być zainteresowany spojrzeniem na system warunków Common Lisp, który jest pewnego rodzaju uogólnieniem wyjątków wykonanych prawidłowo. Ponieważ możesz rozwinąć stos lub nie w kontrolowany sposób, otrzymujesz również „restarty”, które są niezwykle przydatne.
Nie ma to nic wspólnego z najlepszymi praktykami w innych językach, ale pokazuje, co można zrobić z myślą o projektowaniu (z grubsza) w kierunku, o którym myślisz.
Oczywiście wciąż istnieją względy wydajnościowe, jeśli podskakujesz w górę i w dół jak jo-jo, ale jest to o wiele bardziej ogólny pomysł niż „o cholera, pozwala za kaucją” podejście, które ucieleśnia większość systemów wyjątków typu catch / throw.
źródło
Nie sądzę, żeby było coś złego w używaniu wyjątków do kontroli przepływu. Wyjątki są nieco podobne do kontynuacji i w językach o typie statycznym. Wyjątki są silniejsze niż kontynuacje, więc jeśli potrzebujesz kontynuacji, ale twój język ich nie ma, możesz użyć wyjątków, aby je zaimplementować.
Cóż, właściwie, jeśli potrzebujesz kontynuacji, a twój język ich nie ma, wybrałeś niewłaściwy język i powinieneś raczej używać innego. Ale czasami nie masz wyboru: programowanie stron internetowych po stronie klienta jest najlepszym przykładem - nie tylko nie sposób ominąć JavaScript.
Przykład: Microsoft Volta to projekt, który umożliwia pisanie aplikacji internetowych w prostym .NET i pozwala frameworkowi zadecydować, które bity należy uruchomić w danym miejscu. Jedną z konsekwencji tego jest to, że Volta musi być w stanie skompilować CIL do JavaScript, aby można było uruchomić kod na kliencie. Istnieje jednak problem: .NET ma wielowątkowość, JavaScript nie. Tak więc Volta implementuje kontynuacje w JavaScript za pomocą wyjątków JavaScript, a następnie implementuje wątki .NET za pomocą tych kontynuacji. W ten sposób aplikacje Volty korzystające z wątków można skompilować do działania w niezmodyfikowanej przeglądarce - nie jest wymagane Silverlight.
źródło
Jeden powód estetyczny:
Próba zawsze wiąże się z haczykiem, podczas gdy if nie musi iść z innym.
Z try / catch staje się bardziej gadatliwy.
To o 6 linii kodu za dużo.
źródło
Ale nie zawsze będziesz wiedział, co dzieje się w Metodzie, którą wywołujesz. Nie będziesz wiedział dokładnie, gdzie został zgłoszony wyjątek. Bez szczegółowego badania obiektu wyjątku ....
źródło
Czuję, że nie ma nic złego w twoim przykładzie. Przeciwnie, grzechem byłoby zignorowanie wyjątku zgłaszanego przez wywoływaną funkcję.
W JVM zgłaszanie wyjątku nie jest aż tak drogie, jedynie tworzenie wyjątku za pomocą nowego wyjątku xyzException (...), ponieważ ten ostatni wymaga przejścia stosu. Jeśli więc wcześniej utworzono jakieś wyjątki, możesz je zgłaszać wiele razy bez ponoszenia kosztów. Oczywiście w ten sposób nie można przesyłać danych wraz z wyjątkiem, ale myślę, że i tak jest to złe.
źródło
Istnieje kilka ogólnych mechanizmów, za pomocą których język może pozwolić na zakończenie metody bez zwracania wartości i odejście do następnego bloku „catch”:
Poproś tę metodę o sprawdzenie ramki stosu w celu ustalenia strony wywołującej i użyj metadanych dla strony wywołującej, aby znaleźć albo informacje o
try
bloku w metodzie wywołującej, albo lokalizację, w której metoda wywołująca przechowuje adres swojego wywołującego; w tej drugiej sytuacji sprawdź metadane osoby dzwoniącej, aby określić ją w taki sam sposób, jak osoba dzwoniąca, powtarzając ją do momentu znalezieniatry
bloku lub pustego stosu. Takie podejście powoduje bardzo niewielki narzut w przypadku braku wyjątku (wyklucza pewne optymalizacje), ale jest drogie, gdy wystąpi wyjątek.Niech metoda zwróci „ukrytą” flagę, która odróżnia normalny powrót od wyjątku, i niech wywołujący sprawdzi tę flagę i rozgałęzi się do procedury „wyjątku”, jeśli jest ustawiona. Ta procedura dodaje 1-2 instrukcje do przypadku bez wyjątku, ale stosunkowo niewielki narzut, gdy wystąpi wyjątek.
Poproś dzwoniącego o umieszczenie informacji lub kodu obsługi wyjątków pod stałym adresem względem skumulowanego adresu zwrotnego. Na przykład w przypadku ARM zamiast instrukcji „podprogram BL” można użyć sekwencji:
Aby wyjść normalnie, podprogram po prostu zrobiłby
bx lr
lubpop {pc}
; w przypadku nienormalnego wyjścia, podprogram odejmuje 4 od LR przed wykonaniem zwrotu lub używasub lr,#4,pc
(w zależności od wariantu ARM, trybu wykonania itp.) To podejście będzie działać bardzo źle, jeśli dzwoniący nie jest przystosowany do tego.Język lub środowisko wykorzystujące sprawdzone wyjątki mogą skorzystać z obsługi takich mechanizmów jak # 2 lub # 3 powyżej, natomiast niesprawdzone wyjątki są obsługiwane za pomocą # 1. Chociaż wdrożenie sprawdzonych wyjątków w Javie jest dość uciążliwe, nie byłyby złym pomysłem, gdyby istniały środki, za pomocą których strona wywołująca mogłaby powiedzieć w zasadzie: „Ta metoda jest deklarowana jako rzucanie XX, ale nie oczekuję kiedykolwiek to zrobić; jeśli tak, zwróć jako wyjątek „niesprawdzony”. W ramach, w których sprawdzone wyjątki były obsługiwane w taki sposób, mogą one być skutecznym środkiem kontroli przepływu dla takich rzeczy jak metody parsowania, które w niektórych kontekstach mogą mieć wysokie prawdopodobieństwo niepowodzenia, ale gdy niepowodzenie powinno zwrócić zasadniczo inne informacje niż sukces. Nie jestem jednak świadomy żadnych ram, które używają takiego wzorca. Zamiast tego, bardziej powszechnym wzorcem jest zastosowanie powyższego pierwszego podejścia (minimalny koszt dla przypadku bez wyjątku, ale wysoki koszt przy zgłaszaniu wyjątków) dla wszystkich wyjątków.
źródło