Aby obsłużyć kilka możliwych błędów, które nie powinny powstrzymywać wykonywania, mam error
zmienną, którą klienci mogą sprawdzać i używać do zgłaszania wyjątków. Czy to jest anty-wzór? Czy istnieje lepszy sposób, aby sobie z tym poradzić? Na przykład w akcji możesz zobaczyć API mysqli PHP . Załóżmy, że problemy z widocznością (akcesory, zasięg publiczny i prywatny, czy zmienna w klasie czy globalnie?) Są obsługiwane poprawnie.
44
try
/catch
istnieje. Dodatkowo możesz umieścić swójtry
/catch
dużo dalej w stosie w bardziej odpowiednim miejscu do obsługi go (pozwalając na większe rozdzielenie obaw).Odpowiedzi:
Jeśli język z natury obsługuje wyjątki, preferowane jest zgłaszanie wyjątków, a klienci mogą wychwycić wyjątek, jeśli nie chcą, aby spowodował błąd. W rzeczywistości klienci Twojego kodu oczekują wyjątków i napotkają wiele błędów, ponieważ nie będą sprawdzać zwracanych wartości.
Istnieje kilka zalet korzystania z wyjątków, jeśli masz wybór.
Wiadomości
Wyjątki zawierają czytelne dla użytkownika komunikaty o błędach, które mogą być wykorzystane przez programistów do debugowania, a nawet wyświetlane użytkownikom w razie potrzeby. Jeśli konsumujący kod nie jest w stanie obsłużyć wyjątku, zawsze może go zarejestrować, aby programiści mogli przejrzeć dzienniki bez konieczności zatrzymywania się przy każdym innym śledzeniu, aby dowiedzieć się, jaka była wartość zwracana i zmapować ją w tabeli, aby dowiedzieć się, co to jest faktyczny wyjątek.
W przypadku wartości zwracanych nie można łatwo podać dodatkowych informacji. Niektóre języki obsługują wykonywanie wywołań metod w celu otrzymania ostatniego komunikatu o błędzie, więc problem ten został nieco rozwiązany, ale wymaga to od dzwoniącego wykonania dodatkowych wywołań, a czasami wymaga dostępu do „specjalnego obiektu”, który przenosi te informacje.
W przypadku komunikatów o wyjątkach podaję jak najwięcej kontekstów, takich jak:
Porównaj to z kodem zwrotnym -85. Który wolisz?
Stosy połączeń
Wyjątki zwykle zawierają również szczegółowe stosy wywołań, które pomagają debugować kod szybciej i szybciej, a także mogą być rejestrowane przez kod wywołujący, jeśli jest to pożądane. Dzięki temu programiści mogą precyzyjnie wskazać problem na dokładną linię, a zatem są bardzo wydajne. Jeszcze raz porównaj to do pliku dziennika ze zwracanymi wartościami (np. -85, 101, 0 itd.). Który wolisz?
Podejście szybko tendencyjne
Jeśli metoda zostanie wywołana w miejscu, które się nie powiedzie, wygeneruje wyjątek. Kod wywołujący musi albo wyraźnie wyłączyć wyjątek, albo zawiedzie. Przekonałem się, że jest to naprawdę niesamowite, ponieważ podczas programowania i testowania (a nawet w produkcji) kod szybko zawodzi, co zmusza programistów do jego naprawy. W przypadku wartości zwracanych, jeśli brakuje sprawdzania wartości zwracanej, błąd jest cicho ignorowany, a błąd pojawia się gdzieś nieoczekiwanie, zwykle z dużo wyższym kosztem debugowania i naprawy.
Wyjątki dotyczące zawijania i rozpakowywania
Wyjątki mogą być zawinięte w inne wyjątki, a następnie rozpakowane w razie potrzeby. Na przykład Twój kod może wyrzucić,
ArgumentNullException
który kod wywołujący może zawinąć do wewnątrz,UnableToRetrievePolicyException
ponieważ operacja nie powiodła się w kodzie wywołującym. Podczas gdy użytkownikowi może zostać wyświetlony komunikat podobny do powyższego przykładu, niektóre kody diagnostyczne mogą rozpakować wyjątek i stwierdzić, że toArgumentNullException
on spowodował problem, co oznacza, że jest to błąd kodowania w kodzie klienta. Może to następnie uruchomić alert, aby programista mógł naprawić kod. Takie zaawansowane scenariusze nie są łatwe do wdrożenia z wartościami zwracanymi.Prostota kodu
Ten jest nieco trudniejszy do wyjaśnienia, ale nauczyłem się dzięki temu kodowaniu zarówno z wartościami zwracanymi, jak i wyjątkami. Kod, który został napisany przy użyciu wartości zwracanych, zwykle wykonuje wywołanie, a następnie przeprowadza serię kontroli wartości zwracanej. W niektórych przypadkach wywołałoby inną metodę, a teraz będzie miała kolejną serię kontroli zwracanych wartości z tej metody. Z wyjątkami obsługa wyjątków jest znacznie prostsza w większości, jeśli nie we wszystkich przypadkach. Masz bloki try / catch / wreszcie, a środowisko wykonawcze stara się wykonać kod w blokach ostatecznie w celu wyczyszczenia. Nawet zagnieżdżone bloki try / catch / wreszcie są stosunkowo łatwiejsze do przejścia i utrzymania niż zagnieżdżone if / else i powiązane wartości zwracane z wielu metod.
Wniosek
Jeśli platforma, z której korzystasz, obsługuje wyjątki (zwłaszcza takie jak Java lub .NET), to zdecydowanie powinieneś założyć, że nie ma innego wyjścia, niż zgłaszanie wyjątków, ponieważ platformy te mają wytyczne do zgłaszania wyjątków, a Twoi klienci będą oczekiwać więc. Gdybym korzystał z twojej biblioteki, nie zawracam sobie głowy sprawdzaniem zwracanych wartości, ponieważ spodziewam się wyjątków, tak wygląda świat na tych platformach.
Gdyby jednak był to C ++, ustalenie tego byłoby nieco trudniejsze, ponieważ istnieje duża baza kodów z kodami zwrotnymi, a duża liczba programistów jest dostosowywana do zwracania wartości w przeciwieństwie do wyjątków (np. Windows jest bogaty w HRESULT) . Co więcej, w wielu aplikacjach może to być również problem z wydajnością (lub przynajmniej postrzegany jako taki).
źródło
ErrorStateReturnVariable
superklasę, a jedną z jej właściwości jestInnerErrorState
(co jest instancjąErrorStateReturnVariable
), którą implementujące podklasy mogą ustawić, aby wyświetlać łańcuch błędów ... och, czekaj. : pZmienne błędów są reliktem z języków takich jak C, gdzie wyjątki nie były dostępne. Dzisiaj powinieneś ich unikać, chyba że piszesz bibliotekę, która jest potencjalnie używana z programu C (lub podobnego języka bez obsługi wyjątków).
Oczywiście, jeśli masz rodzaj błędu, który można lepiej sklasyfikować jako „ostrzeżenie” (= Twoja biblioteka może dostarczyć poprawny wynik, a osoba dzwoniąca może zignorować ostrzeżenie, jeśli uzna, że nie jest to ważne), wówczas wskaźnik stanu w formie zmiennej może mieć sens nawet w językach z wyjątkami. Ale strzeż się. Dzwoniący z biblioteki zwykle ignorują takie ostrzeżenia, nawet jeśli nie powinni. Pomyśl dwa razy, zanim wprowadzisz taki konstrukt do swojej biblioteki.
źródło
Istnieje wiele sposobów sygnalizowania błędu:
Problemem zmiennej błędu jest to, że łatwo zapomnieć o sprawdzeniu.
Problem wyjątków polega na tym, że tworzy ukryte ścieżki wykonań i chociaż wypróbowanie / złapanie jest łatwe do napisania, zapewnienie poprawnego odzyskania klauzuli catch jest naprawdę trudne do ściągnięcia (brak wsparcia z systemów typu / kompilatorów).
Problem programów obsługi warunków polega na tym, że nie komponują się one dobrze: jeśli masz dynamiczne wykonywanie kodu (funkcje wirtualne), nie można przewidzieć, które warunki należy obsłużyć. Co więcej, jeśli ten sam warunek można podnieść w kilku miejscach, nie można powiedzieć, że za każdym razem można zastosować jednolite rozwiązanie i szybko robi się bałagan.
Zwroty polimorficzne (
Either a b
w języku Haskell) to moje dotychczasowe ulubione rozwiązanie:Jedynym problemem jest to, że mogą potencjalnie prowadzić do nadmiernej kontroli; języki, które ich używają, mają idiomy do łączenia wywołań funkcji, które ich używają, ale wciąż może wymagać nieco więcej pisania / bałaganu. W Haskell byłyby to monady ; jest to jednak znacznie bardziej przerażające, niż się wydaje, patrz Programowanie zorientowane na kolej .
źródło
Myślę, że to okropne. Obecnie refaktoryzuję aplikację Java, która używa wartości zwracanych zamiast wyjątków. Chociaż wcale nie pracujesz z Javą, myślę, że tak jest.
Otrzymujesz kod taki jak ten:
Albo to:
Wolałbym, żeby działania same generowały wyjątki, więc kończysz na czymś takim:
Możesz zawinąć to w try-catch i pobrać wiadomość z wyjątku lub możesz zignorować wyjątek, na przykład, gdy usuwasz coś, co już może zniknąć. Zachowuje również ślad stosu, jeśli taki masz. Same metody również stają się łatwiejsze. Zamiast samodzielnie zajmować się wyjątkami, po prostu rzucają to, co poszło nie tak.
Aktualny (okropny) kod:
Nowy i ulepszony:
Śledzenie ścieżki zostaje zachowane, a komunikat jest dostępny w wyjątku, a nie bezużyteczne „Coś poszło nie tak!”
Możesz oczywiście dostarczać lepsze komunikaty o błędach i powinieneś. Ale ten post jest tutaj, ponieważ obecny kod, nad którym pracuję, jest uciążliwy i nie powinieneś robić tego samego.
źródło
throw new Exception("Something went wrong with " + instanceVar, ex);
„Aby poradzić sobie z kilkoma możliwymi błędami, które nie powinny powstrzymywać wykonania”
Jeśli masz na myśli, że błędy nie powinny zatrzymywać wykonywania bieżącej funkcji, ale powinny być w jakiś sposób zgłaszane do osoby dzwoniącej - masz kilka opcji, o których tak naprawdę nie wspomniano. Ta sprawa jest naprawdę bardziej ostrzeżeniem niż błędem. Rzucanie / Powrót nie jest opcją, ponieważ kończy bieżącą funkcję. Pojedynczy parametr lub komunikat o błędzie pozwala na wystąpienie tylko jednego z tych błędów.
Dwa wzorce, których użyłem to:
Kolekcja błędów / ostrzeżeń przekazana lub przechowywana jako zmienna członkowska. Do których dodajesz rzeczy i po prostu kontynuujesz przetwarzanie. Osobiście nie podoba mi się to podejście, ponieważ uważam, że osłabia ono rozmówcę.
Przekaż obiekt obsługi błędu / ostrzeżenia (lub ustaw go jako zmienną składową). I każdy błąd wywołuje funkcję elementu obsługi. W ten sposób osoba dzwoniąca może zdecydować, co zrobić z takimi błędami nie kończącymi się.
To, co przekazujesz do tych kolekcji / procedur obsługi, powinno zawierać wystarczającą ilość kontekstu, aby błąd mógł zostać „poprawnie” obsłużony - ciąg znaków jest zwykle zbyt mały, przekazanie go w niektórych przypadkach wyjątku jest często rozsądne - ale czasami jest to niezadowolone (jako nadużycie wyjątków) .
Typowy kod korzystający z procedury obsługi błędów może wyglądać tak
źródło
warnings
pakiecie Pythona , który daje inny wzorzec tego problemu.Często nie ma nic złego w korzystaniu z tego wzorca lub wzorca, o ile używasz wzorca używanego przez wszystkich innych. W rozwoju Objective-C bardzo preferowanym wzorcem jest przekazanie wskaźnika, w którym wywoływana metoda może zdeponować obiekt NSError. Wyjątki są zastrzeżone dla błędów programowania i prowadzą do awarii (chyba że masz programistów Java lub .NET, którzy piszą swoją pierwszą aplikację na iPhone'a). I to działa całkiem dobrze.
źródło
Odpowiedź na to pytanie jest już udzielona, ale nie mogę się powstrzymać.
Naprawdę nie można oczekiwać, że wyjątek zapewni rozwiązanie dla wszystkich przypadków użycia. Wbijać kogoś?
Zdarzają się przypadki, gdy wyjątki nie są końcem wszystkich i są wszystkie, na przykład, jeśli metoda otrzyma żądanie i jest odpowiedzialna za sprawdzenie poprawności wszystkich przekazanych pól, a nie tylko pierwszego, wtedy musisz pomyśleć, że powinno być możliwe wskaż przyczynę błędu dla więcej niż jednego pola. Powinno być również możliwe wskazanie, czy charakter walidacji uniemożliwia użytkownikowi przejście dalej, czy też nie. Przykładem może być słabe hasło. Możesz wyświetlić użytkownikowi komunikat informujący, że wprowadzone hasło nie jest zbyt silne, ale wystarczająco silne.
Można argumentować, że wszystkie te walidacje mogłyby zostać zgłoszone jako wyjątek na końcu modułu walidacji, ale byłyby to kody błędów w niczym innym niż nazwa.
Oto lekcja: wyjątki mają swoje miejsca, podobnie jak kody błędów. Wybrałem mądrze.
źródło
Validator
(interfejs) wstrzyknięty do danej metody (lub obiektu za nią). W zależności od wstrzykniętejValidator
metody metoda będzie działać ze złymi hasłami - lub nie. Otaczający kod mógłby następnie wypróbować,WeakValidator
jeśli użytkownik poprosił o to po, np. AWeakPasswordException
został rzucony przez początkowo wypróbowanyStrongValidator
.MiddlyStrongValidator
coś lub coś. A jeśli tak naprawdę nie przeszkodziło to w przepływie,Validator
należy wcześniej je wywołać, to znaczy przed kontynuowaniem przepływu, gdy użytkownik wciąż wprowadza swoje hasło (lub podobne). Ale wtedy walidacja nie była częścią omawianej metody. :) Prawdopodobnie w końcu kwestia gustu ...AggregateException
(lub podobnyValidationException
) i umieszczam określone wyjątki dla każdego problemu z walidacją w InnerExceptions. Może to być na przykładBadPasswordException
: „Hasło użytkownika jest mniejsze niż minimalna długość 6” lubMandatoryFieldMissingException
: „Należy podać imię użytkownika” itp. Nie jest to równoważne z kodami błędów. Wszystkie te komunikaty mogą być wyświetlane użytkownikowi w sposób, który zrozumieją, a jeśliNullReferenceException
zamiast niego zostanie rzucony, to mamy błąd.Istnieją przypadki użycia, w których kody błędów są lepsze niż wyjątki.
Jeśli Twój kod może kontynuować pomimo błędu, ale wymaga zgłoszenia, wyjątek jest złym wyborem, ponieważ wyjątki powodują zakończenie przepływu. Na przykład, jeśli czytasz w pliku danych i odkrywasz, że zawiera on trochę nieterminowych kawałków złych danych, lepiej jest przeczytać resztę pliku i zgłosić błąd, a nie zawieść.
Inne odpowiedzi wyjaśniły, dlaczego wyjątki powinny być preferowane od kodów błędów w ogóle.
źródło
AcknowledgePossibleCorruption
metody. ,Z pewnością nie ma nic złego w niestosowaniu wyjątków, gdy wyjątki nie są dobrym rozwiązaniem.
Kiedy wykonywanie kodu nie powinno być przerywane (np. Działanie na podstawie danych wejściowych użytkownika, które mogą zawierać wiele błędów, takich jak program do kompilacji lub formularz do przetworzenia), stwierdzam, że zbieranie błędów w zmiennych błędów, takich jak
has_errors
ierror_messages
jest rzeczywiście o wiele bardziej elegancki projekt niż rzucanie wyjątek przy pierwszym błędzie. Pozwala znaleźć wszystkie błędy w danych wejściowych użytkownika bez zmuszania użytkownika do niepotrzebnego ponownego przesyłania.źródło
W niektórych dynamicznych językach programowania można używać zarówno wartości błędów, jak i obsługi wyjątków . Odbywa się to poprzez zwrócenie niezatwierdzonego obiektu wyjątku zamiast zwykłej wartości zwracanej, którą można sprawdzić jak wartość błędu, ale zgłasza wyjątek, jeśli nie jest sprawdzany.
W Perlu 6 odbywa się to za pośrednictwem
fail
, który przy użyciuno fatal;
zasięgu zwraca specjalny, nieprzerzucanyFailure
obiekt wyjątku .W Perlu 5 możesz użyć Contextual :: Return, możesz to zrobić
return FAIL
.źródło
Chyba że istnieje coś bardzo konkretnego, myślę, że posiadanie zmiennej błędu do sprawdzania poprawności jest złym pomysłem. Wydaje się, że celem jest zaoszczędzenie czasu poświęcanego na sprawdzanie poprawności (możesz po prostu zwrócić wartość zmiennej)
Ale jeśli cokolwiek zmienisz, i tak musisz przeliczyć tę wartość. Nie mogę jednak powiedzieć więcej o zatrzymywaniu i rzucaniu wyjątków.
EDYCJA: Nie zdawałem sobie sprawy, że jest to kwestia paradygmatu oprogramowania zamiast konkretnego przypadku.
Pozwolę sobie wyjaśnić moje uwagi w konkretnym przypadku, w którym moja odpowiedź miałaby sens
Istnieją dwa rodzaje błędów:
W warstwie usługi nie ma innego wyboru, jak użycie obiektu Result jako opakowania, które jest równoważnością zmiennej błędu. Symulacja wyjątku poprzez zgłoszenie serwisowe w protokole takim jak http jest możliwa, ale zdecydowanie nie jest dobrym rozwiązaniem. Nie mówię o tego rodzaju błędach i nie sądziłem, że jest to rodzaj błędu zadawanego w tym pytaniu.
Myślałem o drugim rodzaju błędu. A moja odpowiedź dotyczy tego drugiego rodzaju błędów. W obiektach bytu są dla nas wybory, niektóre z nich
Korzystanie ze zmiennej sprawdzania poprawności jest takie samo, jak w przypadku pojedynczej metody sprawdzania poprawności dla każdego obiektu jednostki. W szczególności użytkownik może ustawić wartość w taki sposób, aby ustawić setera jako setera, bez efektu ubocznego (często jest to dobra praktyka) lub można włączyć walidację do każdego setera, a następnie zapisać wynik w zmiennej walidacji. Zaletą tego byłoby zaoszczędzenie czasu, wynik sprawdzania poprawności jest buforowany w zmiennej sprawdzania poprawności, dzięki czemu gdy użytkownik wywoła funkcję sprawdzania poprawności () wiele razy, nie ma potrzeby wielokrotnego sprawdzania poprawności.
Najlepszą rzeczą do zrobienia w tym przypadku jest użycie pojedynczej metody sprawdzania poprawności bez użycia nawet sprawdzania poprawności w celu buforowania błędu sprawdzania poprawności. Pomaga to utrzymać setera jako po prostu setera.
źródło