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ą Controller
decydują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 void
funkcjami. 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.
źródło
Odpowiedzi:
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:
Przeczytaj dyskusję na wiki Warda, aby uzyskać więcej szczegółowych informacji.
Zobacz także duplikat tego pytania tutaj
źródło
if
aforeach
także wyrafinowaneGOTO
s. 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.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ć.
źródło
const myvar = theFunction
ponieważ 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ć.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
GOTO
nawet 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ą
GOTO
lub 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).źródło
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
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 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:
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 flag wewnętrznych. 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 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
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
null
nie 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
/rescue
operatorów. Ale możesz użyćthrow
/catch
do 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 naint.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
throw
icatch
są to słowa kluczowe związane z wyjątkami w wielu innych językach.źródło
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:
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
if
stwierdzenia, 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.
źródło
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:
źródło
return
zamiastgoto
.try:
bloki do 1 lub 2 wierszy, proszę, nigdytry: # Do lots of stuff
.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.
źródło
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
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.
źródło