Jak napisać dobry komunikat wyjątku

101

Obecnie sprawdzam kod i jedną z rzeczy, które zauważam, jest liczba wyjątków, w których komunikat o wyjątku wydaje się powtarzać, gdzie wystąpił wyjątek. na przykład

throw new Exception("BulletListControl: CreateChildControls failed.");

Wszystkie trzy elementy w tej wiadomości mogę opracować na podstawie reszty wyjątku. Znam klasę i metodę ze śledzenia stosu i wiem, że zawiodła (ponieważ mam wyjątek).

Sprawiło, że pomyślałem o tym, jaką wiadomość umieszczam w wiadomościach wyjątkowych. Najpierw tworzę klasę wyjątków, jeśli jeszcze nie istnieje, z ogólnego powodu (np PropertyNotFoundException. Dlaczego ), a następnie, gdy ją wyrzucam, komunikat wskazuje, co poszło źle (np. „Nie można znaleźć właściwości„ IDontExist ”w węźle 1234 „- co ). Gdzie jest w StackTrace. Kiedy może zakończyć się w dzienniku (jeśli dotyczy). Jak jest deweloper wypracować (i naprawić)

Czy masz jeszcze jakieś wskazówki dotyczące zgłaszania wyjątków? W szczególności w odniesieniu do tworzenia nowych typów i komunikatu wyjątku.

Colin Mackay
źródło
4
Czy są to pliki dziennika lub prezentowane użytkownikowi?
Jon Hopkins,
5
Tylko do debugowania. Mogą skończyć się w dzienniku. Nie zostaną one przedstawione użytkownikowi. Nie jestem fanem prezentowania użytkownikowi komunikatów o wyjątkach.
Colin Mackay

Odpowiedzi:

70

Swoją odpowiedź skieruję bardziej na to, co nastąpi po wyjątku: po co to jest dobre i jak powinno się zachowywać oprogramowanie, co użytkownicy powinni zrobić z wyjątkiem? Świetną techniką, na którą natknąłem się na początku mojej kariery, było zawsze zgłaszanie problemów i błędów w 3 częściach: kontekst, problem i rozwiązanie. Korzystanie z tej przewagi ogromnie zmienia obsługę błędów i sprawia, że ​​oprogramowanie jest znacznie lepsze dla operatorów.

Oto kilka przykładów.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

W takim przypadku operator dokładnie wie, co zrobić i na jaki plik należy to zmienić. Wiedzą również, że zmiany w puli połączeń nie zostały przyjęte i powinny zostać powtórzone.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

Piszę systemy po stronie serwera, a moi operatorzy są zorientowani na technologię pierwszej linii. Napisałbym inaczej w przypadku programów komputerowych, które mają różnych odbiorców, ale zawierają te same informacje.

Gdy używa się tej techniki, dzieje się kilka cudownych rzeczy. Deweloper oprogramowania jest często najlepiej przygotowany do rozwiązywania problemów we własnym kodzie, więc kodowanie rozwiązań w ten sposób podczas pisania kodu jest ogromną korzyścią dla użytkowników końcowych, którzy mają trudności ze znalezieniem rozwiązań, ponieważ często brakuje im informacji o co dokładnie robiło oprogramowanie. Każdy, kto kiedykolwiek przeczytał komunikat o błędzie Oracle, będzie wiedział, co mam na myśli.

Drugą cudowną rzeczą, która przychodzi na myśl, jest próba opisania rozwiązania w twoim wyjątku i piszesz „Sprawdź X, a jeśli A to B jeszcze C”. Jest to bardzo wyraźny i oczywisty znak, że Twój wyjątek jest sprawdzany w niewłaściwym miejscu. Wy, programiści, macie możliwość porównywania rzeczy w kodzie, więc „jeśli” instrukcje powinny być uruchamiane w kodzie, po co angażować użytkownika w coś, co można zautomatyzować? Możliwe, że jest to z głębszego kodu, a ktoś zrobił leniwą rzecz i wyrzucił wyjątek IOException z dowolnej liczby metod i wyłapał potencjalne błędy z każdego z nich w bloku wywołującego kodu, który nie może odpowiednio opisać, co poszło źle, co konkretniekontekst jest i jak to naprawić. To zachęca Cię do pisania drobniejszych błędów ziarna, wychwytywania i obsługiwania ich we właściwym miejscu w kodzie, abyś mógł poprawnie wyartykułować kroki, które powinien podjąć operator.

W jednej firmie mieliśmy najlepszych operatorów, którzy naprawdę dobrze poznali oprogramowanie i prowadzili własną „księgę”, która poprawiła nasze zgłaszanie błędów i sugerowało rozwiązania. Aby to rozpoznać, oprogramowanie zaczęło w wyjątkowych przypadkach włączać linki wiki do księgi głównej, tak aby dostępne było podstawowe wyjaśnienie, a także łącza do bardziej zaawansowanych dyskusji i spostrzeżeń operatorów.

Jeśli masz doświadczenie w wypróbowaniu tej techniki, staje się o wiele bardziej oczywiste, jak powinieneś nazwać swoje wyjątki w kodzie podczas tworzenia własnego. NonRecoverableConfigurationReadFailedException staje się trochę skrótem tego, co zamierzasz pełniej opisać operatorowi. Lubię być gadatliwy i myślę, że będzie to łatwiejsze dla następnego programisty, który dotknie mojego kodu do interpretacji.

Sir Wobin
źródło
1
+1 To jest dobry system. Co jest ważniejsze: upewnienie się, że informacje się przedostają, czy użycie krótkich słów?
Michael K
3
+1 za podoba mi się rozwiązanie obejmujące kontekst, problem, rozwiązanie
WebDev
1
Ta technika jest bardzo pomocna. Na pewno zamierzam to wykorzystać.
Kid Diamond
Kontekst to niepotrzebne dane. Jest już obecny w śledzeniu stosu. Posiadanie rozwiązania jest lepsze, ale nie zawsze możliwe / przydatne. Większość problemów polega na wdzięcznym zatrzymaniu aplikacji lub zignorowaniu oczekujących operacji i powrocie do głównej pętli wykonawczej aplikacji w nadziei, że następnym razem odniesiesz sukces ... Nazwa klasy wyjątku powinna być taka, aby rozwiązanie stało się oczywiste, bo FileNotFoundlub ConnectExceptionwiesz, co robić))
gavenkoa
2
@ThomasFlinkow W twoim przykładzie ślady będą miały init (), execute () i cleanup () w śledzeniu stosu. Przy dobrym schemacie nazewnictwa w bibliotece i czystym / zrozumiałym interfejsie API nie potrzebujesz objaśnień łańcucha. I szybko zawiedzie, nie noś stanu uszkodzonego w całym systemie. Śledzenie i rejestrowanie rekordów z unikalnym identyfikatorem może wyjaśnić przebieg / stan aplikacji.
gavenkoa
23

W tym najnowszym pytaniu zwróciłem uwagę, że wyjątki w ogóle nie powinny zawierać wiadomości. Moim zdaniem fakt, że tak robią, jest ogromnym nieporozumieniem. Proponuję to

„Komunikatem” wyjątku jest (w pełni kwalifikowana) nazwa klasy wyjątku.

Wyjątek powinien zawierać w obrębie własnych zmiennych składowych jak najwięcej szczegółów na temat dokładnie tego, co się stało; na przykład a IndexOutOfRangeExceptionpowinien zawierać wartość indeksu, która została uznana za niepoprawną, a także górne i dolne wartości, które były ważne w momencie zgłoszenia wyjątku. W ten sposób, korzystając z odbicia, możesz mieć automatycznie skonstruowany komunikat, który brzmi następująco: IndexOutOfRangeException: index = -1; min=0; max=5i to, wraz ze śledzeniem stosu, powinny stanowić wszystkie obiektywne informacje, których potrzebujesz, aby rozwiązać problem. Sformatowanie go w ładną wiadomość, np. „Indeks -1 nie był między 0 a 5”, nie dodaje żadnej wartości.

W twoim konkretnym przykładzie NodePropertyNotFoundExceptionklasa zawierałaby nazwę właściwości, która nie została znaleziona, oraz odwołanie do węzła, który nie zawierał właściwości. Jest to ważne: należy nie zawierają nazwę węzła; powinien zawierać odniesienie do faktycznego węzła. W twoim konkretnym przypadku może to nie być konieczne, ale jest to kwestia zasady i preferowanego sposobu myślenia: podstawową troską przy konstruowaniu wyjątku jest to, że musi być użyteczny przez kod, który może go złapać. Użycie przez ludzi jest ważnym, ale jedynie drugorzędnym problemem.

Zapobiega to bardzo frustrującej sytuacji, której być może doświadczyłeś w pewnym momencie swojej kariery zawodowej, w której możesz zauważyć wyjątek zawierający istotne informacje o tym, co wydarzyło się w tekście wiadomości, ale nie w jej zmiennych członkowskich, więc musiałem parsować ciąg tekstowy, aby dowiedzieć się, co się stało, mając nadzieję, że tekst wiadomości pozostanie taki sam w przyszłych wersjach warstwy podstawowej, i modląc się, aby tekst wiadomości nie był w jakimś obcym języku, gdy twój program jest uruchomić w innych krajach.

Oczywiście, ponieważ nazwa klasy wyjątku jest komunikatem wyjątku (a zmienne składowe wyjątku są szczegółowymi szczegółami), oznacza to, że potrzebujesz wielu różnych wyjątków, aby przekazać wszystkie różne komunikaty, oraz w porządku.

Czasami, kiedy piszemy kod, spotykamy się z błędną sytuacją, w której chcemy po prostu szybko zakodować throwinstrukcję i kontynuować pisanie kodu zamiast przerywać to, co robimy, aby stworzyć nową klasę wyjątków, abyśmy mogli rzuć to tam. W tych przypadkach mam GenericExceptionklasę, która faktycznie akceptuje komunikat łańcuchowy jako parametr czasu budowy, ale konstruktor tej wyjątkowej klasy jest ozdobiony wielkim, wielkim, jasnym fioletowym FIXME XXX TODOkomentarzem stwierdzającym, że każda instancja tej klasy musi być zastąpione wystąpieniem jakiejś bardziej wyspecjalizowanej klasy wyjątków przed wydaniem systemu, najlepiej przed zatwierdzeniem kodu.

Mike Nakis
źródło
8
Jeśli jesteś w języku, który nie ma GC, takim jak C ++, powinieneś bardzo uważać na umieszczanie odwołań do dowolnych danych w wyjątkach wysyłanych przez stos. Możliwe, że wszystko, do czego się odnosisz, zostało zniszczone do momentu wyłapania wyjątku.
Sebastian Redl
4
@SebastianRedl True. To samo może mieć zastosowanie do C # i Java, jeśli nodeobiekt jest chroniony przez klauzulę use-disposable (w C #) lub try-with-resources (w Javie): obiekt przechowywany z wyjątkiem zostałby unieszkodliwiony / zamknięty, co czyni go nielegalnym aby uzyskać do niego dostęp, aby uzyskać z niego użyteczne informacje w miejscu, w którym obsługiwany jest wyjątek. Przypuszczam, że w tych przypadkach jakieś streszczenie podsumowania obiektu powinno być przechowywane w ramach wyjątku, zamiast samego obiektu. Nie mogę wymyślić głupiego sposobu, aby ogólnie poradzić sobie z tym we wszystkich przypadkach.
Mike Nakis
13

Zasadą ogólną jest, że wyjątek powinien pomóc programistom w określeniu przyczyny poprzez podanie użytecznych informacji (oczekiwane wartości, rzeczywista wartość, możliwe przyczyny / rozwiązanie itp.).

Nowe typy wyjątków powinny być tworzone, gdy żaden z wbudowanych typów nie ma sensu . Określony typ umożliwia innym programistom wyłapanie określonego wyjątku i obsłużenie go. Jeśli programista będzie wiedział, jak obsłużyć wyjątek, ale taki jest typ Exception, nie będzie w stanie odpowiednio go obsłużyć.

Mbillard
źródło
+1 - wartości oczekiwane vs. wartości rzeczywiste są niezwykle przydatne. W przykładzie podanym w pytaniu nie należy po prostu mówić, że metoda zawiodła, ale dlaczego się nie udała (w zasadzie dokładne polecenie, które nie powiodło się, oraz okoliczności, które spowodowały błąd).
Felix Dombek
4

W .NET nigdy throw new Exception("...")(jak pokazał autor pytania). Wyjątek jest głównym typem wyjątku i nie powinien być nigdy zgłaszany bezpośrednio. Zamiast tego wyrzuć jeden z pochodnych typów wyjątków .NET lub utwórz własny niestandardowy wyjątek, który wywodzi się z wyjątku (lub innego typu wyjątku).

Dlaczego nie rzucić wyjątku? Ponieważ zgłoszenie wyjątku catch(Exception ex) { ... }nie ma wpływu na opis Twojego wyjątku i zmusza Twój kod wywołujący do napisania kodu, co na ogół nie jest dobrą rzeczą! :-).

bytedev
źródło
2

Rzeczy, których chcesz „dodać” do wyjątku, to te elementy danych, które nie są nieodłączne od wyjątku lub śledzenia stosu. To, czy stanowią one część „wiadomości”, czy muszą zostać dołączone po zalogowaniu, jest interesującym pytaniem.

Jak już zauważyłeś, wyjątek mówi ci co, stacktrace prawdopodobnie mówi ci gdzie, ale „dlaczego” może być bardziej zaangażowane (powinno być, można mieć nadzieję), niż po prostu zajrzeć do linii lub dwóch i powiedzieć „ doh! Oczywiście ". Jest to tym bardziej prawdziwe przy logowaniu błędów w kodzie produkcyjnym - zbyt często gryzły mnie złe dane, które trafiły do ​​działającego systemu, który nie istnieje w naszych systemach testowych. Coś tak prostego, jak znajomość identyfikatora rekordu w bazie danych, który powoduje (lub przyczynia się) do błędu, może zaoszczędzić znaczną ilość czasu.

Więc ... Albo na liście, albo w .NET, dodany do zbieranych danych wyjątków (por. @Plip!):

  • Parametry (może to być nieco interesujące - nie można dodać do kolekcji danych, jeśli nie będzie ona serializowana, a czasami pojedynczy parametr może być zaskakująco złożony)
  • Dodatkowe dane zwrócone przez ADO.NET lub Linq do SQL lub podobnego (może to być trochę interesujące!).
  • Cokolwiek innego może nie być oczywiste.

Niektóre rzeczy, oczywiście, nie będziesz wiedział, że potrzebujesz, dopóki nie będziesz ich mieć w swoim początkowym raporcie / dzienniku błędów. Niektóre rzeczy, o których nie zdajesz sobie sprawy, że możesz je zdobyć, dopóki ich nie potrzebujesz.

Murph
źródło
0

Jakie są wyjątki dla ?

(1) Mówienie użytkownikowi, że coś poszło nie tak?

To powinno być ostatecznością, ponieważ Twój kod powinien interweniować i pokazywać im coś „ładniejszego” niż wyjątek.

Komunikat „błąd” powinien jasno i zwięźle wskazywać, co poszło nie tak i co użytkownik może zrobić, aby wyjść z błędu.

np. „Proszę nie naciskać tego przycisku ponownie”

(2) Mówienie deweloperowi, gdy popełnił błąd?

Jest to rodzaj rzeczy, którą logujesz do pliku w celu późniejszej analizy. Śledzenie stosu powie Developer gdzie kod złamał; komunikat powinien ponownie wskazywać, co poszło nie tak.

(3) Mówienie programowi obsługi wyjątków (tj. Kodu), że coś poszło nie tak?

Typ wyjątku, który zadecyduje wyjątek obsługi dostanie na to patrzeć i właściwości zdefiniowane na obiekt wyjątku pozwolą Wózek do czynienia z nim.

Przesłanie wyjątku jest całkowicie nieistotne .

Phill W.
źródło
-3

Nie twórz nowych typów, jeśli możesz pomóc. Mogą powodować dodatkowe zamieszanie, złożoność i prowadzić do utrzymania większej liczby kodów. Mogą sprawić, że Twój kod będzie musiał zostać rozszerzony. Projektowanie hierarchii wyjątków wymaga dużo pracy i testowania. To nie jest myśl następcza. Często najlepiej jest korzystać z wbudowanej hierarchii wyjątków językowych.

Treść wiadomości wyjątku zależy od odbiorcy wiadomości - więc musisz postawić się w sytuacji tej osoby.

Inżynier wsparcia musi być w stanie jak najszybciej zidentyfikować źródło błędu. Dołącz krótki opisowy ciąg oraz wszelkie dane, które mogą pomóc w rozwiązaniu problemu. Zawsze dołączaj ślad stosu - jeśli możesz - będzie to jedyne prawdziwe źródło informacji.

Prezentowanie błędów ogólnym użytkownikom systemu zależy od rodzaju błędu: jeśli użytkownik może naprawić problem, na przykład poprzez podanie różnych danych wejściowych, wymagany jest zwięzły opisowy komunikat. Jeśli użytkownik nie może rozwiązać problemu, najlepiej poinformować, że wystąpił błąd, i zaloguj się / wyślij błąd do pomocy technicznej (korzystając z powyższych wskazówek).

Ponadto - nie wyrzucaj dużego „BŁĘDU HALTU!” Ikona. To błąd - to nie koniec świata.

Podsumowując: pomyśl o aktorach i zastosuj przypadki dla swojego systemu. Postaw się w butach tego użytkownika. Być pomocnym. Bądź miły. Pomyśl o tym z góry w projekcie systemu. Z perspektywy użytkowników - te wyjątki i sposób, w jaki system je obsługuje, są tak samo ważne jak normalne przypadki w twoim systemie.

Conor
źródło
10
Nie zgadzam się. Istnieje wiele dobrych powodów, aby wprowadzić własne wyjątki, gdy interfejs API języka nie pokrywa dokładnie twoich potrzeb. Jednym z powodów jest to, że w metodzie, w której wiele rzeczy może zawieść, możesz napisać różne klauzule catch dla różnych typów wyjątków, w których możesz zareagować na dokładny problem. Innym jest to, że można rozdzielić wiele warstw wyjątków reprezentujących różne warstwy abstrakcji, przy czym dokładną warstwę, do której należy wyjątek, można zakodować w jego typie. Samo użycie „Exception” lub „IllegalStateException” i ciąg komunikatu niewiele tam pomaga.
Felix Dombek
1
Ja też się nie zgadzam. Typy wyjątków powinny mieć sens dla kodu wywołującego, który go wykorzysta. Na przykład, jeśli wywołuję framework, a to powoduje wewnętrznie wyjątek FileDoesNotExistException, to może to nie mieć sensu dla mnie jako wywołującego framework. Zamiast tego lepiej byłoby utworzyć niestandardowy wyjątek i przekazać zgłoszony wyjątek jako wyjątek wewnętrzny.
bytedev