Kto powinien przeczytać wyjątek. Wiadomość, jeśli w ogóle?

27

Czy projektując wyjątki, powinienem pisać wiadomości zrozumiałe dla użytkownika lub programisty? Kto powinien być czytelnikiem komunikatów o wyjątkach?

Uważam, że komunikaty o wyjątkach w ogóle nie są przydatne i zawsze trudno mi je pisać. Zgodnie z konwencją typ wyjątku powinien już informować nas, dlaczego coś nie działało, a właściwości niestandardowe mogą dodawać jeszcze więcej informacji, takich jak nazwy plików, indeksy, klucze itp., Więc po co powtarzać je w samym komunikacie? Wiadomość wygenerowana automatycznie może również zrobić, a wszystko, co musi zawierać, to nazwa wyjątku z listą dodatkowych właściwości. Byłoby to tak samo przydatne jak odręczny tekst.

Czy nie lepiej byłoby w ogóle nie pisać wiadomości, ale zamiast tego miały specjalne renderery wyjątków, które zajmują się tworzeniem znaczących wiadomości, być może w różnych językach, a nie kodowaniem ich na stałe w kodzie?


Zapytano mnie, czy którekolwiek z tych pytań stanowią odpowiedź na moje pytanie:

Przeczytałem je oba i nie byłem zadowolony z ich odpowiedzi. Mówią ogólnie o użytkownikach i koncentrują się na treści samej wiadomości, a nie na adresacie, i okazuje się, że może być co najmniej dwóch: użytkownik końcowy i programista. Nigdy nie wiem, z kim powinienem porozmawiać, pisząc komunikaty o wyjątkach.

Myślę nawet, że słynna wiadomość nie ma żadnej realnej wartości, ponieważ po prostu powtarza nazwę typu wyjątku innymi słowami, więc po co w ogóle pisać? Mogę idealnie wygenerować je automatycznie.

Dla mnie wiadomość wyjątkowa nie ma zróżnicowania swoich czytelników. Idealny wyjątek musiałyby zapewnić co najmniej dwie wersje wiadomości: jedną dla użytkownika końcowego i jeden dla dewelopera. Nazywanie tego po prostu wiadomością jest zbyt ogólne. Wiadomość programisty powinna być następnie napisana w języku angielskim, ale wiadomość użytkownika końcowego może wymagać przetłumaczenia na inne języki. Nie da się tego wszystkiego osiągnąć za pomocą tylko jednej wiadomości, więc wyjątek musiałby zawierać pewien identyfikator wiadomości użytkownika końcowego, który, jak już powiedziałem, może być dostępny w różnych językach.

Kiedy czytam wszystkie inne powiązane pytania, mam wrażenie, że komunikat o wyjątku rzeczywiście jest przeznaczony do przeczytania przez użytkownika końcowego, a nie programistę ... pojedyncza wiadomość jest jak zjeść ciasto i zjeść je.

t3chb0t
źródło
4
Tutaj przychodzą mi na myśl dwa inne pytania dotyczące programistów. Nie jestem pewien, czy są duplikatami, czy nie, ale myślę, że powinieneś je przeczytać i być może odpowiedzą one na niektóre lub nawet wszystkie Twoje obawy: Jak napisać dobry komunikat o wyjątku i Dlaczego wiele komunikatów o wyjątkach nie zawiera przydatnych szczegółów ?
Thomas Owens
2
@ThomasOwens Przeczytałem je oba, ale nie zwracają się do konkretnego odbiorcy komunikatu wyjątku. Mówią ogólnie o użytkownikach, ale kim on jest? Czy to użytkownik oprogramowania ma teraz pomysł na programowanie, czy programiści powinni przeanalizować, co poszło nie tak? Nigdy nie jestem pewien, do kogo mam się zwrócić podczas pisania wiadomości.
t3chb0t
2
Myślę, że dobra odpowiedź tutaj może odnosić się do jednego lub obu tych pytań, ale daleko im do duplikatów.
Wyścigi lekkości z Moniką
1
Dlaczego, u licha, było to zamknięte jako duplikat? Nawet jeśli byłby duplikatem tego drugiego pytania (które nie jest), to na pewno ma lepszą odpowiedź, powinno być to drugie oznaczone jako duplikat.
Ben Aaronson
2
@MichaelT Nadal tak naprawdę nie widzę wielu nakładek, chyba że założysz, że wiadomości o wyjątkach są dla użytkowników
Ben Aaronson

Odpowiedzi:

46

Te wiadomości są dla innych programistów

Wiadomości te powinny zostać odczytane przez programistów, aby pomóc im w debugowaniu aplikacji. Może to przybierać dwie formy:

  • Aktywne debugowanie. W rzeczywistości uruchamiasz debuger podczas pisania kodu i próbujesz dowiedzieć się, co się stanie. W tym kontekście przydatny wyjątek poprowadzi cię, ułatwiając zrozumienie, co się dzieje źle, lub ewentualnie sugerując obejście (chociaż jest to opcjonalne).

  • Pasywne debugowanie. Kod działa w środowisku produkcyjnym i kończy się niepowodzeniem. Wyjątek jest rejestrowany, ale otrzymujesz tylko komunikat i ślad stosu. W tym kontekście pomocny komunikat wyjątku pomoże ci szybko zlokalizować błąd.

    Ponieważ te wiadomości są często rejestrowane, oznacza to również, że nie należy w nich umieszczać poufnych informacji (takich jak klucze prywatne lub hasła, nawet jeśli użyteczne byłoby debugowanie aplikacji).

Na przykład IOSecurityExceptionrodzaj wyjątku zgłaszanego podczas zapisywania pliku nie jest zbyt jasny na temat problemu: czy to dlatego, że nie mamy uprawnień dostępu do pliku? A może możemy to przeczytać, ale nie napisać? A może plik nie istnieje i nie mamy uprawnień do tworzenia plików? A może jest zablokowany (mam nadzieję, że w tym przypadku wyjątek będzie inny, ale w praktyce typy mogą być czasami tajemnicze). A może Code Access Security uniemożliwia nam wykonanie jakiejkolwiek operacji we / wy?

Zamiast:

IOSecurityException: plik został znaleziony, ale odmówiono pozwolenia na odczyt jego zawartości.

jest o wiele bardziej wyraźny. W tym miejscu natychmiast wiemy, że uprawnienia do katalogu są ustawione poprawnie (w przeciwnym razie nie będziemy mogli wiedzieć, że plik istnieje), ale uprawnienia na poziomie pliku są problematyczne.

Oznacza to również, że jeśli nie możesz podać żadnych dodatkowych informacji, które nie są jeszcze w typie wyjątku, możesz pozostawić wiadomość pustą. DivisionByZeroExceptionjest dobrym przykładem, w którym wiadomość jest zbędna. Z drugiej strony fakt, że większość języków pozwala na zgłoszenie wyjątku bez określania jego komunikatu, dzieje się z innego powodu: albo dlatego, że domyślny komunikat jest już dostępny, albo dlatego, że zostanie wygenerowany później, jeśli zajdzie taka potrzeba (i wygenerowanie tego wiadomość jest zawarta w typie wyjątku, co ma sens, mówiąc „OOPly” ).

Pamiętaj, że ze względów technicznych (często związanych z wydajnością) niektóre wiadomości są o wiele bardziej tajemnicze niż powinny. .NET NullReferenceException:

Odwołanie do obiektu nie jest ustawione na instancję obiektu.

jest doskonałym przykładem wiadomości, która nie jest pomocna. Pomocną wiadomością byłoby:

Odwołanie do obiektu productnie jest ustawione na instancję obiektu po wywołaniu product.Price.

Te wiadomości nie są dla użytkowników końcowych!

Użytkownicy końcowi nie powinni widzieć komunikatów wyjątków. Nigdy. Chociaż niektórzy programiści kończą pokazywanie tych wiadomości użytkownikom, prowadzi to do słabego doświadczenia użytkownika i frustracji. Wiadomości takie jak:

Odwołanie do obiektu nie jest ustawione na instancję obiektu.

dla użytkownika końcowego absolutnie nic i należy go za wszelką cenę unikać.

Najgorszym scenariuszem jest globalny try / catch, który zgłasza wyjątek użytkownikowi i wychodzi z aplikacji. Aplikacja, która dba o swoich użytkowników:

  • W pierwszej kolejności obsługuje wyjątki. Większość z nich można obsługiwać bez przeszkadzania użytkownikowi. Sieć nie działa? Dlaczego nie poczekać kilka sekund i spróbować ponownie?

  • Zapobiega prowadzeniu aplikacji przez użytkownika do wyjątkowego przypadku. Jeśli poprosisz użytkownika o wprowadzenie dwóch liczb i podzielenie pierwszej przez drugą, dlaczego miałbyś pozwolić użytkownikowi wprowadzić zero w drugim przypadku, aby obwinić go kilka sekund później? Co powiesz na wyróżnienie pola tekstowego na czerwono (z pomocną podpowiedzią, że liczba powinna być różna od zera) i wyłączenie przycisku sprawdzania poprawności, dopóki pole nie pozostanie czerwone?

  • Zaprasza użytkownika do wykonania akcji w formie, która nie jest błędem. Nie masz wystarczających uprawnień, aby uzyskać dostęp do pliku? Dlaczego nie poprosić użytkownika o przyznanie uprawnień administracyjnych lub wybrać inny plik?

  • Jeśli nic więcej nie działa, pokazuje pomocny, przyjazny błąd, który został napisany specjalnie w celu zmniejszenia frustracji użytkownika, pomaga użytkownikowi zrozumieć, co poszło źle i ostatecznie rozwiązuje problem, a także pomaga mu zapobiec błędowi w przyszłości (jeśli dotyczy).

    W swoim pytaniu zasugerowałeś dwa wyjątki: techniczny dla programistów i jeden dla użytkowników końcowych. Chociaż jest to ważna sugestia w niektórych drobnych przypadkach, większość wyjątków powstaje na poziomie, na którym niemożliwe jest przedstawienie znaczącej wiadomości dla użytkowników. Wziąć DivisionByZeroExceptioni wyobrazić sobie, że nie mógł zapobiec wyjątek od tego wydarzenia i nie może poradzić sami. Kiedy podział występuje, czy środowisko (ponieważ jest to środowisko, a nie kod biznesowy, który generuje wyjątek) wie, co będzie użytecznym komunikatem dla użytkownika? Absolutnie nie:

    Wystąpił podział przez zero. [DOBRZE]

    Zamiast tego można pozwolić, aby wyrzucił wyjątek, a następnie złapał go na wyższym poziomie, na którym znaliśmy kontekst biznesowy i mogliśmy działać odpowiednio, aby faktycznie pomóc użytkownikowi końcowemu, pokazując w ten sposób:

    Pole D13 nie może mieć wartości identycznej z wartością w polu E6, ponieważ odejmowanie tych wartości jest używane jako dzielnik. [DOBRZE]

    albo może:

    Wartości zgłaszane przez usługę ATP są niezgodne z lokalnymi danymi. Może to być spowodowane brakiem synchronizacji danych lokalnych. Czy chcesz zsynchronizować informacje o wysyłce i spróbować ponownie? [Tak nie]

Te wiadomości nie są przeznaczone do analizowania

Komunikaty o wyjątkach również nie powinny być analizowane ani wykorzystywane programowo. Jeśli uważasz, że dzwoniący może potrzebować dodatkowych informacji, dołącz je do wyjątku obok wiadomości. Jest to ważne, ponieważ wiadomość może ulec zmianie bez powiadomienia. Typ jest częścią interfejsu, ale komunikat nie jest: nigdy nie należy polegać na nim w przypadku obsługi wyjątków.

Wyobraź sobie komunikat wyjątku:

Upłynął limit czasu połączenia z serwerem buforowania po odczekaniu 500 ms. Zwiększ limit czasu lub sprawdź monitorowanie wydajności, aby zidentyfikować spadek wydajności serwera. Średni czas oczekiwania na buforowanie serwera wynosił 6 ms. za ostatni miesiąc, 4 ms. za ostatni tydzień i 377 ms. za ostatnią godzinę.

Chcesz wyodrębnić wartości „500”, „6”, „4” i „377”. Pomyśl trochę o podejściu, którego użyjesz do parsowania, a dopiero potem kontynuuj czytanie.

Masz pomysł Świetny.

Teraz oryginalny programista odkrył literówkę:

Connecting to the caching sever timed out after waiting [...]

powinno być:

                            ↓
Connecting to the caching server timed out after waiting [...]

Co więcej, programista uważa, że ​​miesiąc / tydzień / godzina nie są szczególnie istotne, więc wprowadza również dodatkową zmianę:

Średni czas oczekiwania na buforowanie serwera wynosił 6 ms. przez ostatni miesiąc, 5 ms. przez ostatnie 24 godziny i 377 ms. za ostatnią godzinę.

Co dzieje się z parsowaniem?

Zamiast analizować, możesz użyć właściwości wyjątku (które mogą zawierać wszystko, co tylko chcesz, gdy tylko dane zostaną przekształcone do postaci szeregowej):

{
    message: "Connecting to the caching [...]",
    properties: {
        "timeout": 500,
        "statistics": [
            { "timespan": 1, "unit": "month", "average-timeout": 6 },
            { "timespan": 7, "unit": "day", "average-timeout": 4 },
            { "timespan": 1, "unit": "hour", "average-timeout": 377 },
        ]
    }
}

Jak łatwo jest teraz korzystać z tych danych?

Czasami (np. W .NET) wiadomość może być nawet przetłumaczona na język użytkownika (IMHO, tłumaczenie tych wiadomości jest absolutnie niepoprawne, ponieważ oczekuje się, że każdy programista będzie mógł czytać w języku angielskim). Analiza takich wiadomości jest prawie niemożliwa.

Arseni Mourzenko
źródło
2
Dobra uwaga na temat użytkownika końcowego. Dodam, że ze względów bezpieczeństwa użytkownik końcowy również nie powinien widzieć komunikatów o wyjątkach. Znając dokładny charakter błędu dla użytkownika nie będącego laikiem, można przedstawić exploita. Użytkownik końcowy powinien znać błąd tylko w kontekście tego, co robił.
Neil,
1
@ Neil: to trochę zbyt teoretycznie. W praktyce korzyści płynące z ukrywania dzienników przed użytkownikiem przeważa złożoność rejestrowania online. Nie licząc aspektu przyjaznego dla użytkownika. Wyobraź sobie, że instalujesz program Visual Studio na nowej maszynie wirtualnej z systemem Windows. Nagle instalacja kończy się niepowodzeniem. Czy wolisz po prostu przejść do dzienników i samodzielnie zdiagnozować problem (przy pomocy Stack Exchange i blogów), czy poczekać, aż Microsoft rozwiąże problem i opublikuje poprawkę?
Arseni Mourzenko
4
Myślę, że wszyscy zdają sobie sprawę, że komunikat „odniesienie do obiektu ...” jest nieprzydatny. Istotne pytanie brzmi jednak: jaki kod maszynowy chcesz wygenerować jittera, który generuje żądany komunikat? Jeśli wykonasz to ćwiczenie, szybko odkryjesz, że komunikatu nie można łatwo wygenerować bez ogromnego kosztu wydajności nałożonego na wszystkie wyjątkowe przypadki . Korzyści wynikające z uczynienia pracy nieostrożnego programisty nieco łatwiejszymi nie są opłacane przez wydajność osiągniętą przez użytkownika końcowego.
Eric Lippert,
7
Odnośnie wyjątków bezpieczeństwa: im mniej informacji w wyjątku, tym lepiej. Moja ulubiona anegdota mówi, że we wcześniejszej wersji .NET 1.0 można było otrzymać komunikat o wyjątku, taki jak: „Nie masz uprawnień do określenia nazwy pliku C: \ foo.txt”. Świetnie, dziękuję za ten szczegółowy komunikat wyjątku!
Eric Lippert,
2
Nie zgadzam się z tym, że nigdy nie pokazuję użytkownikowi końcowemu szczegółowego komunikatu o błędzie. Stało się dla mnie wiele razy, że mogę dostać tajemniczy komunikat o błędzie uniemożliwia mi uruchomienie moja gra / oprogramowania, ale kiedy google to znajdę tam to łatwe obejście. Bez tego komunikatu o błędzie nie byłoby sposobu na znalezienie obejścia. Może lepiej po prostu ukryć szczegóły pod przyciskiem „więcej szczegółów”?
BlueRaja - Danny Pflughoeft
2

Odpowiedź na to zależy całkowicie od wyjątku.

Najważniejsze pytanie, które należy zadać, to: „kto może rozwiązać problem?” Jeśli wyjątek jest spowodowany błędem systemu plików podczas otwierania pliku, sensowne może być przekazanie tej wiadomości użytkownikowi końcowemu. Komunikat może zawierać więcej informacji o tym, jak naprawić błąd. Mogę wymyślić jeden ostateczny przypadek w mojej pracy, w którym miałem system wtyczek, który ładował biblioteki DLL. Jeśli użytkownik popełnił literówkę w wierszu polecenia, aby załadować niewłaściwą wtyczkę, podstawowy komunikat o błędzie systemowym faktycznie zawierał przydatne informacje, które pozwolą mu rozwiązać problem.

Jednak przez większość czasu użytkownik nie może usunąć nieprzechwyconego wyjątku. Większość z nich wymaga wprowadzania zmian w kodzie. W takich przypadkach konsument wiadomości jest wyraźnie programistą.

Przypadek przechwyconych wyjątków jest bardziej skomplikowany, ponieważ masz możliwość prawidłowego przetworzenia wyjątku i rzucenia bardziej przyjaznego. Jeden język skryptowy, który napisałem, wyrzucił wyjątki, których przesłanie było wyraźnie przeznaczone dla programisty. Jednak gdy stos się rozwinął, dodałem czytelne dla użytkownika ślady stosu z poziomu języka skryptowego. Moi użytkownicy bardzo rzadko potrafili odczytać komunikat, ale mogli sprawdzić ślad stosu w swoim skrypcie i dowiedzieć się, co się dzieje w 95% przypadków. Przez pozostałe 5%, kiedy do mnie zadzwonili, mieliśmy nie tylko ślad stosu, ale także wiadomość przygotowaną przez programistę, która pomogła mi ustalić, co było nie tak.

Podsumowując, wiadomość o wyjątku traktuję jako nieznaną treść. Ktoś dalej, kto wiedział więcej o implementacjach, podał mojemu oprogramowaniu wyjątek. Czasami mam przeczucie, że nieznana treść będzie przydatna dla użytkownika końcowego, a czasem nie.

Cort Ammon - Przywróć Monikę
źródło
1
„Jeśli wyjątek jest spowodowany błędem systemu plików podczas otwierania pliku, sensowne może być przekazanie tej wiadomości użytkownikowi końcowemu”: ale kiedy zgłaszasz błąd systemu plików, nie znasz kontekstu: może to być akcja użytkownika lub coś zupełnie niezwiązanego (na przykład twoja aplikacja wykonująca kopię zapasową jakiejś konfiguracji, nawet jeśli użytkownik tego nie zauważy). Oznacza to, że będziesz mieć blok try / catch, który wyświetla komunikat o błędzie , który różni się bardzo od bezpośredniego wyświetlania komunikatu o wyjątku .
Arseni Mourzenko
@MainMa Możesz nie znać kontekstu wyraźnie, ale jest mnóstwo wskazówek. Na przykład, jeśli są w trakcie otwierania pliku, a wyjątkiem jest „IOError: odmowa uprawnień”, istnieje uzasadniona szansa, że ​​użytkownik może połączyć 2 i 2 razem i dowiedzieć się, o który plik chodzi. Mój ulubiony edytor to robi - po prostu wyświetla tekst wyjątku w oknie dialogowym, bez puchu. Z drugiej strony, jeśli ładowałem aplikację i otrzymałem „odmowę zgody” podczas ładowania wiązki obrazów, o wiele mniej rozsądne jest założenie, że użytkownik może zrobić coś z tymi informacjami.
Cort Ammon - Przywróć Monikę
Argumentując za tym, nigdy nie widziałem żadnej wartości w pokazywaniu użytkownikowi wyjątku NullReferenceException, poza tym, że sam fakt wyświetlenia tego komunikatu pokazuje, że twoje oprogramowanie nie miało pojęcia, jak go odzyskać! Ten komunikat wyjątku nigdy nie jest użyteczny dla użytkownika końcowego.
Cort Ammon - Przywróć Monikę