Czy wyjątki jako kontrola są uważane za poważny antypattern? Jeśli tak, dlaczego?

129

W późnych latach 90-tych całkiem sporo pracowałem z bazą kodu, która używała wyjątków jako kontroli przepływu. Zaimplementowano maszynę skończoną do sterowania aplikacjami telefonii. Ostatnio przypominają mi się te dni, ponieważ tworzyłem aplikacje internetowe MVC.

Oba mają Controllerdecydujące miejsce, w którym należy iść dalej, i dostarczają dane do logiki docelowej. Działania użytkownika z dziedziny starego telefonu, takie jak dźwięki DTMF, stały się parametrami metod działania, ale zamiast zwracać coś takiego ViewResult, rzuciły StateTransitionException.

Myślę, że główną różnicą było to, że metody działania były voidfunkcjami. Nie pamiętam wszystkich rzeczy, które zrobiłem z tym faktem, ale wahałem się, czy nie pójść drogą pamiętania dużo, ponieważ od czasu tej pracy, jak 15 lat temu, nigdy nie widziałem tego w kodzie produkcyjnym w żadnej innej pracy . Zakładałem, że to znak, że jest to tak zwany anty-wzór.

Czy tak jest, a jeśli tak, to dlaczego?

Aktualizacja: kiedy zadałem pytanie, miałem już na myśli odpowiedź @ MasonWheeler, więc wybrałem odpowiedź, która najbardziej poszerzyła moją wiedzę. Myślę, że jego odpowiedź również brzmi solidnie.

Aaron Anodide
źródło
2
Powiązane pytanie: programmers.stackexchange.com/questions/107723
Eric King
25
Nie. W Pythonie użycie wyjątków jako przepływu sterowania jest uważane za „pytoniczne”.
user16764
4
Gdybym miał zrobić coś takiego, ja Java, z pewnością nie rzuciłbym na to wyjątku. Wyprowadziłbym się z jakiejś hierarchii nie wyjątkowej, nienormalnej, Throwable.
Thomas Eding,
2
Aby dodać do istniejących odpowiedzi, oto krótka wskazówka, która mi dobrze służyła: - Nigdy nie używaj wyjątków dla „szczęśliwej ścieżki”. Szczęśliwą ścieżką może być zarówno (dla sieci) całe żądanie lub po prostu jeden obiekt / metoda. Oczywiście obowiązują wszystkie inne rozsądne zasady :)
Houen,
8
Czy wyjątki nie zawsze kontrolują przepływ aplikacji?

Odpowiedzi:

109

Szczegółowa dyskusja na ten temat znajduje się na Wiki Warda . Zasadniczo użycie wyjątków w przepływie kontroli jest anty-wzorcem, z wieloma znaczącymi sytuacjami - i kaszlem wyjątkami kaszlu specyficznymi dla języka (patrz na przykład Python ) .

Krótko podsumowując, dlaczego ogólnie jest to anty-wzór:

  • Wyjątki stanowią w istocie wyrafinowane wypowiedzi GOTO
  • Programowanie z wyjątkami prowadzi zatem do trudniejszego do odczytania i zrozumienia kodu
  • Większość języków ma istniejące struktury kontrolne zaprojektowane w celu rozwiązywania problemów bez użycia wyjątków
  • Argumenty dotyczące wydajności wydają się być dyskusyjne w przypadku nowoczesnych kompilatorów, które mają tendencję do optymalizacji przy założeniu, że wyjątki nie są wykorzystywane do sterowania przepływem.

Przeczytaj dyskusję na wiki Warda, aby uzyskać więcej szczegółowych informacji.


Zobacz także duplikat tego pytania tutaj

Blueberryfields
źródło
18
Twoja odpowiedź brzmi, jakby wyjątki były złe we wszystkich okolicznościach, podczas gdy pytanie koncentruje się na wyjątkach jako kontroli przepływu.
whatsisname
14
@MasonWheeler Różnica polega na tym, że pętle for / while zawierają wyraźnie zmiany kontroli przepływu i ułatwiają odczytanie kodu. Jeśli w kodzie znajduje się instrukcja for, nie musisz próbować dowiedzieć się, który plik zawiera koniec pętli. Goto nie są złe, ponieważ jakiś bóg powiedział, że są, są złe, ponieważ są trudniejsze do naśladowania niż konstrukcje zapętlone. Wyjątki są podobne, nie niemożliwe, ale na tyle trudne, że mogą wprowadzać w błąd.
Bill K
24
@BillK, a następnie argumentuj i nie przesadzaj z uproszczonymi stwierdzeniami o tym, jak wyjątki są gotos.
Winston Ewert
6
Ok, ale poważnie, co jest z błędami zakopywania aplikacji i serwerów przez serwer przy pustych instrukcjach catch w JavaScript? To paskudne zjawisko, które kosztowało mnie dużo czasu i nie wiem, jak zapytać bez rantingu. Błędy są twoim przyjacielem.
Erik Reppen,
12
@mattnz: ifa foreachtakże wyrafinowane GOTOs. Szczerze mówiąc, myślę, że porównanie z goto nie jest pomocne; to prawie jak uwłaczające określenie. Korzystanie z GOTO nie jest z natury złe - ma prawdziwe problemy i wyjątki mogą się z nimi dzielić, ale nie mogą. Lepiej byłoby usłyszeć te problemy.
Eamon Nerbonne,
110

Przypadek użycia, dla którego zaprojektowano wyjątki, to: „Właśnie spotkałem się z sytuacją, z którą nie mogę sobie teraz poradzić, ponieważ nie mam wystarczającego kontekstu, aby sobie z tym poradzić, ale procedurę, która do mnie zadzwoniła (lub coś dalej stos) powinien wiedzieć, jak sobie z tym poradzić. ”

Drugi przypadek użycia to „Właśnie napotkałem poważny błąd, a teraz wyjście z tego przepływu kontroli, aby zapobiec uszkodzeniu danych lub innym uszkodzeniom, jest ważniejsze niż próba kontynuacji”.

Jeśli nie używasz wyjątków z jednego z tych dwóch powodów, prawdopodobnie jest lepszy sposób, aby to zrobić.

Mason Wheeler
źródło
18
To jednak nie odpowiada na pytanie. To, do czego zostały zaprojektowane, nie ma znaczenia; jedyne, co jest istotne, to dlaczego wykorzystywanie ich do sterowania przepływem jest złe, a tego tematu nie dotknąłeś. Na przykład szablony C ++ zostały zaprojektowane z jednej strony, ale doskonale nadają się do użycia w metaprogramowaniu, czego projektanci nigdy się nie spodziewali.
Thomas Bonini,
6
@Krelp: Projektanci nigdy nie przewidywali wielu rzeczy, takich jak przypadkowy system szablonów Turinga! Szablony C ++ nie są dobrym przykładem do zastosowania tutaj.
Mason Wheeler,
15
@Krelp - szablony C ++ NIE są „idealnie w porządku” do metaprogramowania. Są koszmarem, dopóki ich nie poprawisz, a następnie mają tendencję do kodowania tylko do zapisu, jeśli nie jesteś geniuszem szablonów. Możesz wybrać lepszy przykład.
Michael Kohne,
Wyjątki wpływają w szczególności na kompozycję funkcji i ogólnie zwięzłość kodu. Np. Kiedy byłem niedoświadczony, użyłem wyjątku w projekcie, co spowodowało kłopoty przez długi czas, ponieważ 1) ludzie muszą pamiętać, aby go złapać, 2) nie możesz pisać, const myvar = theFunctionponieważ myvar trzeba utworzyć z try-catch, dlatego nie jest już stały. To nie znaczy, że nie używam go w C #, ponieważ jest to główny nurt z jakiegokolwiek powodu, ale staram się je jednak zmniejszyć.
Cześć Anioł
2
@AndreasBonini Co są zaprojektowane / przeznaczone do spraw, ponieważ często skutkuje to decyzjami implementacyjnymi kompilatora / frameworka / środowiska wykonawczego zgodnymi z projektem Na przykład zgłoszenie wyjątku może być o wiele „droższe” niż zwykły zwrot, ponieważ zbiera informacje mające pomóc komuś w debugowaniu kodu, takie jak ślady stosu itp.
binki
29

Wyjątki są tak silne, jak Kontynuacje i GOTO. Są to uniwersalne konstrukcje sterowania przepływem.

W niektórych językach są one jedyną uniwersalną konstrukcją przepływu sterowania. Na przykład JavaScript nie ma ani kontynuacji, ani GOTOnawet nie ma odpowiednich wywołań ogona. Tak więc, jeśli chcesz stosować wyrafinowane przepływ sterowania w JavaScript, musisz mieć używać wyjątków.

Projekt Microsoft Volta był (obecnie zaniechany) projektem badawczym mającym na celu skompilowanie dowolnego kodu .NET do JavaScript. .NET ma wyjątki, których semantyka nie jest dokładnie odwzorowana na JavaScript, ale co ważniejsze, ma Wątki i musisz jakoś odwzorować je na JavaScript. Volta zrobiło to, wdrażając Volta Continuations przy użyciu wyjątków JavaScript, a następnie implementując wszystkie konstrukcje przepływu sterowania .NET pod względem Volta Continuations. Oni mieli używać wyjątków przepływ sterowania, ponieważ nie ma innego przepływu sterowania zbudować wystarczająco silny.

Wspomniałeś o State Machines. SM są trywialne w implementacji z odpowiednimi wywołaniami ogona: każdy stan jest podprogramem, każde przejście stanu jest wywołaniem podprogramu. SM można również łatwo wdrożyć za pomocą GOTOlub Coroutines lub Continuations. Jednak Java nie posiada żadnej z tych czterech, ale nie ma wyjątków. Jest więc całkowicie dopuszczalne stosowanie ich jako przepływu kontrolnego. (Cóż, właściwie właściwym wyborem byłoby prawdopodobnie użycie języka z odpowiednią konstrukcją przepływu sterowania, ale czasami możesz utknąć w Javie).

Jörg W Mittag
źródło
7
Gdybyśmy tylko mieli odpowiednią rekurencję wywołania ogona, być może moglibyśmy zdominować tworzenie stron internetowych po stronie klienta za pomocą JavaScript, a następnie rozprzestrzenić się na serwer i urządzenia mobilne jako najlepsze rozwiązanie na platformę, takie jak wildfire. Niestety, ale tak nie było. Cholera, nasze niewystarczające pierwszorzędne funkcje, zamknięcia i paradygmat sterowany zdarzeniami. To, czego naprawdę potrzebowaliśmy, było prawdziwe.
Erik Reppen
20
@ErikReppen: Zdaję sobie sprawę, że jesteś po prostu sarkastyczny, ale. . . I naprawdę nie sądzę, fakt, że „zdominowały stronie klienta internetowej rozwoju z Javascript” nie ma nic wspólnego z funkcji językowej. Ma monopol na tym rynku, więc udało mu się uciec od wielu problemów, których nie można odpisać sarkazmem.
ruakh
1
Rekurencja ogona byłaby miłym bonusem (przestarzałe funkcje funkcji, aby mieć ją w przyszłości), ale tak, powiedziałbym, że wygrała z VB, Flash, apletami itp. Z powodów związanych z funkcjami. Gdyby nie zmniejszyło złożoności i nie znormalizowało tak łatwo, jak mogłoby, miałoby w pewnym momencie prawdziwą konkurencję. Obecnie korzystam z Node.js, aby obsłużyć przepisywanie 21 plików konfiguracyjnych dla ohydnego stosu Java C # + i wiem, że nie jestem jedynym, który to robi. Jest bardzo dobry w tym, w czym jest dobry.
Erik Reppen
1
Wiele języków nie ma gotos (Goto uważane za szkodliwe!), Coroutines ani kontynuacji i implementuje doskonale poprawną kontrolę przepływu za pomocą ifs, whiles i wywołań funkcji (lub gosubs!). Większość z nich można w rzeczywistości wykorzystać do wzajemnego naśladowania, podobnie jak kontynuacja może być wykorzystana do przedstawienia wszystkich powyższych. (Na przykład możesz użyć chwili, aby wykonać if, nawet jeśli jest to śmierdzący sposób kodowania). Dlatego żadne wyjątki NIE są konieczne do przeprowadzenia zaawansowanej kontroli przepływu.
Shayne
2
Cóż, może masz rację, jeśli. „For” w swojej formie w stylu C może jednak być użyte jako niezręcznie określone, a chwilę potem może być użyte w połączeniu z if do implementacji maszyny o stanie skończonym, która może następnie emulować wszystkie inne formy kontroli przepływu. Znów śmierdzący sposób kodowania, ale tak. I znowu, musiałem uważać się za szkodliwy. (I kłóciłbym się, tak samo z wykorzystaniem wyjątków w wyjątkowych okolicznościach). Pamiętaj, że istnieją całkowicie poprawne i bardzo wydajne języki, które nie zapewniają ani goto, ani wyjątków i działają dobrze.
Shayne,
19

Jak inni wspominali licznie ( np. W tym pytaniu o przepełnienie stosu ), 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ątek jest „właściwym narzędziem” - 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 w jOOQ (biblioteka abstrakcji SQL dla Javy) (oświadczenie: pracuję dla dostawcy), takie wyjątki są czasami wykorzystywane 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 flag wewnętrznych. 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 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.

Lukas Eder
źródło
11

Używanie wyjątków dla przepływu kontrolnego jest ogólnie uważane za anty-wzorzec, ale są wyjątki (nie ma sensu).

Tysiąc razy powiedziano, że wyjątki dotyczą wyjątkowych warunków. Zerwane połączenie z bazą danych jest wyjątkowym warunkiem. Użytkownik wpisujący litery w polu wprowadzania, które powinno dopuszczać tylko cyfry, nie jest .

Błąd w twoim oprogramowaniu, który powoduje wywołanie funkcji z niedozwolonymi argumentami, np. Gdy nullnie pozwala, jest wyjątkowym warunkiem.

Stosując wyjątki od czegoś, co nie jest wyjątkowe, używasz nieodpowiednich abstrakcji dla problemu, który próbujesz rozwiązać.

Ale może również wystąpić kara wydajności. Niektóre języki mają mniej lub bardziej wydajną implementację obsługi wyjątków, więc jeśli wybrany język nie ma wydajnej obsługi wyjątków, może być bardzo kosztowny, pod względem wydajności *.

Ale inne języki, na przykład Ruby, mają składnię podobną do wyjątku dla przepływu sterowania. Wyjątkowe sytuacje są obsługiwane przez raise/ rescueoperatorów. Ale możesz użyć throw/ catchdo konstrukcji przepływów kontrolnych podobnych do wyjątków **.

Tak więc, chociaż generalnie nie stosuje się wyjątków w przepływie sterowania, wybrany język może mieć inne idiomy.

* Na przykład kosztowne wykorzystanie wyjątków: raz byłem ustawiony, aby zoptymalizować słabo działającą aplikację ASP.NET Web Form. Okazało się, że rendering dużego stołu wymaga int.Parse()ok. tysiąc pustych ciągów na przeciętnej stronie, co daje ok. rozpatrzono tysiąc wyjątków. Zamieniając kod na int.TryParse(), ogoliłem się na sekundę! Do każdego żądania jednej strony!

** To może być bardzo mylące dla programisty Ruby pochodzące z innych języków, jak zarówno throwi catchsą to słowa kluczowe związane z wyjątkami w wielu innych językach.

Pete
źródło
1
+1 dla „Stosując wyjątki od czegoś, co nie jest wyjątkowe, używasz nieodpowiednich abstrakcji dla problemu, który próbujesz rozwiązać”.
Giorgio
8

Całkowicie możliwe jest radzenie sobie z błędami bez użycia wyjątków. Niektóre języki, w szczególności C, nawet nie mają wyjątków, a ludzie nadal potrafią tworzyć z nim dość złożone aplikacje. Wyjątki są przydatne, ponieważ umożliwiają zwięzłe określenie dwóch zasadniczo niezależnych przepływów sterowania w tym samym kodzie: jeden, jeśli wystąpi błąd, a drugi, jeśli nie. Bez nich skończysz z kodem w całym miejscu, które wygląda tak:

status = getValue(&inout);
if (status < 0)
{
    logError("message");
    return status;
}

doSomething(*inout);

Lub odpowiednik w twoim języku, np. Zwracanie krotki z jedną wartością jako statusem błędu itp. Często ludzie, którzy wskazują, jak „drogie” jest obsługiwanie wyjątków, zaniedbują wszystkie dodatkowe ifstwierdzenia, takie jak wyżej, które należy dodać, jeśli nie „ t stosować wyjątki.

Chociaż ten wzorzec zdarza się najczęściej podczas obsługi błędów lub innych „wyjątkowych warunków”, moim zdaniem, jeśli zaczniesz widzieć podobny kod w innych okolicznościach, masz całkiem niezły argument za zastosowaniem wyjątków. W zależności od sytuacji i implementacji widzę wyjątki używane poprawnie w maszynie stanów, ponieważ masz dwa ortogonalne przepływy kontrolne: jeden, który zmienia stan, a drugi dla zdarzeń występujących w stanach.

Takie sytuacje są jednak rzadkie i jeśli zamierzasz zrobić wyjątek (gra słów zamierzona) w regułę, lepiej przygotuj się na pokazanie jej wyższości nad innymi rozwiązaniami. Odchylenie bez takiego uzasadnienia słusznie nazywa się anty-wzorem.

Karl Bielefeldt
źródło
2
Dopóki te stwierdzenia zawodzą tylko w „wyjątkowych” okolicznościach, logika przewidywania gałęzi nowoczesnych procesorów sprawia, że ​​ich koszt jest znikomy. Jest to jedno miejsce, w którym makra mogą faktycznie pomóc, pod warunkiem, że jesteś ostrożny i nie próbujesz w nich robić zbyt wiele.
James
5

W Pythonie wyjątki są używane do zakończenia generatora i iteracji. Python ma bardzo wydajne bloki try / oprócz, ale w rzeczywistości zgłoszenie wyjątku ma pewne narzuty.

Z powodu braku wielopoziomowych przerw lub instrukcji goto w Pythonie czasami używałem wyjątków:

class GOTO(Exception):
  pass

try:
  # Do lots of stuff
  # in here with multiple exit points
  # each exit point does a "raise GOTO()"
except GOTO:
  pass
except Exception as e:
  #display error
gahooa
źródło
6
Jeśli musisz zakończyć obliczenia gdzieś w głęboko zagnieżdżonej instrukcji i przejść do jakiegoś wspólnego kodu kontynuacji, ta konkretna ścieżka wykonania może być prawdopodobnie uwzględniona jako funkcja, returnzamiast goto.
9000
3
@ 9000 Chciałem skomentować dokładnie to samo ... Trzymaj try:bloki do 1 lub 2 wierszy, proszę, nigdy try: # Do lots of stuff.
wim
1
@ 9000, w niektórych przypadkach, oczywiście. Ale potem tracisz dostęp do zmiennych lokalnych i przenosisz swój kod w jeszcze inne miejsce, gdy proces jest spójny, liniowy.
gahooa
4
@gahooa: Kiedyś myślałem tak jak ty. Był to znak złej struktury mojego kodu. Kiedy się nad tym zastanowiłem, zauważyłem, że lokalne konteksty można rozplątywać, a cały bałagan przekształcić w krótkie funkcje z kilkoma parametrami, kilkoma liniami kodu i bardzo precyzyjnym znaczeniem. Nigdy nie oglądałem się za siebie.
9000
4

Naszkicujmy takie użycie wyjątku:

Algorytm wyszukuje rekurencyjnie, aż coś zostanie znalezione. Wracając z rekurencji, należy sprawdzić, czy wynik został znaleziony, i powrócić, w przeciwnym razie kontynuować. I to wielokrotnie wraca z pewnej głębokości rekurencji.

Poza tym potrzebna jest dodatkowa wartość logiczna found(do spakowania w klasie, gdzie w przeciwnym razie może zostać zwrócona tylko liczba całkowita), a dla głębokości rekurencji dzieje się to samo.

Takie odwijanie stosu wywołań jest właśnie tym wyjątkiem. Wydaje mi się więc, że nie jest to podobny do goto, bardziej bezpośredni i odpowiedni sposób kodowania. Niepotrzebne, rzadkie użycie, może zły styl, ale do rzeczy. Porównywalny z operacją cięcia Prolog.

Joop Eggen
źródło
Hmm, czy głosowanie w dół za rzeczywiście przeciwną pozycją, czy za niewystarczającą lub krótką argumentację? Jestem naprawdę ciekawa.
Joop Eggen,
Mam dokładnie taką sytuację, miałem nadzieję, że zobaczysz, co myślisz o moim następnym pytaniu? Rekurencyjnie używa wyjątków, aby zgromadzić przyczynę (komunikat) wyjątku najwyższego poziomu codereview.stackexchange.com/questions/107862/…
CL22,
@ Jodes Właśnie przeczytałem twoją interesującą technikę, ale na razie jestem dość zajęty. Mam nadzieję, że ktoś inny rzuci jej / jej światło na ten problem.
Joop Eggen,
4

Programowanie dotyczy pracy

Myślę, że najłatwiejszym sposobem na to jest zrozumienie postępu, jaki OOP poczyniło przez lata. Wszystko, co zostało zrobione w OOP (i większość paradygmatów programowania, jeśli o to chodzi) jest modelowane wokół konieczności wykonania pracy .

Za każdym razem, gdy wywoływana jest metoda, dzwoniący mówi: „Nie wiem, jak wykonać tę pracę, ale wiesz, jak to zrobić, więc zrób to dla mnie”.

Stwarza to trudność: co się dzieje, gdy wywoływana metoda ogólnie wie, jak wykonać pracę, ale nie zawsze? Potrzebowaliśmy sposobu komunikowania się „Chciałem ci pomóc, naprawdę to zrobiłem, ale po prostu nie mogę tego zrobić”.

Wczesną metodyką informowania o tym było po prostu zwrócenie wartości „śmieci”. Być może oczekujesz dodatniej liczby całkowitej, więc wywoływana metoda zwraca liczbę ujemną. Innym sposobem na osiągnięcie tego było ustawienie gdzieś wartości błędu. Niestety, oba sposoby doprowadziły do tego, że sprawdziłem kod koszerności „pozwól mi sprawdzić” tutaj, aby upewnić się, że wszystko jest koszerne . Gdy sprawy stają się coraz bardziej skomplikowane, ten system się rozpada.

Wyjątkowa analogia

Powiedzmy, że masz cieśli, hydraulika i elektryka. Chcesz hydraulika naprawić zlew, więc on na to patrzy. Nie jest to zbyt przydatne, jeśli mówi tylko tobie: „Przepraszam, nie mogę tego naprawić. Jest zepsuty”. Do diabła, jest jeszcze gorzej, jeśli spojrzy, odejdzie i wyśle ​​ci list z informacją, że nie może tego naprawić. Teraz musisz sprawdzić swoją pocztę, zanim nawet zorientujesz się, że nie zrobił tego, co chciałeś.

Wolałbyś, żeby powiedział ci: „Słuchaj, nie mogłem tego naprawić, bo wygląda na to, że Twoja pompa nie działa”.

Dzięki tym informacjom możesz stwierdzić, że chcesz, aby elektryk spojrzał na problem. Być może elektryk znajdzie coś związanego z stolarzem i będziesz musiał go naprawić.

Cholera, możesz nawet nie wiedzieć, że potrzebujesz elektryka, możesz nie wiedzieć , kogo potrzebujesz. Jesteś tylko średnim kierownictwem w firmie zajmującej się naprawą domów, a twoja uwaga skupia się na hydraulice. Więc mówisz, że jesteś szefem o problemie, a potem on każe elektrykowi go naprawić.

Oto co modelują wyjątki: złożone tryby awarii w odsprzężony sposób. Hydraulik nie musi wiedzieć o elektryku - nie musi nawet wiedzieć, że ktoś w łańcuchu może rozwiązać problem. Po prostu informuje o napotkanym problemie.

Więc ... anty-wzór?

Ok, więc zrozumienie punktu wyjątków jest pierwszym krokiem. Następnym jest zrozumienie, czym jest anty-wzór.

Aby zakwalifikować się jako anty-wzór, musi

  • Rozwiąż problem
  • mieć zdecydowanie negatywne konsekwencje

Pierwszy punkt jest łatwy do spełnienia - system działał, prawda?

Drugi punkt jest trudniejszy. Podstawowym powodem stosowania wyjątków jako normalnego przepływu sterowania jest zła sytuacja, ponieważ nie jest to ich celem. Każdy element funkcjonalności w programie powinien mieć stosunkowo jasny cel, a kooperacja z tym celem prowadzi do niepotrzebnego zamieszania.

Ale to nie jest ostateczna szkoda. To kiepski sposób na robienie rzeczy i dziwne, ale anty-wzór? Nie. Po prostu ... dziwne.

MirroredFate
źródło
Jest jedna zła konsekwencja, przynajmniej w językach zapewniających śledzenie pełnego stosu. Ponieważ kod jest mocno zoptymalizowany z dużą ilością wstawiania, rzeczywisty ślad stosu i ten, który deweloper chce zobaczyć, bardzo się różnią, dlatego generowanie śladu stosu jest kosztowne. Nadużywanie wyjątków jest bardzo niekorzystne dla wydajności w takich językach (Java, C #).
maaartinus
„To zły sposób na robienie rzeczy” - czy nie powinno to wystarczyć do zaklasyfikowania go jako anty-wzorca?
Maybe_Factor
@Maybe_Factor Zgodnie z definicją wzorca ant, nie.
MirroredFate