Analizatory kodu statycznego, takie jak Fortify, „narzekają”, gdy wyjątek może zostać zgłoszony do finally
bloku, mówiąc to Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally
. Zwykle się z tym zgadzam. Ale ostatnio natknąłem się na ten kod:
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} catch (...) {
//exception handling
} finally {
if (writer!= null) writer.close();
}
Teraz, jeśli writer
nie można poprawnie zamknąć, writer.close()
metoda zgłosi wyjątek. Należy zgłosić wyjątek, ponieważ (najprawdopodobniej) plik nie został zapisany po zapisaniu.
Mógłbym zadeklarować dodatkową zmienną, ustawić ją, jeśli wystąpił błąd podczas zamykania writer
i zgłosić wyjątek po bloku w końcu. Ale ten kod działa dobrze i nie jestem pewien, czy go zmienić, czy nie.
Jakie są wady rzucenia wyjątku wewnątrz finally
bloku?
Odpowiedzi:
Zasadniczo
finally
istnieją klauzule zapewniające prawidłowe zwolnienie zasobu. Jeśli jednak w ostatnim bloku zostanie zgłoszony wyjątek, gwarancja zniknie. Co gorsza, jeśli główny blok kodu zgłasza wyjątek, wyjątek zgłoszony wfinally
bloku ukryje go. Wygląda na to, że błąd został wywołany przez wywołanieclose
, a nie z prawdziwego powodu.Niektóre osoby stosują paskudny wzór zagnieżdżonych procedur obsługi wyjątków, połykając wszelkie wyjątki zgłoszone w
finally
bloku.W starszych wersjach Java możesz „uprościć” ten kod, owijając zasoby w klasy, które wykonują to „bezpieczne” czyszczenie. Mój dobry przyjaciel tworzy listę anonimowych typów, z których każdy zapewnia logikę oczyszczania swoich zasobów. Następnie jego kod po prostu zapętla listę i wywołuje metodę dispose w
finally
bloku.źródło
To, co powiedział Travis Parks, jest prawdą, że wyjątki w
finally
bloku wykorzystują wszystkie zwracane wartości lub wyjątki odtry...catch
bloków.Jeśli jednak używasz Java 7, problem można rozwiązać za pomocą bloku try-with-resources . Zgodnie z dokumentacją, o ile Twój zasób implementuje
java.lang.AutoCloseable
(większość pisarzy / czytelników robi to teraz), blok try-with-resources zamknie go dla ciebie. Dodatkową korzyścią jest to, że każdy wyjątek występujący podczas zamykania zostanie pominięty, umożliwiając pominięcie oryginalnej wartości zwracanej lub wyjątku.Z
Do
http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
źródło
close()
. „każdy wyjątek, który wystąpi podczas zamykania, zostanie pominięty, umożliwiając pominięcie pierwotnej wartości zwracanej lub wyjątku” - to po prostu nieprawda . Wyjątek odclose()
metody zostanie pominięty tylko wtedy, gdy inny wyjątek zostanie zgłoszony z bloku try / catch. Zatem jeślitry
blok nieclose()
zgłosi żadnego wyjątku, wyjątek od metody zostanie zgłoszony (a zwracana wartość nie zostanie zwrócona). Można go nawet złapać z bieżącegocatch
bloku.Myślę, że jest to kwestia, którą należy rozwiązać indywidualnie dla każdego przypadku. W niektórych przypadkach to, co mówi analizator, jest poprawne, ponieważ kod, który posiadasz, nie jest tak świetny i wymaga ponownego przemyślenia. Ale mogą być inne przypadki, w których rzucanie lub nawet ponowne rzucanie może być najlepszą rzeczą. To nie jest coś, co możesz zlecić.
źródło
Będzie to bardziej konceptualna odpowiedź na pytanie, dlaczego te ostrzeżenia mogą istnieć nawet przy podejściu typu „spróbuj z zasobami”. Niestety nie jest to łatwe rozwiązanie, na które możesz liczyć.
Odzyskiwanie po błędzie nie może zakończyć się niepowodzeniem
finally
modeluje przepływ kontrolny po transakcji, który jest wykonywany niezależnie od tego, czy transakcja się powiedzie, czy nie.W przypadku fail,
finally
przechwytuje logiczne, że jest wykonywany w środku odzyskania z błędu, zanim został on w pełni sprawny (zanim osiągniemy naszcatch
cel).Wyobraźmy sobie, że problemy koncepcyjne prezentuje do wystąpienia błędu w środku odzyskania z błędu.
Wyobraź sobie serwer bazy danych, w którym próbujemy dokonać transakcji, a transakcja kończy się niepowodzeniem (powiedzmy, że serwerowi zabrakło pamięci w środku). Teraz serwer chce wycofać transakcję do punktu, tak jakby nic się nie stało. Wyobraź sobie jednak, że napotyka kolejny błąd w procesie wycofywania. Teraz mamy do czynienia z transakcją w połowie zatwierdzoną w bazie danych - atomowość i niepodzielny charakter transakcji jest teraz zepsuty, a integralność bazy danych zostanie teraz zagrożona.
Ten problem koncepcyjny występuje w każdym języku, który zajmuje się błędami, niezależnie od tego, czy jest to C z ręczną propagacją kodu błędu, C ++ z wyjątkami i destruktorami, czy Java z wyjątkami i
finally
.finally
nie może zawieść w językach, które zapewniają to w ten sam sposób, w jaki niszczyciele nie mogą zawieść w C ++ podczas napotkania wyjątków.Jedynym sposobem na uniknięcie tego koncepcyjnego i trudnego problemu jest upewnienie się, że proces wycofywania transakcji i zwalniania zasobów w środku nie może napotkać rekurencyjnego wyjątku / błędu.
Zatem jedynym bezpiecznym projektem jest tutaj projekt, w którym
writer.close()
nie może zawieść. Zazwyczaj istnieją sposoby, aby uniknąć scenariuszy, w których takie rzeczy mogą zawieść w trakcie odzyskiwania, co uniemożliwia.Jest to niestety jedyny sposób - odzyskiwanie po błędzie nie może zakończyć się niepowodzeniem. Najprostszym sposobem, aby to zapewnić, jest uniemożliwienie tego rodzaju „uwolnienia zasobów” i „odwrotnych efektów ubocznych”. To nie jest łatwe - prawidłowe odzyskiwanie błędów jest trudne, a także niestety trudne do przetestowania. Ale sposobem na osiągnięcie tego jest upewnienie się, że wszelkie funkcje, które „niszczą”, „zamykają”, „zamykają”, „wycofują” itp., Nie mogą napotkać zewnętrznego błędu w procesie, ponieważ takie funkcje często będą musiały zostać wywołanym w trakcie odzyskiwania po istniejącym błędzie.
Przykład: rejestrowanie
Powiedzmy, że chcesz zalogować rzeczy w
finally
bloku. Jest to często poważny problem, chyba że rejestracja nie powiedzie się . Logowanie prawie na pewno może się nie powieść, ponieważ może chcieć dołączyć więcej danych do pliku, a to może łatwo znaleźć wiele przyczyn niepowodzenia.Rozwiązaniem jest tutaj, aby żadna funkcja rejestrowania używana w
finally
blokach nie mogła przekazać obiektu wywołującego (może się nie powieść, ale nie wyrzuci). Jak możemy to zrobić? Jeśli twój język pozwala na rzucanie w kontekście pod warunkiem, że istnieje zagnieżdżony blok try / catch, byłby to jeden ze sposobów uniknięcia rzucania dzwoniącemu przez połykanie wyjątków i zamienianie ich w kody błędów, np. Może rejestrowanie może odbywać się w osobnym proces lub wątek, które mogą zawieść osobno i poza istniejącym stosem odzyskiwania po awarii, rozwijają się. Tak długo, jak możesz komunikować się z tym procesem bez możliwości wystąpienia błędu, byłoby to również bezpieczne, ponieważ problem bezpieczeństwa występuje tylko w tym scenariuszu, jeśli rekurencyjnie rzucamy z tego samego wątku.W takim przypadku możemy uniknąć awarii rejestrowania, pod warunkiem, że nie rzuca się, ponieważ nie można się zalogować i nic nie robić, to nie koniec świata (nie wycieknie żadnych zasobów lub nie przywróci efektów ubocznych, np.).
W każdym razie jestem pewien, że możesz już zacząć wyobrażać sobie, jak niesamowicie trudno jest naprawdę uczynić oprogramowanie bezpiecznym dla wyjątków. Być może nie będzie konieczne uzyskanie tego w jak największym stopniu we wszystkich programach oprócz najbardziej krytycznych dla misji. Warto jednak zwrócić uwagę na to, jak naprawdę osiągnąć bezpieczeństwo wyjątków, ponieważ nawet autorzy bibliotek o bardzo ogólnym przeznaczeniu często grzebią tutaj i niszczą całe bezpieczeństwo wyjątków aplikacji korzystającej z biblioteki.
SomeFileWriter
Jeśli
SomeFileWriter
można wrzucić do środkaclose
, powiedziałbym, że jest to generalnie niezgodne z obsługą wyjątków, chyba że nigdy nie spróbujesz go zamknąć w kontekście wymagającym odzyskania z istniejącego wyjątku. Jeśli kod jest poza twoją kontrolą, możemy być SOL, ale warto powiadomić autorów tego rażącego problemu dotyczącego bezpieczeństwa wyjątków. Jeśli jest to twoja kontrola, moim głównym zaleceniem jest upewnienie się, że zamknięcie go nie może zakończyć się niepowodzeniem za pomocą wszelkich niezbędnych środków.Wyobraź sobie, że system operacyjny może nie zamknąć pliku. Teraz każdy program, który próbuje zamknąć plik podczas zamykania, nie zostanie zamknięty . Co powinniśmy teraz zrobić, po prostu utrzymuj aplikację otwartą i zawieszoną (prawdopodobnie nie), po prostu wyciek zasobu pliku i zignoruj problem (może w porządku, jeśli nie jest to takie krytyczne)? Najbezpieczniejszy projekt: spraw, aby zamknięcie pliku było niemożliwe.
źródło