Spis treści Poprzednia strona: XML, czyli dane hierarchiczne Następna strona: Klasy i obiekty
[draft]
Samo złapanie wyjątku nie jest jednoznaczne z jego obsłużeniem. Trzeba napisać sensowny kod w bloku catch aby obsłużyć wyjątek. Sensowny kod oznacza na przykład taki, który nie zawiera interakcji z użytkownikiem ani operacji wejścia/wyjścia. W produkcyjnej wersji aplikacji nie należy łapać wyjątków ogólnych bazowego typu Exception. W testach może się to przydać do wykrycia nieznanych typów wyjątków. W języku Java zawsze wiemy jakie wyjątki są rzucane i mamy obowiązek je co najmniej złapać, więc musimy się niejednokrotnie męczyć z ich obsługą. W C# nie mamy takiego obowiązku i musimy się męczyć z nieprzewidywalnością aplikacji. Wyjątek może oznaczać błąd aplikacji (pomyłkę popełnioną przez programistę podczas pisania programu [ang. bug]) albo błąd wykonania (sytuację niedozwoloną, którą należy przewidzieć i obsłużyć [ang. error]). Obsługa błędu aplikacji polegać powinna na jego logowaniu (zapisaniu do logów aplikacji) i poradzeniu użytkownikowi żeby się skontaktował w tej sprawie z dostawcą aplikacji. Obsługa błędu wykonania powinna zależeć od tego jak bardzo poważny jest to błąd. Na przykład brak uprawnień do odczytu można obsłużyć przez wyświetlenie komunikatu dla użytkownika, ale brak uprawnień do zapisu nie powinien powodować utraty niezapisanych danych i należy rozważyć ich zapisanie gdzie indziej (np. jakaś dodatkowa pamięć buforowa do której wiemy, że mamy dostęp – tak robi z dokumentami na przykład MS Office) lub powtórzenie próby zapisu później, a nawet umożliwienie uzyskania potrzebnych uprawnień bezpośrednio z okna z tym komunikatem. Obsługa zerwanego połączenia z siecią internet może polegać na kilku próbach ponownego nawiązania tego połączenia zanim poinformujemy o tym fakcie użytkownika. Bardzo ważne jest przetestowanie kodu obsługi wyjątku. Staraj się myśleć o nieprzetestowanym wyjątku jak o nieobsłużonym wyjątku. Nie raz zobaczysz, że testy obsługi błędów spowodują wykrycie innych błędów i poprawienie twojej obsługi. Niezłapany wyjątek wcale nie oznacza złej obsługi wyjątków. Taki wyjątek jest przekazywany w górę stosu wywołań i jeśli masz dobrze napisane sekcje finally, to nic złego się nie stanie i wyjątek może zostać złapany na samej górze aplikacji, gdzie – po zapisaniu w logach – albo spowoduje jej zakończenie, albo po prostu wyświetlimy jakiś komunikat i cześć. Łapanie wyjątków na górze aplikacji, tzw GEH [ang. Global Exception Handler] jest ważnym elementem systemu obsługi błędów. W aplikacjach konsolowych całą metodę Main() można zawrzeć w bloku try-catch, w aplikacjach webowych (przeglądarkowych) mamy global.asax, a w web-serwisach AppDomain.CurrentDomain.UnhandledException. Najważniejsza jest decyzja czy bardziej opłaca się obsłużyć wyjątek podejmując jakąś sprytną automatyczną próbę naprawy skutków błędu (która też może się nie powieść), czy może bardziej opłaca się od razu zgłosić błąd i zatrzymać aplikację tak, żeby było wiadomo gdzie błąd wystąpił i żeby można go było od razu poprawić, a nie szukać przyczyn nie wiadomo gdzie i nie wiadomo jak długo. Często kiedy piszemy kod mamy ochotę dodać komentarz mówiący o tym jak należy używać danego fragmentu, jakie parametry przekazać do metody itp. W tym samym momencie kiedy mamy ochotę na taki komentarz powinniśmy oprócz komentarza dodać tzw. asercję. Asercja to wyjątek rzucany wtedy, gdy nagle nie są spełnione jakieś warunki, które spełnione być powinny (a tak przynajmniej zakładamy) na danym etapie przetwarzania. Weźmy pod uwagę, że jesteśmy tylko ludźmi i możemy popełnić błąd w założeniach. Ponadto naszego kodu może użyć inny programista i może nie znać naszych założeń i przez to się do nich nie zastosuje. Ale dzięki asercji zobaczy wyjątek i wszystko stanie się jasne. Asercje oszczędzają nasz czas. Ponadto właśnie asercji najlepiej jest używać do pisania automatycznych testów naszego kodu w specjalnych metodach testowych (patrz NUnit, TDD). Dobry kod powinien działać bezbłędnie pod pewnymi warunkami i te właśnie warunki powinny sprawdzać asercje. Program powinien „wywalać się szybko” [ang. fail-fast], czyli zgłosić wyjątek tam, gdzie faktycznie wystąpił problem, a nie gdzie indziej na skutek przechowanych i nie sprawdzonych wyników częściowych, które już wcześniej były błędne, ale pozwolono im za długo wywierać zgubny wpływ na system. I jeszcze jedno: asercje są dla programistów, a nie dla użytkowników. Tekst błędu asercji powinien być obszerny i dawać wyczerpującą informację na temat kontekstu w jakim dany błąd wystąpił i informację potrzebną do naprawienia problemu. Często zawiera szczegóły techniczne, które nic nie powiedzą użytkownikowi, a tylko go zniechęcą. Błędy asercji należy schować przed użytkownikiem, a rzucanie wyjątków przez asercje powinno zależeć od parametrów kompilacji (np. DEBUG). Logowanie asercji powinno się jednak odbywać zawsze, nawet u klienta w produkcyjnej wersji aplikacji, gdzie będzie łapać błędy trudne do przewidzenia i powtórzenia w warunkach developerskich, a niejednokrotnie także i w warunkach testowych. Takie błędy są wtedy sygnałem do poprawy procedur testowania naszych aplikacji, bo proces testowania przepuścił błędy, których nie powinien. W sytuacjach, gdzie błąd operacji może być krytyczny w skutkach, należy zawsze bezpośrednio przed wykonaniem tej operacji sprawdzić, czy błąd może wystąpić i pozwolić na wykonanie operacji wtedy i tylko wtedy, gdy sprawdzenie stwierdzi poprawność wszystkich warunków. To dzieli błędy krytyczne na błędy dobre i błędy złe. Nawet w systemie, który kontroluje własne błędy może wystąpić błąd krytyczny. Błąd dobry to zaniechanie czynności jeśli zawiedzie sam system kontroli błędów. Błąd zły to taki, który doprowadzi do katastrofy jeśli zawiedzie system kontroli błędów. Przykładem są semafory kolejowe. Światło zielone pozwala wjechać, a czerwone zabrania. A co jeśli nastąpi awaria świateł? Wtedy powinno zawsze świecić się czerwone. A co jeśli nic nie jedzie? Wtedy, również powinno zawsze świecić się czerwone, bo system stwierdzający czy coś jedzie, czy nie mógł ulec awarii. Nawet jeśli nie świeci się nic, to kierujący pojazdem powinien traktować sygnalizator tak, jakby świeciło się światło czerwone. Zasada „dmuchania na zimne”, czyli sprawdzania czy coś może się nie powieść powinna zawsze dotyczyć interakcji z użytkownikiem oraz z innymi elementami systemu, które są nieznanego pochodzenia. Elementy znanego pochodzenia (np. własne) mogą być przetestowane tak, że nie należy się od nich spodziewać niespodziewanych zachowań. Wtedy należy rozważyć wyłączenie niepotrzebnej kontroli błędów, błędów które nigdy nie wystąpią. Niektórzy jednak twierdzą, że nie ma czegoś takiego jak niepotrzebna kontrola błędów. Dlatego mówię o jej wyłączeniu, a nie usunięciu. Jeśli wykonamy daleko idące zmiany w systemie, ta kontrola, pomimo że w działającym systemie niepotrzebna, musi zostać włączona i ponownie przetestowana.
InnerException…
rethrow…
retry…
Spis treści Poprzednia strona: XML, czyli dane hierarchiczne Następna strona: Klasy i obiekty