Czy Try / Wreszcie (bez Catch) bąbelkuje wyjątek?

115

Jestem prawie pewien, że odpowiedź brzmi TAK. Jeśli używam bloku Try Final, ale nie używam bloku Catch, to wszelkie wyjątki BĘDĄ bąbelkowe. Poprawny?

Jakieś ogólne przemyślenia na temat praktyki?

Seth

Seth Spearman
źródło

Odpowiedzi:

130

Tak, absolutnie tak. Zakładając finallyoczywiście, że twój blok nie zgłasza wyjątku, w takim przypadku skutecznie "zastąpi" ten, który został pierwotnie rzucony.

Jon Skeet
źródło
13
@David: nie można wrócić z końcowego bloku w C #.
Jon Skeet
3
Dokumentacja msdn również potwierdza tę odpowiedź: Alternatywnie, możesz przechwycić wyjątek, który może zostać wyrzucony w bloku try instrukcji try-last na górze stosu wywołań. Oznacza to, że można złapać wyjątek w metodzie, która wywołuje metodę, która zawiera instrukcję try-last, w metodzie, która wywołuje tę metodę, lub w dowolnej metodzie na stosie wywołań. Jeśli wyjątek nie zostanie przechwycony, wykonanie ostatniego bloku zależy od tego, czy system operacyjny zdecyduje się wyzwolić operację wycofania wyjątku.
szerokopasmowy
60

Jakieś ogólne przemyślenia na temat praktyki?

Tak. Ostrożnie . Kiedy twój ostatni blok jest uruchomiony, jest całkowicie możliwe, że działa, ponieważ został zgłoszony nieobsłużony, nieoczekiwany wyjątek . Oznacza to, że coś jest zepsute i może się wydarzyć coś zupełnie nieoczekiwanego .

W takiej sytuacji prawdopodobnie w ogóle nie powinieneś uruchamiać kodu w końcowych blokach. Kod w bloku końcowym można zbudować tak, aby zakładał, że podsystemy, od których zależy, są w dobrym stanie, podczas gdy w rzeczywistości mogą zostać głęboko uszkodzone. Kod w ostatnim bloku może pogorszyć sytuację.

Na przykład często widzę takie rzeczy:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

Autor tego kodu myśli: „Dokonuję tymczasowej mutacji stanu świata; muszę przywrócić stan, jaki był przed wezwaniem”. Ale zastanówmy się, jak może to się nie udać.

Po pierwsze, wywołujący mógł już zablokować dostęp do zasobu; w takim przypadku ten kod włącza go ponownie, prawdopodobnie przedwcześnie.

Po drugie, jeśli DoSomethingToTheResource zgłasza wyjątek, czy jest właściwe, aby umożliwić dostęp do zasobu ??? Kod zarządzający zasobem jest nieoczekiwanie uszkodzony . Ten kod mówi, że w efekcie „jeśli kod zarządzający jest uszkodzony, upewnij się, że inny kod może wywołać ten uszkodzony kod tak szybko, jak to możliwe, tak aby również mógł się strasznie zawieść ”. Wydaje się, że to zły pomysł.

Po trzecie, jeśli DoSomethingToTheResource zgłasza wyjątek, to skąd wiemy, że EnableAccessToTheResource również nie zgłosi wyjątku? Jakiekolwiek okropności, jakie spotkały użycie zasobu, mogą również wpłynąć na kod porządkujący, w którym to przypadku oryginalny wyjątek zostanie utracony, a problem będzie trudniejszy do zdiagnozowania.

Zwykle piszę taki kod bez używania bloków try-last:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

Teraz stan nie jest zmutowany, chyba że musi. Teraz stan dzwoniącego nie jest zmieniany. A teraz, jeśli DoSomethingToTheResource zawiedzie, nie włączamy ponownie dostępu. Zakładamy, że coś jest głęboko zepsute i nie ryzykujemy pogorszenia sytuacji, próbując utrzymać działający kod. Jeśli to możliwe, pozwól dzwoniącemu rozwiązać problem.

Kiedy więc warto uruchomić ostatecznie blok? Po pierwsze, kiedy oczekuje się wyjątku. Na przykład można się spodziewać, że próba zablokowania pliku może się nie powieść, ponieważ ktoś inny go zablokował. W takim przypadku sensowne jest przechwycenie wyjątku i zgłoszenie go użytkownikowi. W takim przypadku niepewność co do tego, co jest zepsute, jest zmniejszona; raczej nie pogorszysz sytuacji, posprzątając.

Po drugie, gdy oczyszczany zasób jest rzadkim zasobem systemowym. Na przykład sensowne jest zamknięcie uchwytu pliku w końcowym bloku. („Używanie” to oczywiście tylko inny sposób zapisania bloku try-Final.) Zawartość pliku może być uszkodzona, ale nie możesz teraz nic na to poradzić. Uchwyt pliku zostanie ostatecznie zamknięty, więc równie dobrze może to nastąpić raczej wcześniej niż później.

Eric Lippert
źródło
7
Jeszcze więcej przykładów tego, jak naprawdę nie zebraliśmy się jeszcze jako branża, jeśli chodzi o obsługę błędów. Nie znaczy to, że mam coś lepszego do zasugerowania niż wyjątki, ale mam nadzieję, że w przyszłości będzie coś, co będzie bardziej prawdopodobne, że właściwy kierunek działań zostanie podjęty w miarę łatwo.
Jon Skeet
W vb.net kod w bloku Final może wiedzieć, który wyjątek oczekuje. Jeśli ustawi się hierarchię wyjątków, aby odróżnić „nie zrobił tego; stan w przeciwnym razie w porządku” od „stan jest uszkodzony”, można uruchomić blok Final tylko wtedy, gdy oczekujący wyjątek nie jest jednym z złych. Lubię, gdy procedury usuwania / czyszczenia wychwytują wyjątki i zgłaszają wyjątek DisposerFailedException (który należy do kategorii „zły” i zawiera oryginalny wyjątek jako wyjątek InnerException). Chciałbym zobaczyć standardowy interfejs iDisposableEx z Dispose (Ex as Exception), aby to ułatwić.
supercat
Chociaż zdaję sobie sprawę, że są przypadki, w których może wystąpić wyjątek, gdy obiekt jest w złym stanie, a próba czyszczenia pogorszy sytuację, myślę, że takie problemy powinny być obsługiwane w kodzie czyszczenia, być może z pomocą delegatów. Na przykład otoka blokady może uwidocznić delegata funkcji „CheckIfSafeToUnlock (Ex as Exception)”, który można ustawić, gdy zablokowany obiekt jest w nieprawidłowym stanie i wyczyszczony w inny sposób. Przed zwolnieniem blokady opakowanie mogłoby sprawdzić delegata; jeśli nie jest pusty, może go uruchomić i zwolnić blokadę tylko wtedy, gdy zwróci wartość true.
supercat
Posiadanie opakowania używającego takiego delegata pozwoliłoby kodowi, który manipulował stanem obiektu, na wybranie odpowiedniej akcji czyszczenia, jeśli zostanie zakłócona. Takie działania mogą obejmować naprawę struktury danych i zwrócenie wartości True, zgłoszenie kolejnego „poważniejszego” wyjątku, który zawija oryginalny, lub pozwolenie na przesączanie się oryginalnego wyjątku podczas zwracania False.
supercat
2
Eric, dzięki za świetną odpowiedź. Chyba powinienem był zadać dwa pytania, ponieważ zarówno ty, jak i Jon macie rację. W każdym razie jesteś za. Dziękuję za zainteresowanie pytaniem.
Seth Spearman