Dlaczego nie wykorzystać wyjątków jako regularnego przepływu kontroli?


193

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.MinValueNa przykład int.MaxValue + 1 == int.MinValue:).

Ale, widząc moją okrutną naturę, dodam zniewagę do tej kontuzji, rozszerzając to zachowanie na, powiedzmy, DateTimetyp Overridden . (Wiem, że DateTimejest 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 Addmetoda:

/// <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:

  1. Jasny
  2. Honoruj ​​kontrakt swojej metody

Zastrzel mnie.


2
+1 Czuję to samo. Poza wydajnością jedynym dobrym powodem do unikania przepływu kontroli wyjątków jest sytuacja, w której kod dzwoniącego będzie znacznie bardziej czytelny dzięki zwracanym wartościom.

4
jest: return -1, jeśli coś się wydarzyło, return -2, jeśli coś innego itp.… naprawdę bardziej czytelne niż wyjątki?
kender

1
To smutne, że ktoś dostaje negatywną reputację za mówienie prawdy: że twój przykład nie mógłby zostać napisany, gdyby twierdzenia. (Nie oznacza to, że jest poprawny / kompletny.)
Ingo

8
Twierdziłbym, że wyłączenie wyjątku może czasami być Twoją jedyną opcją. Mam na przykład komponent biznesowy, który inicjuje swój stan wewnętrzny w swoim konstruktorze, sprawdzając bazę danych. Są chwile, kiedy nie są dostępne odpowiednie dane w bazie danych. Zgłoszenie wyjątku w konstruktorze jest jedynym sposobem skutecznego anulowania budowy obiektu. Jest to wyraźnie określone w umowie (w moim przypadku Javadoc), więc nie mam problemu, że kod klienta może (i powinien) wychwycić ten wyjątek podczas tworzenia komponentu i kontynuować od tego momentu.
Stefan Haberl

1
Ponieważ sformułowałeś hipotezę, na tobie spoczywa również obowiązek przytoczenia potwierdzających dowodów / powodów. Na początek podaj jeden z powodów, dla których Twój kod przewyższa znacznie krótsze, samokontrujące się ifoś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.
Konrad Rudolph

Odpowiedzi:


170

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


13
Przykład: kiedy debuguję program .net, uruchamiam go ze studia wizualnego i proszę VS o złamanie wszystkich wyjątków. Jeśli polegasz na wyjątkach jako oczekiwanym zachowaniu, nie mogę tego dłużej robić (ponieważ spowodowałoby to uszkodzenie 5 razy / s), a zlokalizowanie problematycznej części kodu jest znacznie bardziej skomplikowane.
Brann

15
+1 za wskazanie, że nie chcesz tworzyć wyjątkowego stogu siana, w którym znajdziesz prawdziwą wyjątkową igłę.
Grant Wagner

16
w ogóle nie otrzymuję tej odpowiedzi, myślę, że ludzie tutaj źle to rozumieją W ogóle nie ma to nic wspólnego z debugowaniem, ale z designem. Obawiam się, że jest to okrągłe rozumowanie w jego czystej formie. I wasz punkt widzenia jest naprawdę poza pytaniem, jak powiedziano wcześniej
Peter

11
@Peter: Debugowanie bez wyłączania wyjątków jest trudne, a wyłapanie wszystkich wyjątków jest bolesne, jeśli jest ich wiele z założenia. Myślę, że projekt, który sprawia, że ​​debugowanie jest trudne, jest prawie częściowo zepsuty (innymi słowy, projekt ma coś wspólnego z debugowaniem, IMO)
Brann

7
Nawet ignorując fakt, że większość sytuacji, które chcę debugować, nie odpowiadają zgłaszanym wyjątkom, odpowiedź na twoje pytanie brzmi: „według typu”, np. Powiem mojemu debuggerowi, aby przechwycił tylko AssertionError lub StandardError lub coś, co robi odpowiadają na złe rzeczy. Jeśli masz z tym problem, to jak logujesz się - czy nie logujesz się według poziomu i klasy, a dokładnie, aby móc je filtrować? Myślisz, że to też zły pomysł?
Ken

157

Wyjątek stanowią zasadniczo nielokalne gotostwierdzenia 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 throwkod 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ą StopIterationwyjątek, jeśli nie ma żadnych innych elementów. forOpierają się na tym nawet standardowe konstrukcje językowe (takie jak ).


11
hej, wyjątki nie są zadziwiające! I w pewnym sensie zaprzeczasz sobie, gdy mówisz „to zły pomysł”, a następnie mówisz „ale to dobry pomysł w pythonie”.
hasen

6
Nadal nie jestem wcale przekonany: 1) Oprócz pytania o wydajność, wiele programów innych niż bachtowe nie mogło mniej obchodzić (np. Interfejs użytkownika) 2) Zdumiewające: jak powiedziałem, to zadziwiające, ponieważ nie jest używane, ale pozostaje pytanie: dlaczego nie użyć id? Ale skoro taka jest odpowiedź
Peter

4
+1 Właściwie cieszę się, że wskazałeś różnicę między Pythonem a C #. Nie sądzę, że to sprzeczność. Python jest znacznie bardziej dynamiczny, a oczekiwanie na użycie wyjątków w ten sposób jest wypiekane na język. Jest to również część kultury EAFP Pythona. Nie wiem, które podejście jest koncepcyjnie czystsze lub bardziej spójne, ale podoba mi się pomysł pisania kodu, który robi to, co inni tego oczekują , co oznacza różne style w różnych językach.
Mark E. Haase

13
W przeciwieństwie gotodo 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.
Lukas Eder

4
W rzeczywistości większość dostawców maszyn wirtualnych oczekuje wyjątków i obsługuje je skutecznie. Jak zauważa @LukasEder, wyjątki są całkowicie odmienne od goto, ponieważ mają strukturę.
Marcin

29

Moja ogólna zasada brzmi:

  • Jeśli możesz zrobić wszystko, aby naprawić błąd, wychwyć wyjątki
  • Jeśli błąd występuje bardzo często (np. Użytkownik próbował zalogować się przy użyciu niewłaściwego hasła), użyj returnvalues
  • Jeśli nie możesz nic zrobić, aby naprawić błąd, pozostaw go bez przechwytywania (lub złap go w głównym łapaczu, aby częściowo częściowo z wdziękiem zamknąć aplikację)

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:

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

.. 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:

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

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 :)


4
Odp: Twój drugi przykład - dlaczego po prostu nie po prostu wywołaj BestMethodEver w bloku try, po Build? Jeśli Build () zgłosi wyjątek, nie zostanie on wykonany, a kompilator jest szczęśliwy.
Blorgbeard jest poza

2
Tak, prawdopodobnie to byś na tym skończył, ale rozważ bardziej złożony przykład, w którym sam typ myInstance może zgłaszać wyjątki. I inne wartości w zakresie metody też mogą. Skończysz z wieloma zagnieżdżonymi blokami try / catch :(
cwap

Powinieneś zrobić tłumaczenie wyjątku (na typ wyjątku odpowiedni do poziomu abstrakcji) w swoim bloku catch. FYI: „Multi-catch” ma wejść w Javę 7.
jasonnerothin

FYI: W C ++ możesz umieścić wiele połowów po próbie złapania różnych wyjątków.
RobH

2
W przypadku oprogramowania shrinkwrap musisz wychwycić wszystkie wyjątki. Przynajmniej otwórz okno dialogowe wyjaśniające, że program musi zostać zamknięty, a oto coś niezrozumiałego, które możesz wysłać w raporcie o błędzie.
David Thornley,

25

Pierwsza reakcja na wiele odpowiedzi:

piszesz dla programistów i zasada najmniejszego zdziwienia

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ć.

wyjątki dla normalnych sytuacji, jak zlokalizować sytuacje niezwykłe (tj. wyjątkowe)?

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?


1
1) Czy zawsze sprawdzasz xprzed dzwonieniem 1/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?
Lightman

1
Z wyjątkiem DivisionByZero i FileNotFound są złymi przykładami, ponieważ są wyjątkowymi przypadkami, które należy traktować jako wyjątki.
0x6C38

Nie ma nic „nadmiernie wyjątkowego” w pliku, który nie został znaleziony w taki sposób, jak reklamujący się tutaj „anty-wyjątkowi” ludzie. 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.
Loduwijk

Twoje przykłady są wyjątkami. Nie, nie zawsze sprawdzasz xprzed 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 losowo x, 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ć odpowiednik switchinstrukcji, ale byłoby to dość głupie.
Thor84no,

16

Przed wyjątkami w C istniały setjmpi longjmpmogł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 gototego, co moim zdaniem jest błędne. Gotos to ściśle powiązana para (podobnie jak setjmpi longjmp). 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życie gotos.

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ęc breaknie ma ich również w testach warunkowych. if-elseWzó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 z Successklas zdefiniowanych lokalnie - oczywiście zaznaczonych - aby zapomnieć o złapaniu kodu, nie można go skompilować. I nie łapię innych metod Success.

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 returnrozwią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 wydajnegoto 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.


3
Korzystając z tego wzorca, większość moich gruboziarnistych funkcji ma na końcu dwa małe zaczepy - jeden dla Sukcesu, a drugi dla Niepowodzenia, i tam właśnie funkcja podsumowuje takie rzeczy, jak przygotowanie poprawnej odpowiedzi serwletu lub przygotowanie wartości zwracanych. Posiadanie jednego miejsca na podsumowanie jest miłe. Opcja 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 :-)
nekromanta

11

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-catchstrukturę 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.


Myślę, że niedoceniona odpowiedź. Komputer może nie zwolnić zbyt wiele, gdy wykryje połknięcie wyjątku, ale gdy pracuję nad kodem innej osoby i natrafiam na to, zatrzymuje mnie martwy na moich śladach, gdy ćwiczę, jeśli przegapiłem coś ważnego, czego nie zrobiłem nie wiem, czy faktycznie nie ma uzasadnienia dla zastosowania tego anty-wzoru.
Tim Abell,

9

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.


8

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:

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 * @throws AdditionException if addend1 or addend2 is less than or equal to zero
 */
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
  if( addend1 <= 0 ){
     throw new AdditionException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new AdditionException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 */
public int addPositiveNumbers(int addend1, int addend2) {
  if( addend1 <= 0 ){
     throw new IllegalArgumentException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new IllegalArgumentException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

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:

  1. Nie musisz tego dokumentować.
  2. Nie musisz go testować (w zależności od tego, jak agresywna jest Twoja strategia testów jednostkowych).
  3. Nie wymaga się od osoby dzwoniącej obsługi trzech przypadków użycia.

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.


7

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:

public Throwable fillInStackTrace() { return this; }

Taki wyjątek nie będzie wypełniał żadnych śladów stosu.


Stacktrace wymaga również, aby osoba dzwoniąca „wiedziała” (tj. Była zależna od) wszystkich Throwables w stosie. To jest zła rzecz. Zgłaszaj wyjątki odpowiednie do poziomu abstrakcji (ServiceExceptions in Services, DaoExceptions from Method Dao itp.). Po prostu przetłumacz w razie potrzeby.
jasonnerothin

5

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ą.


5

Oto najlepsze praktyki, które opisałem w moim blogu :

  • Rzuć wyjątek, aby podać nieoczekiwaną sytuację w swoim oprogramowaniu.
  • Użyj wartości zwracanych do weryfikacji poprawności danych wejściowych .
  • Jeśli wiesz, jak radzić sobie z wyjątkami zgłaszanymi przez bibliotekę, złap je na najniższym możliwym poziomie .
  • Jeśli masz nieoczekiwany wyjątek, całkowicie odrzuć bieżącą operację. Nie udawaj, że wiesz, jak sobie z nimi radzić .

4

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;)


4

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 !!


1
Już poradziłem sobie z jedną wyższą: debugowanie wszystkich wyjątków: to samo, po prostu zrobione, ponieważ projekt nieużywania wyjątków jest powszechny. Moje pytanie brzmiało: dlaczego jest to w ogóle powszechne?
Peter,

Więc twoja odpowiedź jest w zasadzie „To źle, ponieważ Visual Studio ma tę jedną funkcję ...”? Programuję od około 20 lat i nawet nie zauważyłem, że istnieje opcja „przerwania wszystkich wyjątków”. Mimo to „z powodu tej jednej funkcji!” brzmi jak słaby powód. Wystarczy prześledzić wyjątek od jego źródła; mam nadzieję, że używasz języka, który sprawia, że ​​jest to łatwe - w przeciwnym razie twój problem dotyczy funkcji językowych, a nie ogólnego zastosowania samych wyjątków.
Loduwijk

3

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:

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

itp.

Czy to naprawdę mniej czytelne niż rzucanie i łapanie wyjątku?


dlatego wymyślili wyliczenia :) Ale magiczne liczby to zupełnie inny temat .. en.wikipedia.org/wiki/…
Isak Savo

Świetny przykład. Już miałem napisać to samo. @ IsakSavo: Wyliczenia nie są pomocne w tej sytuacji, jeśli metoda zwraca wartość znaczącą lub obiekt. Np. GetAccountBalance () powinien zwrócić obiekt Money, a nie obiekt AccountBalanceResultEnum. Wiele programów C ma podobny wzorzec, w którym jedna wartość wartownika (0 lub null) reprezentuje błąd, a następnie trzeba wywołać inną funkcję, aby uzyskać osobny kod błędu w celu ustalenia przyczyny błędu. (Interfejs API MySQL C jest taki.)
Mark E. Haase

3

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 ifwarunki 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 ifstwierdzeniem?

Powiedział inaczej: tylko dlatego, że możesz , nie znaczy, że powinieneś .


1
Czy mówisz, że nie ma potrzeby wyjątku, po tym, jak mamy ifdo normalnego użycia lub wykonania nie jest zamierzone, ponieważ nie jest zamierzone (argument kołowy)?
Val

1
@Val: Wyjątki dotyczą wyjątkowych sytuacji - jeśli wykryjemy wystarczająco dużo, aby zgłosić wyjątek i obsłużyć go, mamy wystarczającą ilość informacji, aby go nie wyrzucić i nadal go obsłużyć. Możemy przejść od razu do logiki obsługi i pominąć kosztowną, zbędną próbę / złapanie.
Bryan Watts,

Zgodnie z tą logiką równie dobrze możesz nie mieć wyjątków i zawsze robić wyjście z systemu zamiast zgłaszać wyjątek. Jeśli chcesz coś zrobić przed wyjściem, zrób opakowanie i zadzwoń. Przykład Java: 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”.
Loduwijk

@Aaron: Jeśli rzucę i złapię wyjątek, mam wystarczającą ilość informacji, aby tego uniknąć. To nie znaczy, że wszystkie wyjątki są nagle śmiertelne. Inny kod, którego nie kontroluję, może go złapać i to jest w porządku. Moim argumentem, który pozostaje słuszny, jest to, że zgłaszanie wyjątku w tym samym kontekście jest zbędne. Nie stwierdziłem ani nie powiedziałbym, że wszystkie wyjątki powinny wyjść z procesu.
Bryan Watts

@BryanWatts Acknowledged. Wielu innych nie powiedział, że należy używać tylko wyjątki dla niczego nie można odzyskać od, a co za tym idzie powinna być zawsze upaść na wyjątki. Dlatego trudno jest omawiać te rzeczy; są nie tylko 2 opinie, ale wiele. Nadal się z tobą nie zgadzam, ale nie zdecydowanie. Są chwile, w których rzucanie / łapanie razem jest najbardziej czytelnym, łatwym do utrzymania, ładniej wyglądającym kodem; zwykle dzieje się tak, jeśli już wychwytujesz inne wyjątki, więc już próbujesz / złapiesz, a dodanie 1 lub 2 kolejnych połowów jest czystsze niż oddzielne sprawdzanie błędów przez if.
Loduwijk

3

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 gotoprzy okazji, podobnie jak on sam, który jest dostarczany w formie breakiw continueję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:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }

    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 generuje INSERTlub UPDATEoświadczenia, w zależności od Record„s wewnętrznych flagami. Z „zewnątrz” nie wiemy, w jaki rodzaj logiki jest zawarty store()(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śmy store()wygenerować tylko instrukcję SQL, a nie wykonać ją. Przykład:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }

    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 gotojest zgodne z tym, co powiedział [Mason Wheeler] [5] w swojej odpowiedzi:

„Właśnie spotkałem się z sytuacją, z którą w tej chwili nie mogę sobie poradzić, ponieważ nie mam wystarczającego kontekstu, aby sobie z tym poradzić, ale procedura, która do mnie zadzwoniła (lub coś wyżej niż stos wywołań) powinna wiedzieć, jak sobie z tym poradzić . ”

Oba zastosowania ControlFlowExceptionsbył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.


2

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.


Add'l point: Jeśli Twój wyjątek zawiera informacje o przechwytywaniu błędów (moduł pobierający, taki jak „Param getWhatParamMESSMeUp ()”), może pomóc użytkownikowi interfejsu API podjąć dobrą decyzję, co dalej. W przeciwnym razie podajesz nazwę stanu błędu.
jasonnerothin

2

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ć?


1
W ogóle nieprawda: „Zasadniczo używasz wyjątku dla instrukcji else, jeśli używasz go do przepływu sterowania.” Jeśli używasz go do przepływu sterowania, wiesz dokładnie, co dokładnie łapiesz i nigdy nie używasz ogólnego catch, ale oczywiście jeden!
Peter

2

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.


2

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.


2

Jeden powód estetyczny:

Próba zawsze wiąże się z haczykiem, podczas gdy if nie musi iść z innym.

if (PerformCheckSucceeded())
   DoSomething();

Z try / catch staje się bardziej gadatliwy.

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

To o 6 linii kodu za dużo.


1

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 ....


1

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.


Przepraszam, to oczywiste, Brann. To zależy od stanu. Nie zawsze jest tak, że warunek jest trywialny. Zatem instrukcja if może zająć godziny, dni lub nawet dłużej.
Ingo

To znaczy w JVM. Nie droższy niż zwrot. Domyśl. Ale pytanie brzmi: co byś napisał w instrukcji if, jeśli nie sam kod, który jest już obecny w wywoływanej funkcji, aby odróżnić wyjątkowy przypadek od normalnego --- w ten sposób duplikacja kodu?
Ingo

1
Ingo: wyjątkowa sytuacja to taka, której się nie spodziewasz. tzn. taki, o którym nie myślałeś jako programisty. Więc moją zasadą jest „pisz kod, który nie zgłasza wyjątków” :)
Brann

1
Nigdy nie piszę programów obsługi wyjątków, zawsze naprawiam problem (z wyjątkiem sytuacji, gdy nie mogę tego zrobić, ponieważ nie mam kontroli nad błędnym kodem). I nigdy nie rzucam wyjątków, chyba że kod, który napisałem, jest przeznaczony dla kogoś innego (np. Biblioteki). Nie pokaż mi sprzeczności?
Brann

1
Zgadzam się z tobą, aby nie wprowadzać w wyjątki wyjątków. Ale z pewnością to, co jest „wyjątkowe”, jest kwestią definicji. String.parseDouble zgłasza wyjątek, jeśli nie może dostarczyć użytecznego wyniku, na przykład jest w porządku. Co jeszcze powinien zrobić? Zwrócić NaN? Co ze sprzętem innym niż IEEE?
Ingo

1

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 trybloku 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 znalezienia trybloku 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:

        adr lr,next_instr
        b subroutine
        b handle_exception
    next_instr:
    

Aby wyjść normalnie, podprogram po prostu zrobiłby bx lrlub pop {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.

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.