Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

Handle Unhandled, czyli .NET'owa obsługa krytycznych błędów w kilku krokach

W programowaniu, jak w codziennym życiu, pojawiają się sytuacje całkiem nieprzewidziane. Sytuacje, czy też zdarzenia, na które nie mieliśmy gotowych odpowiedzi na etapie projektowania programu. Lecz człowiek to nie maszyna. O ile w prawdziwym świecie na każde nieprzewidziane zdarzenie mamy szansę choćby "jakoś", nawet prowizorycznie, zareagować (nie dotyczy tragicznych zdarzeń losowych skutkujących definitywnym wyjściem z nieskończonej pętli życia), to już prawie każde nieprzewidziane działanie algorytmów komputerowych zwykle kończy się dla użytkownika drastycznie.

Cóż wtedy czynić? Gdyby taka sytuacja miała miejsce na etapie przygotowywania aplikacji, programista z pewnością rozwiązałby problem. Jeśli natomiast błąd w oprogramowaniu uwidacznia się po stronie użytkownika końcowego - tu sprawa wygląda nieciekawie. Użytkownik po prostu coś wymamrocze pod nosem (zapewne przeklnie dzień, w którym zdecydował się na zakup Twojego programu :) i uruchomi aplikację ponownie. Do czasu następnego błędu, czyli powielenia schematu, który przyczynił się do powstania poprzedniego. I tak w kółko. Setki kilometrów dalej, Ty - dumny programista, spijasz kawę, nieświadomy całej sytuacji. Czasem dostaniesz nieprzyjaznego mejla, czasem ktoś do Ciebie zadzwoni i powie, że "to i to nie działa" i spyta "dlaczego". A Ty powiesz:

"Bo się zepsuło"

Wszyscy dobrze wiemy, że nawet w przypadku niewielkich projektów sprawny system raportowania i przekazywania informacji o błędach to podstawa i uzasadniona konieczność. Owszem, użytkownik może przedstawić zaistniałą sytuację dokładnie, opowiedzieć Tobie co, kiedy i ile razy kliknął. Niemniej będą to tylko wskazówki, samemu naprawdę trudno będzie zrekonstruować takie zdarzenie. A i tak większość użytkowników nie piśnie nawet słowem.

Płatne czy darmowe

Znasz to powiedzenie "jeśli coś jest do wszystkiego...". Naprawdę dobre systemy raportowania błędów sporo kosztują, a rozwiązania darmowe trącą w/w "wszystkim", więc nie tracąc więcej czasu powiem otwarcie - mechanizm raportowania błędów napisz sobie sam. Dzięki temu przygotujesz rozwiązanie dedykowane konkretnej, stworzonej przez Ciebie aplikacji. Rozwiązanie niezależne od zewnętrznych bibliotek, takie w sam raz. I nie jest to szczególnie trudne, jeśli poza znakomitą :) wiedzą z zakresu języka C# znasz choćby podstawy PHP.

Obsługa typowych błędów

Mechanizm obsługi wyjątków w środowisku .NET jest fundamentem całej platformy programistycznej oferowanej nam przez Microsoft. Temat ten jest obszernie i szczegółowo opisany zarówno w MSDN, jak również w wielu pozycjach książkowych dostępnych także w naszym rodzimym języku. Pokrótce sprowadza się do prostej zasady, która mówi, że każdy element kodu, który w Twoim programie może być przyczyną powstania błędu na skutek nieprawidłowych danych wejściowych, braku odpowiednich uprawnień do jego wykonania itp. powinieneś umieścić w bloku try/catch:

try { // Instrukcje, których wykonanie może skutkować // pojawieniem się błędu } catch (...Exception ex) // Typ wyjątku - znakomita większość z przyrostkiem Exception { // Obsługa błędu np. przekazanie użytkownikowi // informacji o nieprawidłowych danych wejściowych } finally { // Zwolnienie zasobów itp. }

Czytelników anglojęzycznych odsyłam do przystępnie napisanego artykułu autorstwa Daniela Turini prezentującego najlepsze praktyki radzenia sobie z wyjątkami w .NET (źródło: CodeProject):Exception Handling Best Practices in .NET

Obsługa wystąpienia wyjątku to bardzo kosztowne przedsięwzięcie z punktu widzenia programisty, któremu zależy na wydajności przygotowywanych aplikacji. Nie można zatem traktować mechanizmu obsługi błędów (w postaci przechwytywania wyjątków) jako prostego mechanizmu zarządzającego kolejnością wykonywania instrukcji w programie. Taką rolę powinny spełniać wyłącznie tradycyjne instrukcje przepływu sterowania (np. instrukcje warunkowe, pętle, funkcje itd.). Właściwa inicjalizacja zmiennych czy też analiza danych wejściowych przed wykonaniem kodu w ciele metody to prawidłowe sposoby radzenia sobie z błędami w większości przypadków. Nie zawsze jednak przewidzisz każdą z możliwych do wystąpienia sytuacji. Wtedy też sięgniesz po opisane powyżej mechanizmy obsługi wyjątków. Jeśli i to zawiedzie, staniesz przed poważnym problemem, którego efektem będzie niespodziewane zakończenie pracy oprogramowania. Pozostanie tylko usunąć błąd i przeprosić użytkowników za zaistniałe niedogodności.

Ostatnia deska ratunku

W sytuacjach, w których nie możesz już liczyć na sprawne (czyt. jakiekolwiek) działanie programu należy zarejestrować wystąpienie błędu wraz z możliwie jak największą ilością danych jednoznacznie wskazujących przyczynę jego wystąpienia. Pozwól więc, że w kolejnych krokach przedstawię Tobie prostą ścieżkę do utworzenia skromnego, ale w pełni funkcjonalnego systemu raportowania krytycznych błędów w środowisku .NET (choć zawsze znajdą się odstępstwa od reguły). Nie przedstawię tu jednak konkretnej implementacji - tę pozostawiam Tobie. Mam nadzieję, że dzięki poniższym wskazówkom poradzisz sobie bez problemu (odpowiednie słowa kluczowe umożliwią Tobie sprawne odnalezienie wszelkich niezbędnych informacji w dokumentacji Microsoftu).

    1. Utwórz publiczną, statyczną klasę (np. Logger) zawierającą prywatne składniki klasy System.Diagnostics.TraceSource oraz klasy System.Diagnostics.TextWriterTraceListener i powiąż je między sobą np. w statycznym konstruktorze klasy Logger. Inicjalizacja obiektu klasy TextWriterTraceListener powinna nastąpić w oparciu o ścieżkę do pliku w specjalnych folderze Environment.SpecialFolder.ApplicationData, aby uniknąć przykrości z systemowym UAC w przypadku Visty i wyżej.

    2. Ustaw pole System.Diagnostics.Trace.AutoFlush, ponieważ jest ono wykorzystywane przez obiekty klasy TraceSource. Następnie utwórz filtr EventTypeFilter(SourceLevels.Critical) dla obiektu TextWriterTraceListener.

    3. Zaimplementuj statyczną metodę LogCritical przez wywołanie na obiekcie klasy TraceSource metody TraceEvent (metoda LogCritical jako argument przyjmować będzie obiekt klasy System.Exception).

    4. Uzupełnij metodę LogCritical o obiekt klasy StringBuilder, przechowujący dane dotyczące wyjątku (przekazanego jako parametr wejściowy metody) oraz wszelkie dane dotyczące środowiska, w którym wystąpił nieobsługiwany wyjątek (odpowiednie pola klasy System.Environment oraz instancji klasy System.Diagnostics.Process.GetCurrentProcess()) a następnie zapisz tak utworzony raport błędu przez wywołanie metody TraceEvent na obiekcie klasy TraceSource.

    5. Przypisz aplikację do zdarzeń AppDomain.UnhandledException oraz Application.ThreadException. W zdarzeniach tych wykorzystaj metodę LogCritical.

    6. Utwórz osobną aplikację Windows Forms (przyjmującą jako parametr wywołania - ścieżkę do pliku na dysku), której zadaniem będzie powiadomienie użytkownika o utworzeniu raportu, wyświetlenie zawartości tegoż raportu wraz z prośbą zarówno o jego uzupełnienie (od strony "okoliczności" pojawienia się błędu) jak i prośbą o wyrażenie zgody na przesłanie pliku do dewelopera, czyli Ciebie.

    7. Uzupełnij obsługę zdarzeń AppDomain.UnhandledException oraz Application.ThreadException o kod odpowiedzialny za uruchomienie - tuż po wywołaniu metody LogCritical - wcześniej utworzonej aplikacji raportujących błędy (System.Diagnostic.Process i metoda Start).

    8. Utwórz skrypt PHP na stronie domowej projektu rejestrujący treść otrzymywanych zapytań wywołanych metodą POST. Przekaż treść tak przesłanego raportu w formie e-maila na adres swojej skrzynki pocztowej.

    9. Uzupełnij (utworzoną osobno) aplikację Windows Forms raportującą błędy o funkcję wykorzystującą na przykład obiekt klasy System.Net.WebClient i jedną z przeciążonych metod UploadString i prześlij za jej pomocą raport o napotkanym błędzie na serwer.

Na koniec, pozostaje tylko szybko przygotować aktualizację i powiadomić użytkowników o pojawieniu się poprawionej wersji oprogramowania. Powodzenia!
 

windows porady programowanie

Komentarze

0 nowych
  #1 23.03.2012 16:53

Przepis wydaje się fajny, ale ja się zawsze przy błędach krytycznych i wszelkiego rodzaju StringBuilderach zastanawiam nad jedną sprawą: co w momencie, kiedy błąd w programie spowodowany jest wyczerpaniem pamięci i błędem alokacji?

W C# czy Javie nie za bardzo da się uniknąć dodatkowych alokacji w kodzie obsługującym błędy krytyczne, ale pisząc kod w C++ zawsze staram się w takich newralgicznych miejscach przydzielania pamięci unikać.

PcSA   4 #2 23.03.2012 18:22

Szkoda, że nie wymieniłeś Podglądu zdarzeń jako sposobu na szukanie przyczyny błędu.

djfoxer   17 #3 23.03.2012 18:41

Jak dobrze, że tworząc aplikację www nie trzeba się w takie rzeczy zbytnio bawić ;)

alucosoftware   7 #4 23.03.2012 18:47

@PcSA
Ale użyłem słowa kluczowego TextWriterTraceListener. Idąc w ślad za nim szybko zauważysz, że .NET domyślnie udostępnia także EventLogTraceListener. Wystarczy, że dodasz go do kolekcji "nasłuchiwaczy" :) swojego obiektu klasy TraceSource i raporty zostaną zapisane także w Podglądzie zdarzeń.

Czy użytkownik końcowy będzie korzystał z Podglądu zdarzeń? Niech lepiej aplikacja wyśle logi do dewelopera...

alucosoftware   7 #5 23.03.2012 18:52

@djfoxer
Nie mów mi, że zajmujesz się tylko deweloperką w sieci... ;P

djfoxer   17 #6 23.03.2012 20:29

@alucosoftware
Masz rację, nie tylko :) Jest jakaś tam mała biblioteczka napisana kiedyś, która wrzuca błędy do pliku tekstowego. Może czas ją przerobić jak opisujesz (oczywiście pewnie nie będzie czasu, ale może kilka chwil ktoś znajdzie). Jak zawsze dobry wpis na poziomie. Thx :)

Używałeś może kiedyś Microsoft Moles?

sanurss   3 #7 23.03.2012 22:20

Kiedyś szukałem tego typu gotowych bibliotek. Oto co znalazłem:
http://code.google.com/p/crashrpt/
http://exceptionreporter.codeplex.com/
http://nbug.codeplex.com/
Prawdę mówiąc nie testowałem jeszcze żadnego z tych narzędzi, bo projekt mam w rozsypce z powodu braku czasu, ale może komuś się przyda ;]

alucosoftware   7 #8 23.03.2012 22:50

@djfoxer
Dzięki za dobre słowo. Nie miałem wcześniej styczności z Microsoft Moles, ale już rozpocząłem proces wchłaniania informacji :) Bardzo ciekawy projekt.

@sanurss
Ciekawe linki, zawsze można posiłkować się zewnętrznym kodem kiedy tylko staniemy w martwym punkcie...

  #9 24.03.2012 09:29

świetny wpis! podoba mi się twój styl pisania. chyba sam spróbuje zrobić takie logowanie błędów dla małych programów. wcześniej używałem NLoga ale może czas spróbować własnych sił.

"Super ważny program" hehe. dobra robota :)

alucosoftware   7 #10 24.03.2012 22:56

@darekj
Dzięki. Jeśli będziesz miał pytania - służę pomocą. Nie zapomnij także i obsługi w/w wyjątków umieścić w bloku try/catch. Nie chciałbyś przecież sytuacji, w której uruchomienie aplikacji raportującej błędy nie powiodło się z powodu jakichś innych nieprawidłowości. Zwróć także uwagę na zachowanie TraceSource'a w sytuacji dostępu z wielu wątków jednocześnie.