Istnieje wiele wymagań, aby system mógł poprawnie przekazywać i obsługiwać wyjątki. Istnieje również wiele opcji wyboru języka do wdrożenia tej koncepcji.
Wymagania dotyczące wyjątków (w określonej kolejności):
Dokumentacja : język powinien umożliwiać dokumentowanie wyjątków, które API może zgłaszać. Idealnie, ten nośnik dokumentacji powinien nadawać się do użytku maszynowego, aby umożliwić kompilatorom i IDE wsparcie programistom.
Przekazywanie wyjątkowych sytuacji : ta jest oczywista, aby umożliwić funkcji przekazywanie sytuacji, które uniemożliwiają wywołanej funkcji wykonanie oczekiwanej akcji. Moim zdaniem istnieją trzy duże kategorie takich sytuacji:
2.1 Błędy w kodzie, które powodują, że niektóre dane są nieprawidłowe.
2.2 Problemy z konfiguracją lub innymi zasobami zewnętrznymi.
2.3 Zasoby, które są z natury zawodne (sieć, systemy plików, bazy danych, użytkownicy końcowi itp.). Są to trochę zasadne przypadki, ponieważ ich niewiarygodna natura powinna sprawić, że spodziewamy się ich sporadycznych niepowodzeń. Czy w takim przypadku sytuacje te należy uznać za wyjątkowe?
Podaj wystarczającą ilość informacji, aby kod mógł go obsłużyć : wyjątki powinny zapewniać odbiorcy wystarczające informacje, aby mógł zareagować i ewentualnie poradzić sobie z sytuacją. informacje powinny również być wystarczające, aby po zalogowaniu wyjątki zapewniały programiście wystarczający kontekst do zidentyfikowania i wyodrębnienia złych stwierdzeń i zapewnienia rozwiązania.
Zapewnij programistom pewność co do aktualnego stanu stanu wykonania jego kodu : Możliwości obsługi wyjątków w systemie oprogramowania powinny być na tyle obecne, aby zapewnić niezbędne zabezpieczenia, jednocześnie nie wchodząc w drogę programistom, aby mógł skupić się na zadaniu dłoń.
Aby uwzględnić te, w różnych językach wdrożono następujące metody:
Sprawdzone wyjątki Zapewniają świetny sposób na dokumentowanie wyjątków, a teoretycznie, jeśli są poprawnie wdrożone, powinny zapewnić wystarczającą pewność, że wszystko jest w porządku. Jednak koszt jest taki, że wielu uważa, że bardziej produktywne jest po prostu ominięcie albo przez połknięcie wyjątków, albo ponowne ich odrzucenie jako niesprawdzone wyjątki. W przypadku niewłaściwie sprawdzonego wyjątku traci on całą swoją użyteczność. Sprawdzone wyjątki utrudniają utworzenie stabilnego w czasie interfejsu API. Implementacje ogólnego systemu w ramach konkretnej domeny przyniosą mnóstwo wyjątkowej sytuacji, która byłaby trudna do utrzymania przy użyciu wyłącznie sprawdzonych wyjątków.
Niesprawdzone wyjątki - znacznie bardziej wszechstronne niż sprawdzone wyjątki, nie udokumentują prawidłowo możliwych wyjątkowych sytuacji w danej implementacji. Opierają się na dokumentacji ad hoc, jeśli w ogóle. Stwarza to sytuacje, w których zawodna natura medium jest maskowana przez interfejs API, który daje wrażenie niezawodności. Również po rzuceniu wyjątki te tracą znaczenie, gdy wracają w górę przez warstwy abstrakcji. Ponieważ są słabo udokumentowane, programista nie może specjalnie zaatakować ich i często musi rzucić znacznie szerszą sieć niż jest to konieczne, aby zapewnić, że systemy wtórne, w przypadku awarii, nie spowodują awarii całego systemu. Co prowadzi nas z powrotem do problemu z połykaniem, sprawdzone wyjątki.
Typy zwrotów wielostanowiskowych W tym przypadku należy polegać na rozłącznym zestawie, krotce lub innej podobnej koncepcji, aby zwrócić oczekiwany wynik lub obiekt reprezentujący wyjątek. Tutaj nie ma rozwijania stosu, bez przecinania kodu, wszystko działa normalnie, ale wartość zwracana musi być sprawdzona pod kątem błędu przed kontynuowaniem. Tak naprawdę nie pracowałem z tym jeszcze, więc nie mogę komentować z doświadczenia. Przyznaję, że rozwiązuje to pewne wyjątki od omijania normalnego przepływu, ale nadal będzie cierpiał z powodu tych samych problemów, co sprawdzone wyjątki, jako męczących i stale „na twarz”.
Pytanie brzmi:
Jakie masz doświadczenie w tej sprawie i co według ciebie jest najlepszym kandydatem do stworzenia dobrego systemu obsługi wyjątków dla danego języka?
EDYCJA: Kilka minut po napisaniu tego pytania natknąłem się na ten post , straszne!
źródło
noexcept
historię w C ++ może dać bardzo dobry wgląd w EH w C # i Javie.)Odpowiedzi:
We wczesnych latach C ++ odkryliśmy, że bez jakiegoś ogólnego programowania, mocno pisane języki były wyjątkowo niewygodne. Odkryliśmy również, że sprawdzone wyjątki i ogólne programowanie nie działały dobrze razem, a sprawdzone wyjątki zostały zasadniczo porzucone.
Typy zwrotów wielosetowych są świetne, ale nie zastępują wyjątków. Bez wyjątków kod jest pełen szumu sprawdzającego błędy.
Innym problemem związanym ze sprawdzonymi wyjątkami jest to, że zmiana wyjątków zgłaszana przez funkcję niskiego poziomu wymusza kaskadę zmian wszystkich dzwoniących i ich dzwoniących itd. Jedynym sposobem, aby temu zapobiec, jest przechwycenie wyjątków zgłaszanych przez niższe poziomy i zawinięcie ich w nowy wyjątek na każdym poziomie kodu. Ponownie otrzymujesz bardzo głośny kod.
źródło
Przez długi czas języki OO stosowanie wyjątków było de facto standardem w komunikowaniu błędów. Ale funkcjonalne języki programowania dają możliwość innego podejścia, np. Przy użyciu monad (których nie używałem) lub bardziej lekkiego „programowania zorientowanego na kolej”, jak opisał Scott Wlaschin.
W tym poście na blogu
W tej prezentacji na NDC 2014
To naprawdę wariant wielostanowiskowego typu wyniku.
Typ wyniku można zadeklarować w ten sposób
Zatem wynikiem funkcji zwracającej ten typ byłby albo a,
Success
alboFail
typ. Nie może to być jedno i drugie.W bardziej imperatywnych językach programowania ten styl może wymagać dużej ilości kodu na stronie wywołującej. Ale programowanie funkcjonalne umożliwia konstruowanie funkcji wiązania lub operatorów w celu powiązania wielu funkcji, dzięki czemu sprawdzanie błędów nie zajmuje połowy kodu. Jako przykład:
updateUser
Wywołania funkcji każdej z tych funkcji z rzędu, a każdy z nich może zakończyć się niepowodzeniem. Jeśli wszystkie się powiedzą, zwracany jest wynik ostatnio wywołanej funkcji. Jeśli jedna z funkcji zawiedzie, wynik tej funkcji będzie wynikiem ogólnejupdateUser
funkcji. Wszystko to jest obsługiwane przez niestandardowy operator >> =.W powyższym przykładzie typami błędów mogą być
Jeśli program wywołujący
updateUser
nie obsługuje jawnie wszystkich możliwych błędów funkcji, kompilator wygeneruje ostrzeżenie. Więc masz wszystko udokumentowane.W Haskell istnieje
do
zapis, dzięki któremu kod może być jeszcze bardziej przejrzysty.źródło
do
notacji Haskella , która czyni kod wynikowy jeszcze czystszym.Railway Oriented Programming
jest dokładnie monadyczne zachowanie.Uważam, że odpowiedź Pete'a jest bardzo dobra i chciałbym dodać trochę przemyśleń i jeden przykład. Bardzo interesującą dyskusję dotyczącą zastosowania wyjątków w porównaniu ze zwracaniem specjalnych wartości błędów można znaleźć w Programowaniu w Standardowym ML, autorstwa Roberta Harpera , na końcu rozdziału 29.3, strona 243, 244.
Problem polega na implementacji funkcji częściowej
f
zwracającej wartość pewnego typut
. Jednym z rozwiązań jest, aby funkcja miała typi wrzuć wyjątek, gdy nie ma możliwego wyniku. Drugim rozwiązaniem jest zaimplementowanie funkcji z typem
i powrócić
SOME v
do sukcesu iNONE
do porażki.Oto tekst z książki, z niewielkimi dostosowaniami dokonanymi przeze mnie, aby tekst był bardziej ogólny (książka odnosi się do konkretnego przykładu). Zmodyfikowany tekst jest pisany kursywą .
Dotyczy to wyboru między wyjątkami a typami zwrotu opcji.
Jeśli chodzi o pomysł, że reprezentowanie błędu w typie zwracanym prowadzi do kontroli błędów rozłożonych na cały kod: nie musi tak być. Oto mały przykład w Haskell, który to ilustruje.
Załóżmy, że chcemy przeanalizować dwie liczby, a następnie podzielić pierwszą przez drugą. Może więc wystąpić błąd podczas analizowania każdej liczby lub dzielenia (dzielenie przez zero). Musimy więc sprawdzić błąd po każdym kroku.
Analiza i podział są wykonywane w
let ...
bloku. Zauważ, że używającMaybe
monady ido
notacji, określa się tylko ścieżkę sukcesu : semantykaMaybe
monady niejawnie propaguje wartość błędu (Nothing
). Bez kosztów ogólnych dla programisty.źródło
Either
typ byłby bardziej odpowiedni. Co robisz, jeśliNothing
tu dotrzesz ? Po prostu pojawia się komunikat „błąd”. Niezbyt przydatne do debugowania.Stałem się wielkim fanem sprawdzonych wyjątków i chciałbym podzielić się ogólną zasadą, kiedy z nich korzystać.
Doszedłem do wniosku, że mój kod ma zasadniczo 2 rodzaje błędów. Występują błędy, które można przetestować przed wykonaniem kodu oraz błędy, których nie można przetestować przed wykonaniem kodu. Prosty przykład błędu, który można przetestować przed wykonaniem kodu w NullPointerException.
Prosty test mógł uniknąć błędu, takiego jak ...
Są chwile w obliczeniach, w których możesz wykonać 1 lub więcej testów przed wykonaniem kodu, aby upewnić się, że jesteś bezpieczny I WCIĄŻ WYSTĄPISZ WYJĄTEK. Na przykład możesz przetestować system plików, aby upewnić się, że na dysku twardym jest wystarczająca ilość miejsca przed zapisaniem danych na dysku. W wieloprocesowym systemie operacyjnym, takim jak te używane obecnie, proces może przetestować miejsce na dysku, a system plików zwróci wartość informującą o wystarczającej ilości miejsca, a następnie przełączenie kontekstu na inny proces może zapisać pozostałe bajty dostępne dla systemu operacyjnego system. Kiedy kontekst systemu operacyjnego powróci do uruchomionego procesu, w którym zapisujesz zawartość na dysk, nastąpi wyjątek po prostu dlatego, że w systemie plików nie ma wystarczającej ilości miejsca na dysku.
Uważam powyższy scenariusz za idealny przypadek sprawdzonego wyjątku. Jest to wyjątek w kodzie, który zmusza cię do radzenia sobie z czymś złym, nawet jeśli Twój kod może być doskonale napisany. Jeśli zdecydujesz się robić złe rzeczy, takie jak „połknąć wyjątek”, jesteś złym programistą. Nawiasem mówiąc, znalazłem przypadki, w których uzasadnione jest połknięcie wyjątku, ale proszę zostawić komentarz w kodzie, dlaczego wyjątek został połknięty. Nie można winić mechanizmu obsługi wyjątków. Zwykle żartuję, że wolę, aby mój rozrusznik serca był napisany w języku, który ma sprawdzone wyjątki.
Są chwile, kiedy trudno jest zdecydować, czy kod jest testowalny, czy nie. Na przykład, jeśli piszesz interpreter, a wyjątek SyntaxExp jest generowany, gdy kod nie działa z jakiegoś powodu składniowego, czy wyjątek SyntaxException powinien być sprawdzonym wyjątkiem, czy (w Javie) wyjątkiem RuntimeException? Chciałbym odpowiedzieć, jeśli interpreter sprawdzi składnię kodu przed wykonaniem kodu, wówczas wyjątek powinien być RuntimeException. Jeśli interpreter po prostu uruchamia kod „na gorąco” i po prostu napotyka błąd składniowy, powiedziałbym, że wyjątkiem powinien być wyjątek sprawdzony.
Przyznaję, że nie zawsze jestem szczęśliwy, że muszę złapać lub rzucić Checked Exception, ponieważ są chwile, w których nie jestem pewien, co robić. Sprawdzone wyjątki są sposobem zmuszenia programisty do uważania na potencjalny problem, który może wystąpić. Jednym z powodów, dla których programuję w Javie jest to, że ma sprawdzone wyjątki.
źródło
Obecnie jestem w trakcie dość dużego projektu / interfejsu API opartego na OOP i użyłem tego układu wyjątków. Ale wszystko tak naprawdę zależy od tego, jak głęboko chcesz przejść z obsługą wyjątków i tym podobnymi.
ExpectedException
-
AuthorisedException
- EmptySetException
- NoRemainingException
- NoRowsException - NotFoundException
- ValidationException
U nieoczekiwany wyjątek
- ConnectivityException
- EnvironmentException
- ProgrammerException
- SQLException
PRZYKŁAD
źródło
NAN
lubNULL
.