Jak uczyć obsługi wyjątków dla nowych programistów? [Zamknięte]

21

W jaki sposób uczysz programistów obsługi wyjątków? Wszystkie inne rzeczy są nauczane w łatwy sposób - Struktury danych, ASP.NET, WinForms, WPF, WCF - nazywasz to, wszystko można łatwo nauczyć.

W przypadku obsługi wyjątków nauczenie ich próbowania w końcu to tylko składniowa natura obsługi wyjątków.

Czego jednak należy nauczyć - Jaką część kodu umieszczasz w bloku try ? Co robisz w bloku catch ?

Pozwól, że zilustruję to przykładem.

Pracujesz nad projektem Windows Forms Project (małe narzędzie) i zaprojektowałeś go tak, jak poniżej, z 3 różnymi projektami.

  1. UILayer
  2. BusinessLayer
  3. DataLayer

Jeśli w DataLayer pojawi się wyjątek (powiedzmy, że ładowanie XDocument zgłasza wyjątek) (UILayer wywołuje BusinessLayer, który z kolei wywołuje DataLayer), czy po prostu wykonaj następujące czynności

//In DataLayer
try {
    XDocument xd_XmlDocument = XDocument.Load("systems.xml");
} 
catch(Exception ex)
{
    throw ex;
}

który jest ponownie wyrzucany w BusinessLayer, a który jest przechwytywany w UILayer, gdzie zapisuję go do pliku dziennika?

Czy tak postępujesz w przypadku obsługi wyjątków?

Kanini
źródło
15
Jeśli miałbyś to zrobić, nie chcesz łapać (Exception ex) {throw ex; } - zamiast tego po prostu złap {rzuć; }
Steven Evers,
4
Nie zapomnijmy wreszcie o blokach?
Chris
1
Powinieneś określić język w tagach. Wchodzisz w szczegóły bardziej niż jest to typowe dla większości implementacji wyjątków i ignorujesz rzeczy poza blokami. Na przykład w C ++ najważniejszą częścią obsługi wyjątków jest umiejętność pisania programów bezpiecznych dla wyjątków.
David Thornley,
Mam nadzieję, że wiesz, że złym pomysłem jest złapanie „wyjątku”. Powinieneś mieć zagnieżdżone bloki catch, aby złapać określone wyjątki i odpowiednio je obsłużyć (nie tylko wyrzucić wyjątek).
minusSeven

Odpowiedzi:

29

Aby objaśnić obsługę wyjątków, wyjaśnij kryjącą się za tym koncepcję: Kod, w którym często występuje błąd, nie wie, jak poprawnie obsługiwać ten błąd. Kod, który wie, jak poprawnie go obsługiwać, może być funkcją, która go wywołała, lub może znajdować się wyżej w stosie wywołań.

Gdy piszesz procedurę wywołującą procedurę, która może zgłosić wyjątek, jeśli wiesz, jak poprawnie obsługiwać ten błąd, umieść wywołanie w bloku try i umieść kod obsługi błędów w bloku catch. Jeśli nie, zostaw to w spokoju i pozwól, aby coś nad tobą w stosie wywołań obsługiło błąd.

Powiedzenie „catch ex, throw ex” nie jest dobrym sposobem na obsługę wyjątków, ponieważ tak naprawdę nic nie obsługuje. Ponadto, w zależności od tego, jak działa model wyjątku w Twoim języku, może to być rzeczywiście szkodliwe, jeśli usunie informacje śledzenia stosu, które można było wykorzystać do debugowania problemu. Po prostu pozwól wyjątkowi propagować stos wywołań, aż natrafi na procedurę, która wie, jak go obsłużyć.

Mason Wheeler
źródło
4
+1 za „... ponieważ tak naprawdę nic nie obsługuje”, ludzie nowi w obsłudze wyjątków tak często myślą, że łapanie oznacza obsługę i nie zdają sobie sprawy, że jeśli nie zrobisz czegoś, aby naprawić problem, to nie jest obsługa wyjątków, po prostu kod wzdęcia.
Jimmy Hoffa
13

Podobnie jak większość rzeczy, wyjątki i obsługa wyjątków będą prawdopodobnie wydawać się rozwiązaniem w poszukiwaniu problemu dla nowych programistów, dopóki nie dowiesz się, dlaczego pozornie prostsze rozwiązanie (kody powrotu w stylu C i errno) działają tak źle. Zacznę od motywowania problemu i umieszczenia go w odpowiednim kontekście. Pokaż, jak można obsługiwać błędy przy użyciu kodów powrotu lub zmiennych globalnych / statycznych. Następnie podaj przykłady, dlaczego nie działa dobrze. Wtedy i tylko wtedy wprowadź wyjątki i wyjaśnij, że są one formą sygnalizacji pozapasmowej i że o to chodzi to, że domyślnym zachowaniem, jeśli zignorujesz wyjątek, jest przekazanie złotówki stosowi wywołań komuś, kto może zajmij się tym.

Konkluzja: Pokazanie, w jaki sposób obsługa błędów została wykonana w C, pozwoli uczniom zrozumieć, na czym naprawdę polegają wyjątki i dlaczego wychwytywanie wyjątków, z którymi tak naprawdę nie można sobie poradzić, to po prostu symulacja sposobu, w jaki działano w czasach ciemnych.

dsimcha
źródło
2
+1 za praktykę nauczania polegającą na wprowadzeniu ich w tradycyjne kody powrotu i numery błędów w stylu C oraz pokazaniu, że działa źle, a zatem nauczenie ich, jak będzie działać, jest genialne!
Kanini,
3
@Kanini: Ogólnie rzecz biorąc, myślę, że większość stosunkowo nowych / wysokich poziomów konstrukcji wydaje się być rozwiązaniami w poszukiwaniu problemów i jest łatwa w użyciu, jeśli nie rozumiesz, jaki problem miały rozwiązać i dlaczego zostały wymyślone.
dsimcha,
Zgadzam się, że pokazanie, jak można to zrobić bez wyjątków, jest dobre, ale potem spoczywa ciężar wyjaśnienia, kiedy stosować wyjątki, a kiedy korzystać z innych technik (ponieważ nie wszystkie sytuacje są wyjątkowe)
Matthieu M.
@Matthieu: Racja. Ale jeśli zrozumiesz, jakie wyjątki od problemów historycznych mają rozwiązać, zamiast poznawać je w próżni, staje się bardziej oczywiste, że głupotą jest używanie ich w wyjątkowych sytuacjach.
dsimcha
właśnie dlatego masz moją +1. Po prostu czułem, że twoją odpowiedź można interpretować jako „nigdy nie używaj innego mechanizmu” :)
Matthieu M.,
5

Zacznę od Wytycznych projektowych dla wyjątków krótkie i obejmują DO, DO NIE i UNIKAJ. Podaje również powody.

W twoim przypadku sekcją odkrywczą byłyby wyjątki pakowania

I spodziewałbym się, że będzie napisane w ten sposób. Zauważ, że wyłapuje określony wyjątek i próbuje dodać informacje, aby propagować bardziej znaczący komunikat. Należy również pamiętać, że wewnętrzny wyjątek jest nadal utrzymywany do celów logowania

//In DataLayer

try
{
XDocument xd_XmlDocument = XDocument.Load("systems.xml");
}
catch(FileNotFoundException ex)
{
        throw new TransactionFileMissingException(
                     "Cannot Access System Information",ex);
}

AKTUALIZACJA Kanini pyta, czy słusznie jest mieć ten blok wyjątków w warstwie danych, czy też sprawdzenie pliku powinno być dostępne dla warstwy biznesowej.

Na początek chciałbym zauważyć, że uzasadnienie owijania wyjątków jest takie

Rozważ zawijanie określonych wyjątków zgłaszanych z dolnej warstwy w bardziej odpowiednim wyjątku, jeśli wyjątek dolnej warstwy nie ma sensu w kontekście operacji na wyższej warstwie.

Więc jeśli uważasz, że wyższa warstwa powinna w ogóle wiedzieć o pliku, wtedy twoja warstwa danych powinna wyglądać tak

//In DataLayer

XDocument xd_XmlDocument = XDocument.Load("systems.xml");

No Try No Catch.

Osobiście uważam, że jeśli twoja warstwa danych nie jest w stanie zrobić czegoś pożytecznego, np. Użyj domyślnego pliku system.xml, który jest zasobem asemblera, nic nie robiąc lub owijając wyjątek jest dobrym wyborem, ponieważ twoje logowanie powie ci, która metoda i jaki plik był problemem. ( throw exw tym przypadku lub preferowane throwteż, ale nie dodaje żadnej wartości). Oznacza to, że po zidentyfikowaniu będziesz w stanie szybko rozwiązać problem.

Jako przykład ten konkretny przykład ma również następujący problem polegający na tym, że XDocument.Load może wyrzucić cztery wykonania

  • ArgumentNullException
  • Wyjątek bezpieczeństwa
  • FileNotFoundException
  • UriFormatException

Nie możemy bezpiecznie zagwarantować, że następujący kod nie zostanie zgłoszony i wyjątek FileNotFoundException, po prostu dlatego, że może on istnieć, gdy sprawdzamy istnienie, i znikać, gdy ładujemy. Udostępnienie tego warstwie biznesowej nie pomogłoby.

 if (File.Exists("systems.xml")) 
     XDocument.Load("systems.xml");

SecurityException jest jeszcze gorszy, ponieważ między innymi powodami wyrzucenia, jeśli inny proces przechwytuje wyłączną blokadę pliku, nie dostaniesz błędu, dopóki nie spróbujesz otworzyć go do odczytu, ponieważ nie ma metody File.CanIOpenThis (). A jeśli taka metoda istniała, nadal masz ten sam problem, co w przypadku File.Exists

Conrad Frix
źródło
Poprawka: dzięki! Ale czy w ogóle słuszne jest posiadanie tego bloku wyjątków w warstwie danych? Czy cała sprawa polega na sprawdzeniu, czy plik jest dostępny, czy też nie w warstwie biznesowej? W przeciwnym razie zgadzam się na twoją metodę pisania kodu.
Kanini
Poprawka: W moim ojczystym języku Kanini oznacza komputer, podczas gdy Kani oznacza owoc ;-)
Kanini
Mogę powiedzieć, że nie jesteś zbyt zdenerwowany moim błędem, ale bardzo mi przykro i poprawiłem go.
Conrad Frix,
1
Poprawka: zdenerwowany? Ani trochę. W dużej mierze rozbawiony. Mój brat nie przestał się śmiać, ponieważ zwróciłem na to uwagę przede wszystkim dlatego, że i tak nie przypominam owocu, może poza dziwnym kształtem ...
Kanini
4

Zróbmy rolę. (to nie jest żart)

Powinieneś zrobić warsztat, w którym odegrasz łańcuch połączeń. Każda osoba jest przedmiotem. Będziesz potrzebować nowicjuszy, a niektórzy ludzie, którzy rozumieją „grę”, pomogą.

Użyj naprawdę prostego problemu, takiego jak plik IO. gui-> model-> plik_io

Osoba będąca czytnikiem plików musi powiedzieć następnej ....

Najpierw zrób to z kodami powrotu. (użyć karteczek samoprzylepnych?)

jeśli interakcje są po prostu „tym, co mówi kod”, już wkrótce można uświadomić ludziom, że wyjątki są wyjątkowe.

w przypadku kodów powrotu przekaż karteczkę samoprzylepną.

wyjątki, podnieście ręce w powietrze i powiedzcie, na czym polega problem.

następnie poproś, aby wykonali polecenie „złap x, wyrzuć x” i zobacz, jak dużo gorzej diagnoza jest taka, jaką otrzymuje GUI „model miał wyjątek”.

Myślę, że to zadziała, by szkolić ludzi, których masz, ponieważ ludzie dość dobrze rozumieją interakcje z innymi ludźmi.

Tim Williscroft
źródło
+1 za pomysł na odgrywanie ról. Nigdy wcześniej o tym nie myśleliśmy. Kto by pomyślał, że nauczanie programowania może być wykonane za pomocą RPG?
Kanini,
1

Wyobrażam sobie, że rozumiem wyjątki, które musisz najpierw zrozumieć na przykład relacje dziecko-rodzic w klasach. Jeśli zrozumiesz, że dziecko może dziedziczyć funkcje od rodzica, może być w stanie na poziomie podstawowym zrozumieć, że jeśli dziecko ma problem, nie może go obsłużyć, przekaże ten problem (wyjątek) swojemu rodzicowi i pozwoli rodzicowi z tym.

Staje się to powiązaniem łańcuchowym, dopóki nie skończysz w miejscu, w którym coś wie, jak poradzić sobie z wyjątkiem.

I do tego stopnia, że ​​w końcu jest to trywialna część ... kiedy pojawia się problem, coś musi sobie z tym poradzić, aby program nie zakończył się fatalnie, po obsłużeniu tego wyjątku jest blok na końcu, który zawsze będzie wykonywany niezależnie od try catch .

Dobrym przykładem może być sieć:

  • nawiązujemy połączenie
  • połączenie jest w porządku, więc go używamy
  • po zakończeniu zamykamy i uwalniamy zasoby

lub w wyjątkowym przypadku:

  • nawiązać połączenie
  • występuje wyjątek, który coś obsługuje
  • w którym momencie zwalniamy połączenie i powiązane zasoby
Chris
źródło
1

Daj aplikację nowicjuszowi, który ma bardzo dobrą obsługę wyjątków. Rzuć gdzieś wyjątek i pozwól mu debugować go za pomocą Logów. Śledząc propagację wyjątku, powinni móc go debugować. Wykonaj to ćwiczenie 3 lub 4 razy. Teraz po prostu usuń całą obsługę wyjątków z kodu i pozwól im spróbować śledzić ten sam wyjątek.

Wierzę, że uznanie dla kodu obsługi wyjątków zostanie natychmiast docenione.

Maniak
źródło
Brzmi jak plan. Czy masz jakiś przykładowy kod dostępny w Internecie (powiedzmy sourceforge.net), który byś polecił?
Kanini,
0

IMO, powinieneś pomyśleć, że obsługa wyjątków i instrukcje kontroli przepływu są w zasadzie takie same. Używasz ich do kontrolowania przepływu swoich programów w zależności od tego, w jakim są obecnie stanie. Różnica polega na tym, że obsługa wyjątków reaguje tylko w przypadku wystąpienia błędu (lub wyjątku).


źródło
@denny: Chociaż zgadzam się z tym, że „obsługa wyjątków zareaguje tylko wtedy, gdy wystąpi błąd (lub wyjątek)”, nie jestem całkiem pewna co do stwierdzenia, że ​​„instrukcje obsługi wyjątków i kontroli przepływu są w zasadzie takie same”. Z szacunkiem się z tym nie zgadzam. Blok catch wprawdzie robi to, co powinien zrobić w tych warunkach. Blok try nie jest jednak wcale związany z przepływem lub kontrolą. Wreszcie blok nie dotyczy wcale przepływu ani kontroli. Być może źle zrozumiałem twoją odpowiedź, ale czy możesz wyjaśnić z korzyścią dla mnie i innych?
Kanini,
0

Prawdopodobnie nie pomogłoby to nowemu programistowi, ale odkryłem, że rozumiałem pojęcie wyjątków znacznie lepiej, kiedy zacząłem używać monad w programowaniu funkcjonalnym. Monada zmusza cię do rozważenia każdego „kanału”, przez który dane mogą wpływać do lub z programu, ponieważ wszystko to zapewnia wygodną abstrakcję, aby „ukryć” część tego przepływu danych.

Pomysł, że funkcja może mieć różne typy danych wyjściowych, a wyjątek jest jak typ powrotu o wyższym priorytecie z funkcji, jest dość zgrabny.

Pamiętaj, że rozumiem, że nie tak działają wyjątki w większości języków (szczegóły implementacji), ale w abstrakcyjnym sensie tak się dzieje.

CodexArcanum
źródło
0

Udawaj, że małpa używa klawiatury

Kiedyś mówiłem moim chłopakom, kiedy piszą kod, aby udawać, że małpa będzie siedziała przy keyboardzie i będzie korzystać z tej aplikacji.

To nauczyło ich przewidywania wszelkiego rodzaju rzeczy:

  • Brakujące dane
  • Brakujące pliki
  • Znaki alfanumeryczne, gdy oczekujesz liczb
  • Dzielenie przez zero

Myślę, że to był obraz słowny, gdy małpa tylko wali w klawisze i robi, co chce, zamiast ładnie podążać. To zadziałało dla mnie.

Michael Riley - AKA Gunny
źródło
Małpy Przypuszczam, że twoi użytkownicy biznesowi nigdy tego nie słyszeli ;-)
Kanini,
@Kanini - Dobry. Tak było w czasach mojego korpusu piechoty morskiej. Chciałem tylko, żeby moi faceci myśleli nieszablonowo, jeśli chodzi o pułapkę błędów. Czy właśnie powiedziałem pułapkę błędów ... Miałem na myśli obsługę wyjątków.
Michael Riley - AKA Gunny