Czy zmienne błędu są anty-wzorcem czy dobrym projektem?

44

Aby obsłużyć kilka możliwych błędów, które nie powinny powstrzymywać wykonywania, mam errorzmienną, 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.

Mikayla Maki
źródło
6
Po co try/ catchistnieje. Dodatkowo możesz umieścić swój try/ catchdużo dalej w stosie w bardziej odpowiednim miejscu do obsługi go (pozwalając na większe rozdzielenie obaw).
jpmc26
O czym należy pamiętać: jeśli zamierzasz korzystać z obsługi opartej na wyjątkach i otrzymujesz wyjątek, nie chcesz pokazywać użytkownikowi zbyt dużej ilości informacji. Użyj programu do obsługi błędów, takiego jak Elmah lub Raygun.io, aby przechwycić go i wyświetlić użytkownikowi ogólny komunikat o błędzie. NIGDY nie wyświetlaj użytkownikowi śladu stosu lub konkretnego komunikatu o błędzie, ponieważ ujawniają one informacje o wewnętrznych działaniach aplikacji, które mogą być nadużywane.
Nzall
4
@Nate Twoja rada dotyczy tylko aplikacji krytycznych dla bezpieczeństwa, w których użytkownik jest całkowicie niezaufany. Niejasne komunikaty o błędach same w sobie stanowią anty-wzór. Podobnie jak wysyłanie raportów o błędach przez sieć bez wyraźnej zgody użytkownika.
piedar
3
@piedar Stworzyłem osobne pytanie, w którym można to omówić bardziej swobodnie: programmers.stackexchange.com/questions/245255/…
Nzall
26
Ogólna zasada projektowania interfejsu API, która prowadzi cię dość daleko, to zawsze patrzeć na to, co robi PHP, a następnie robić dokładnie odwrotnie.
Philipp

Odpowiedzi:

65

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:

Nie można pobrać zasady o nazwie „foo” dla „paska” użytkownika, do którego odwołano się w profilu użytkownika.

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ć, ArgumentNullExceptionktóry kod wywołujący może zawinąć do wewnątrz, UnableToRetrievePolicyExceptionponieważ 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 to ArgumentNullExceptionon 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).

Omer Iqbal
źródło
5
Windows zwraca wartości HRESULT z funkcji C ++, aby zachować zgodność C w swoich publicznych interfejsach API (i ponieważ zaczynasz wchodzić w świat cierpienia, próbując organizować wyjątki ponad granicami). Jeśli piszesz aplikację, nie ślepo postępuj zgodnie z modelem systemu operacyjnego.
Cody Gray
2
Jedyne, co chciałbym do tego dodać, to wzmianka o luźniejszym sprzężeniu. Wyjątki pozwalają poradzić sobie z wieloma nieoczekiwanymi sytuacjami w najbardziej odpowiednim miejscu. Na przykład w aplikacji internetowej chcesz zwrócić 500 za każdy wyjątek, na który kod nie był przygotowany, zamiast zawieszać aplikację internetową. Więc potrzebujesz jakiegoś rodzaju catch na górze kodu (lub w twoim frameworku). Podobna sytuacja występuje w graficznych interfejsach użytkownika. Możesz jednak umieścić mniej ogólne procedury obsługi w różnych miejscach kodu, aby obsłużyć różne sytuacje awarii w sposób odpowiedni dla podejmowanej próby bieżącego procesu.
jpmc26
2
@TrentonMaki Jeśli mówisz o błędach konstruktorów C ++, najlepsza odpowiedź jest tutaj: parashift.com/c++-faq-lite/ctors-can-throw.html . Krótko mówiąc, rzuć wyjątek, ale pamiętaj, aby najpierw usunąć potencjalne wycieki. Nie znam innych języków, w których zwykłe rzucanie konstruktorem jest czymś złym. Myślę, że większość użytkowników interfejsu API wolałaby wychwytywać wyjątki niż sprawdzać kody błędów.
Ogre Psalm33
3
„Takie zaawansowane scenariusze nie są łatwe do wdrożenia z wartościami zwracanymi”. Oczywiście że możesz! Wszystko, co musisz zrobić, to stworzyć ErrorStateReturnVariablesuperklasę, a jedną z jej właściwości jest InnerErrorState(co jest instancją ErrorStateReturnVariable), którą implementujące podklasy mogą ustawić, aby wyświetlać łańcuch błędów ... och, czekaj. : p
Brian S
5
To, że język obsługuje wyjątki, nie czyni z nich panaceum. Wyjątki wprowadzają ukryte ścieżki wykonania, dlatego ich efekty muszą być odpowiednio kontrolowane; try / catch są łatwe do dodania, ale poprawa odzyskiwania jest trudna , ...
Matthieu M.,
21

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

Doktor Brown
źródło
1
Dziękuję za wyjaśnienie! Twoja odpowiedź + Omer Iqbal odpowiedział na moje pytanie.
Mikayla Maki,
Innym sposobem radzenia sobie z „ostrzeżeniami” jest domyślne zgłaszanie wyjątku i posiadanie opcjonalnej flagi, aby zapobiec zgłoszeniu wyjątku.
Cyanfish,
1
@Cyanfish: tak, ale należy uważać, aby nie przeprojektować takich rzeczy, szczególnie podczas tworzenia bibliotek. Lepiej zapewnij jeden prosty i działający mechanizm ostrzegawczy niż 2, 3 lub więcej.
Doc Brown
Wyjątek należy zgłaszać tylko wtedy, gdy wystąpi awaria, nieznany lub niemożliwy do odzyskania scenariusz - wyjątkowe okoliczności . Podczas konstruowania wyjątków należy spodziewać się wpływu na wydajność
Gusdor,
@Gusdor: absolutnie dlatego typowe „ostrzeżenie” nie powinno IMHO domyślnie zgłaszać wyjątku. Ale to zależy też trochę od poziomu abstrakcji. Czasami usługa lub biblioteka po prostu nie może zdecydować, czy nietypowe zdarzenie powinno być traktowane jako wyjątek z punktu widzenia osoby dzwoniącej. Osobiście, w takich sytuacjach wolę lib, aby ustawić wskaźnik ostrzegawczy (bez wyjątku), pozwól dzwoniącemu przetestować tę flagę i wyrzucić wyjątek, jeśli uzna to za właściwe. Właśnie to miałem na myśli, gdy napisałem powyżej: „lepiej jest zapewnić jeden mechanizm ostrzegawczy w bibliotece”.
Doc Brown
20

Istnieje wiele sposobów sygnalizowania błędu:

  • zmienna błędu do sprawdzenia: C , Go , ...
  • wyjątek: Java , C # , ...
  • program obsługi „warunku”: Lisp (tylko?), ...
  • powrót polimorficzny: Haskell , ML , Rust , ...

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 bw języku Haskell) to moje dotychczasowe ulubione rozwiązanie:

  • jawne: brak ukrytej ścieżki wykonania
  • jednoznaczne: w pełni udokumentowane podpisem typu funkcji (bez niespodzianek)
  • trudne do zignorowania: musisz dopasować wzór, aby uzyskać pożądany wynik i obsłużyć przypadek błędu

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 .

Matthieu M.
źródło
1
Dobra odpowiedź! Miałem nadzieję, że uda mi się uzyskać listę sposobów usuwania błędów obsługi.
Mikayla Maki,
Arrghhh! Ile razy widziałem to „Problemem zmiennej błędu jest to, że łatwo zapomnieć o sprawdzeniu”. Problem z wyjątkami polega na tym, że łatwo go zapomnieć. Następnie aplikacja ulega awarii. Twój szef nie chce płacić za naprawę, ale klienci przestają korzystać z Twojej aplikacji, ponieważ są sfrustrowani awariami. Wszystko z powodu czegoś, co nie wpłynęłoby na wykonanie programu, gdyby kody błędów zostały zwrócone i zignorowane. Jedyny raz, kiedy widziałem, jak ludzie ignorują kody błędów, to kiedy nie miało to większego znaczenia. Kod wyżej w łańcuchu wie, że coś poszło nie tak.
Dunk
1
@Dunk: Nie sądzę, aby moja odpowiedź przepraszała również za wyjątkami; może jednak zależeć od rodzaju pisanego kodu. Moje osobiste doświadczenie zawodowe ma tendencję do faworyzowania systemów, które zawodzą w przypadku błędów, ponieważ cicha korupcja danych jest gorsza (i niewykrywalna), a dane, nad którymi pracuję, są cenne dla klienta (oczywiście oznacza to również pilne poprawki).
Matthieu M.,
To nie jest kwestia cichego uszkodzenia danych. Chodzi o zrozumienie aplikacji i określenie, kiedy i gdzie trzeba sprawdzić, czy operacja się powiodła. W wielu przypadkach dokonanie tego ustalenia i postępowanie może być opóźnione. Ktoś inny nie powinien powiedzieć mi, kiedy muszę obsłużyć nieudaną operację, czego wymaga wyjątek. To moja aplikacja, wiem kiedy i gdzie chcę rozwiązać wszelkie istotne problemy. Jeśli ludzie piszą aplikacje, które mogą uszkodzić dane, to robi to naprawdę kiepską robotę. Pisanie aplikacji, które ulegają awarii (co często widzę), również robi kiepską robotę.
Dunk
12

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:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

Albo to:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Wolałbym, żeby działania same generowały wyjątki, więc kończysz na czymś takim:

x.doActionA();
x.doActionB();

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:

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Nowy i ulepszony:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

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

mrjink
źródło
1
Jednym haczykiem są sytuacje, w których Twój „nowy i ulepszony” utraciłby kontekst, w którym początkowo występuje wyjątek. Na przykład w „bieżącej (okropnej)” wersji doActionA () klauzula catch miałaby dostęp do zmiennych instancji i innych informacji z otaczającego obiektu, co dałoby bardziej przydatny komunikat.
Poinformowano
1
To prawda, ale obecnie tak się nie dzieje. I zawsze możesz złapać wyjątek w doActionA i zawinąć go w inny wyjątek komunikatem o stanie. Wtedy nadal będziesz mieć ślad stosu i przydatny komunikat. throw new Exception("Something went wrong with " + instanceVar, ex);
mrjink
Zgadzam się, może się to nie zdarzyć w twojej sytuacji. Ale nie można „zawsze” wstawiać informacji doActionA (). Dlaczego? Program wywołujący doActionA () może być jedynym, który przechowuje informacje, które należy uwzględnić.
Poinformowano
2
Poproś osobę dzwoniącą o włączenie go, gdy obsługuje wyjątek. To samo dotyczy pierwotnego pytania. Nic nie możesz teraz zrobić, czego nie można zrobić z wyjątkami, a to prowadzi do czystszego kodu. Wolę wyjątki od zwracanych wartości logicznych lub komunikatów o błędach.
mrjink
Przyjmujesz zwykle fałszywe założenie, że wyjątek występujący w dowolnej z operacji „someOperation” może być obsługiwany i usuwany w ten sam sposób. To, co dzieje się w prawdziwym życiu, polega na tym, że musisz wychwytywać i obsługiwać wyjątki dla każdej operacji. Dlatego nie rzucasz wyjątku, jak w twoim przykładzie. Ponadto użycie wyjątków kończy się tworzeniem wiązki zagnieżdżonych bloków try-catch lub serii bloków try-catch. Często sprawia, że ​​kod jest znacznie mniej czytelny. Nie jestem przeciwny wyjątkom, ale używam odpowiedniego narzędzia do konkretnej sytuacji. Wyjątkami są tylko 1 narzędzie.
Dunk
5

„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

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}
Michael Anderson
źródło
3
+1 za zauważenie, że użytkownik chce ostrzeżenia, a nie błędu. Warto wspomnieć o warningspakiecie Pythona , który daje inny wzorzec tego problemu.
James_pic
Dzięki za świetną odpowiedź! Jest to więcej tego, co chciałem zobaczyć, dodatkowe wzorce obsługi błędów, w których tradycyjne try / catch może nie wystarczyć.
Mikayla Maki,
Warto wspomnieć, że przekazany obiekt wywołania zwrotnego błędu może zgłosić wyjątek, gdy wystąpią pewne błędy lub ostrzeżenia - w rzeczywistości może to być zachowanie domyślne - ale może być również użyteczny w przypadku środków, za pomocą których może zapytać funkcja wywołania, aby coś zrobić. Na przykład metoda „błąd parsowania uchwytu” może nadać programowi wywołującemu wartość, którą parsowanie należy uznać za zwrócone.
supercat
5

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.

gnasher729
źródło
4

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.

Alexandre Santos
źródło
Myślę, że to raczej oznacza zły projekt. Metoda, która bierze argumenty, powinna być w stanie sobie z nimi poradzić lub nie - nic pomiędzy. Twój przykład powinien mieć Validator(interfejs) wstrzyknięty do danej metody (lub obiektu za nią). W zależności od wstrzykniętej Validatormetody metoda będzie działać ze złymi hasłami - lub nie. Otaczający kod mógłby następnie wypróbować, WeakValidatorjeśli użytkownik poprosił o to po, np. A WeakPasswordExceptionzostał rzucony przez początkowo wypróbowany StrongValidator.
jhr
Ach, ale nie powiedziałem, że to nie może być interfejs ani walidator. Nawet nie wspomniałem o JSR303. A jeśli czytasz uważnie, upewniłem się, że nie powiem słabego hasła, a raczej, że nie było zbyt silne. Słabe hasło byłoby powodem do zatrzymania przepływu i poproszenia użytkownika o silniejsze hasło.
Alexandre Santos
A co byś zrobił z średnio silnym, ale nie bardzo słabym hasłem? Przerwałbyś przepływ i pokazałbyś użytkownikowi komunikat wskazujący, że wprowadzone hasło nie jest zbyt silne. Więc miej MiddlyStrongValidatorcoś lub coś. A jeśli tak naprawdę nie przeszkodziło to w przepływie, Validatornależ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 ...
jhr
@jhr W walidatorach, które napisałem, zazwyczaj tworzę AggregateException(lub podobny ValidationException) i umieszczam określone wyjątki dla każdego problemu z walidacją w InnerExceptions. Może to być na przykład BadPasswordException: „Hasło użytkownika jest mniejsze niż minimalna długość 6” lub MandatoryFieldMissingException: „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śli NullReferenceExceptionzamiast niego zostanie rzucony, to mamy błąd.
Omer Iqbal,
4

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.

Jack Aidley
źródło
Jeśli ostrzeżenie musi zostać zarejestrowane, czy coś, niech tak będzie. Ale jeśli błąd „wymaga zgłoszenia”, przez co zakładam, że masz na myśli zgłoszenie do użytkownika, nie ma sposobu, aby zagwarantować, że otaczający kod odczyta kod powrotu i faktycznie go zgłosi.
jhr
Nie, mam na myśli zgłoszenie tego dzwoniącemu. Na dzwoniącym spoczywa decyzja, czy użytkownik powinien wiedzieć o błędzie, czy nie, tak jak w przypadku wyjątku.
Jack Aidley,
1
@jhr: Kiedy jest jakakolwiek gwarancja? Umowa dla klasy może określać, że klienci mają określone obowiązki; jeśli klienci będą przestrzegać umowy, zrobią to, czego wymaga umowa. Jeśli nie, wszelkie konsekwencje będą wynikać z kodu klienta. Jeśli ktoś chce uchronić się przed przypadkowymi błędami klienta i ma kontrolę nad typem zwracanym przez metodę serializacji, może mieć flagę „niepotwierdzone możliwe uszkodzenie” i nie zezwalać klientom na odczyt danych z niego bez wywoływania AcknowledgePossibleCorruptionmetody. ,
supercat
1
... ale posiadanie klasy obiektu do przechowywania informacji o problemach może być bardziej pomocne niż zgłaszanie wyjątków lub zwracanie kodu błędu pass-fail. Od tej aplikacji zależeć będzie wykorzystanie tych informacji w odpowiedni sposób (np. Podczas ładowania pliku „Foo”, poinformowanie użytkownika, że ​​dane mogą być niewiarygodne i monitowanie go o wybranie nowej nazwy podczas zapisywania).
supercat
1
Istnieje jedna gwarancja, jeśli używasz wyjątków: jeśli ich nie złapiesz, wznoszą się wyżej - najgorszy przypadek do interfejsu użytkownika. Nie ma takiej gwarancji, jeśli użyjesz kodów powrotu, których nikt nie czyta. Oczywiście, postępuj zgodnie z interfejsem API, jeśli chcesz go używać. Zgadzam się! Ale jest miejsce na błąd, niestety ...
jhr
2

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

ikh
źródło
Ciekawe podejście do pytania. Myślę, że problemem z moim pytaniem i moim zrozumieniem jest niejasna terminologia. To, co opisujesz, nie jest wyjątkowe, ale jest błędem. Jak powinniśmy to nazwać?
Mikayla Maki
1

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życiu no fatal;zasięgu zwraca specjalny, nieprzerzucany Failureobiekt wyjątku .

W Perlu 5 możesz użyć Contextual :: Return, możesz to zrobić return FAIL.

Jakub Narębski
źródło
-1

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

  1. Mam kolekcje obiektów bytu
  2. Mam usługi sieciowe w stylu proceduralnym, które działają z tymi obiektami encji

Istnieją dwa rodzaje błędów:

  1. Błędy podczas przetwarzania występują w warstwie usługi
  2. Błędy, ponieważ występują niespójności w obiektach encji

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

  • za pomocą zmiennej sprawdzania poprawności
  • zgłasza wyjątek natychmiast, gdy pole jest ustawiane niepoprawnie przez ustawiającego

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.

Poinformowano
źródło
Rozumiem o czym mówisz. Ciekawe podejście Cieszę się, że to część zestawu odpowiedzi.
Mikayla Maki