Czy posiadanie pustego zestawienia połowów jest w porządku?


58

Myślałem o tym i nie mogłem podać żadnego przykładu. Dlaczego ktoś miałby chcieć złapać wyjątek i nic z tym nie zrobić? Czy możesz podać przykład? Może jest to po prostu coś, czego nigdy nie należy robić.


Co w końcu?
Chris

2
btw - to zostało zadane na SO w zeszłym roku: stackoverflow.com/questions/1234343/...
warren

17
powinien przynajmniej zawierać komentarz, dlaczego jest pusty.
peterchen

Odpowiedzi:


77

Cały czas robię to z błędami konwersji w D:

import std.conv, std.stdio, std.exception;

void main(string[] args) {  
    enforce(args.length > 1, "Usage:  foo.exe filename");

    double[] nums;

    // Process a text file with one number per line into an array of doubles,
    // ignoring any malformed lines.
    foreach(line; File(args[1]).byLine()) {
        try {
            nums ~= to!double(line);
        } catch(ConvError) {
            // Ignore malformed lines.
        }
    }

    // Do stuff with nums.
}

To powiedziawszy, myślę, że wszystkie bloki catch powinny mieć coś w sobie, nawet jeśli to jest tylko komentarz wyjaśniający, dlaczego ignorujesz wyjątek.

Edycja: Chcę również podkreślić, że jeśli zamierzasz zrobić coś takiego, powinieneś być ostrożny, aby złapać tylko określony wyjątek, który chcesz zignorować. Robienie zwykłego starego catch {}jest prawie zawsze złą rzeczą.


15
+1 za komentarz wyjaśniający.
Michael K

Niektóre środowiska IDE pozwalają na określenie, że określona nazwa wyjątku (zwykle „ignoruj”) wskazuje, że celowo go ignorujesz, co eliminuje ostrzeżenie, które w innym przypadku byłoby wyświetlane; zajmuje to miejsce komentarza.
Alex Feinman,

Nie znam D, ale zakładam, że można zrobić coś takiego jak bool TryParse (linia ciągów, out double valToParse), co może być czystsze.
ysolik

4
Wow, ktoś, kto faktycznie używa D! :-P
James McNellis,

4
Jak mówi @Michael - nie jest całkiem pusty, skoro masz tam komentarz; należy dodać komentarz „ten połów celowo pozostawiono pusty”, jeśli nie ma oświadczenia
STW

50

Jednym z przykładów, w którym moim zdaniem jest OK połknięcie wyjątku bez robienia czegokolwiek, nawet rejestrowanie wyjątku, znajduje się w samym kodzie rejestrowania.

Jeśli próbujesz coś zalogować i otrzymujesz wyjątek, niewiele możesz z tym zrobić:

  • oczywiście nie możesz się zalogować;
  • możesz być w stanie wrócić do mechanizmu rejestrowania rezerw, ale większość aplikacji nie jest tak skomplikowana, a dla tych, które są wystarczająco zaawansowane, ten sam problem projektowy pojawi się w przypadku mechanizmu rejestrowania rezerw;
  • szybka awaria - będzie zbyt radykalnym rozwiązaniem dla większości aplikacji;
  • zgłoszenie wyjątku nie przyniesie nic dobrego, ponieważ skończy się na instrukcji catch na stosie, która prawie na pewno spróbuje go zarejestrować ...

Pozostawia to opcję „najmniejszego zła”, polegającą na cichym połykaniu, co pozwala aplikacji na wykonywanie głównej pracy, ale ogranicza możliwość jej rozwiązania.


10
świetny punkt. debugowanie kodu debugowania jest zawsze wyzwaniem.
DevSolo,

7
To milion razy. Zwłaszcza gdy weźmiesz pod uwagę, że jeśli się logujesz, możesz być w fatalnym stanie; możesz mieć brak pamięci, awarię, może być cokolwiek. W tym momencie, gdy rejestrujesz błąd, który NIE powiedzie się, jedyną odpowiedzialną rzeczą jest nic; nie masz gwarancji, że cokolwiek spróbujesz, nie pogorszy sytuacji.
GWLlosa,

Świetny punkt W moim kodzie logowania chciałbym również dodać InnerMessage dla wyjątku. Wiele razy jest to zero, więc próba dodania go wysadziłaby samo logowanie.
Tangurena,

@Tangurena Jeśli to często jest zerowe, radzę sobie z tym, nie dmuchaj. W C # często strzegę takich rzeczy przez? „”;
Loren Pechtel

8

Jeśli wyjątek zostanie zgłoszony przez operację, która i tak była opcjonalna, może nie być nic do zrobienia. Może się to nie przydać. W takim przypadku możesz po prostu chcieć go złapać i nic nie robić.

Jeśli to robisz, dołącz komentarz. Zawsze komentuj wszystko, co wygląda jak błąd (przegląd switchinstrukcji, pusty catchblok, przypisanie w stanie).

Możesz argumentować, że nie jest to właściwe wykorzystanie wyjątków, ale to dotyczy innego pytania.


6

Puste catchbloki są zapachem kodu w większości języków. Główną ideą jest stosowanie wyjątków w wyjątkowych sytuacjach i nie wykorzystywanie ich do kontroli logicznej. Wszystkie wyjątki muszą być gdzieś obsługiwane.

W wyniku zastosowania tego podejścia podczas programowania jednej konkretnej warstwy aplikacji masz kilka możliwości:

  • nie rób nic z wyjątkiem. Zostanie złapany i obsłużony przez inną warstwę
  • złap go i wykonaj działanie naprawcze.
  • złap go, zrób coś, ale rzuć to ponownie, aby poradzić sobie z kolejną warstwą

To tak naprawdę nie pozostawia miejsca na nic nie rób, puste catchbloki.

ZMIENIONO DO DODANIA : załóżmy, że programujesz w języku, w którym zgłaszanie wyjątków jest normalnym sposobem kontrolowania logiki programu (jedna z alternatyw dla goto). Wówczas pusty catchblok w programie napisanym w tym języku jest bardzo podobny do pustego elsebloku w tradycyjnym języku (lub w ogóle go nie ma else). Uważam jednak, że nie jest to styl programowania zalecany przez społeczności programistów C #, Java lub C ++.


Wydaje mi się, że stosowanie wyjątków jako kontroli logiki stało się dość powszechne, więc często mam puste lub prawie puste bloki catch.
whatsisname

+1 za rozpoznanie, że pusty blok catch może wskazywać na użycie wyjątków do kontroli przepływu.
John M Gant,

Stosowanie wyjątków w logice programowania jest ogólnie uważane za złą praktykę. Co zaskakujące (mój oryginalny argument przeciwko wyjątkom) wyjątki nie powodują zauważalnego spadku wydajności. Zobacz codeproject.com/KB/exception/ExceptionPerformance.aspx .
Evan Plaice,

6

W niektórych przypadkach Java wymaga obsługi wyjątku, który w żadnym wypadku nie może się zdarzyć. Rozważ ten kod:

try {
   bytes[] hw = "Hello World".getBytes("UTF-8");
}
catch(UnsupportedCodingException e) {
}

Każda implementacja platformy Java jest wymagana do obsługi następujących standardowych zestawów znaków.

...

UTF-8

Bez zepsucia platformy Java po prostu nie ma sposobu, aby spowodować ten wyjątek. Więc po co męczyć się z tym? Ale nie można po prostu pominąć klauzuli try-catch.


1
W takich przypadkach chciałbym dodać, throw new Error(e)aby mieć absolutną pewność, że niczego nie przegapiłem (lub nie zgłosiłem faktycznie uszkodzonej platformy).
Halle Knast

@HalleKnast Tak. Zostało to szczegółowo omówione tutaj: softwareengineering.stackexchange.com/questions/122233/…
user281377

5

Zasadniczo nie. Możesz albo rzucić wyjątek na stos, albo zarejestrować, co się stało.

Jednak często to robię, gdy analizuję ciągi znaków i konwertuję na liczby (szczególnie w przypadku mapowania projektów, które są użyteczne raz i odrzucają różnorodność).

boolean isNonZeroNumber = false;

try {
   if(StringUtils.isNotBlank(s) && new BigDecimal(s).compareTo(BigDecimal.ZERO) != 0) {
     b = true;
   }
} catch(NumberFormatException e) {
//swallow this exception; b stays false.
}

return b;

3

W niektórych przypadkach wyjątek wcale nie jest wyjątkowy; w rzeczywistości jest to oczekiwane. Rozważ ten przykład:

    /* Check if the process is still alive; exitValue() throws an exception if it is */
    try {
        p.exitValue();
        processPool.remove(p);
    }
    catch (IllegalThreadStateException e) { /* expected behaviour */ }

Ponieważ w java.lang.Process () nie ma metody isAlive (), jedynym sposobem sprawdzenia, czy wciąż żyje, jest wywołanie metody exitValue (), która zgłasza wyjątek IllegalThreadStateException, jeśli proces nadal działa.


2

Według mojego skromnego doświadczenia rzadko jest to dobra rzecz. Twój przebieg może się jednak różnić.

Niemal za każdym razem, gdy widziałem to w naszej bazie kodu, dzieje się tak, ponieważ wyśledziłem błąd, który ukrył zjedzony wyjątek. Innymi słowy, nie podając żadnego kodu, aplikacja nie może wskazać błędu ani wykonać innej akcji.

Czasami, na przykład, w warstwach API możesz nie chcieć lub nie możesz pominąć wyjątków, więc w takim przypadku staram się wychwycić najbardziej ogólne, a następnie je zapisać. Jeszcze raz, aby przynajmniej dać szansę sesji debugowania / diagnozy.

Zazwyczaj, jeśli musi być pusty, przynajmniej robię to, twierdzę, że coś się dzieje podczas sesji debugowania. To powiedziawszy, jeśli nie mogę sobie z tym poradzić, zwykle całkowicie je pomijam.


2

IMO jest jedno miejsce, w którym puste wyciągi połowowe są OK. W przypadkach testowych, w których spodziewany jest wyjątek:

try
{
   DoSomething(); //This should throw exception if everything works well
   Assert.Fail('Expected exception was not thrown');
}
catch(<whatever exception>)
{
    //Expected to get here
}

+1, nienawidzę tego, ale robię to w JUnit
sal

@sal: co z @Test (oczekiwano = Exception.class)?
ysolik

@ysolik, zły nawyk
sal

1
@sal: Dlaczego to zły nawyk?
Adam Lear

ummmm. istnieją sposoby, aby to zrobić za pomocą ram testowania jednostkowego. Dawny. NUnit. Są pewne przypadki, w których testowanie, czy coś może się nie powieść, może być przydatne. Chociaż nigdy nie zrobiłbym tego w kodzie produkcyjnym.
Evan Plaice,

2

Uważam, że istnieją pewne przypadki, w których uzasadnione jest posiadanie pustej klauzuli catch - są to przypadki, gdy chcesz, aby kod działał dalej, jeśli dana operacja się nie powiedzie. Myślę jednak, że ten wzór można łatwo nadużyć, dlatego każde użycie powinno być dokładnie zbadane, aby upewnić się, że nie ma lepszego sposobu na poradzenie sobie z nim. W szczególności nie należy tego nigdy robić, jeśli pozostawia on program w niespójnym stanie lub jeśli może to doprowadzić do awarii innej części kodu.


1

Nie wierzę w brak pewnego poziomu czujności w przypadku wystąpienia wyjątku - czy jednym z celów programowania nie powinno być ulepszanie kodu? Jeśli wyjątek wystąpi w 10% przypadków i nigdy się o nim nie dowiesz, wówczas wyjątek będzie zawsze taki zły lub gorszy.

Widzę jednak drugą stronę, że jeśli wyjątek nie powoduje rzeczywistej szkody w przetwarzaniu kodu, to po co narażać użytkownika na to. Rozwiązaniem, które zazwyczaj wdrażam, jest rejestrowanie go przez moją klasę loggera (czyli w każdej klasie, jaką kiedykolwiek piszę w pracy).

Jeśli jest to łagodny wyjątek, wywołuję metodę .Debug () mojego Loggera; w przeciwnym razie Logger.Error () (i (być może) rzut).

try
{
   doWork();
}
catch(BenignException x)
{
  _log.Debug("Error doing work: " + x.Message);
}

Nawiasem mówiąc, w mojej implementacji mojego programu rejestrującego wywołanie metody .Debug () spowoduje zarejestrowanie go tylko wtedy, gdy mój plik App.Config określi, że poziom dziennika wykonania programu jest w trybie debugowania.


Co jeśli _log.Debug zgłasza wyjątek (z jakiegokolwiek powodu)?
JohnL,

Następnie .Debug () zjada wyjątek i nigdy o nim nie wiem. Ale jestem o wiele bardziej pewny mojego loggera niż kodu, który często rzuca wyjątki. Gdybym się tym martwił, przypuszczam, że mógłbym wdrożyć jakiś wzorzec, który obserwuje wejście do bloku catch i zapisuje go na później. W każdym razie - punkt zajęty
Tim Claason,

1

Sprawdzanie istnienia jest dobrym przykładem użycia:

// If an exception happens, it doesn't matter. Log the initial value or the new value

function logId(person) {
    let id = 'No ID';
    try {
        id = person.data.id;
    } catch {}
    console.log(id);
}

Innym przypadkiem finallyjest użycie klauzuli:

 function getter(obj){}

 function objChecker()
   {
   try
     {
     /* Check argument length and constructor type */
     if (getter.length === 1 && getter.constructor === Function)
       {
       console.log("Correct implementation");
       return true;
       }
     else
       {
       console.log("Wrong implementation");
       return false;
       }
     }
   catch(e)
     {
     }
   finally
     {
     console.log(JSON.stringify(getter))
     }
   }

 // Test the override of the getter method 
 getter = getter;
 objChecker();
 getter = [];
 objChecker();
 getter = {};
 objChecker();
 getter = RegExp;
 objChecker();
 getter = Function;
 objChecker();

Proponuje się zezwolenie na pominięcie wiązania połowów w ECMAScript, które dotyczy tego i podobnych przypadków użycia.

Bibliografia


Inny przykład: w nodejs metoda fs.exists (która zwraca true lub false) jest przestarzała ( nodejs.org/dist/latest-v10.x/docs/api/… ) na korzyść fs.access, który zgłasza wyjątek w jeśli plik nie istnieje. Jeśli ktoś chce zrobić coś tylko wtedy, gdy plik istnieje, klauzula catch jest całkowicie niepotrzebna.
systemovich

0

Jedyny raz, kiedy naprawdę potrzebowałem zrobić coś takiego, to klasa logowania dla aplikacji konsolowej. Istnieje globalny moduł obsługi catch najwyższego poziomu, który wysłał błąd do STDOUT, zarejestrował błąd do pliku, a następnie zamknął aplikację kodem błędu> 0.

Problem polegał na tym, że czasami logowanie do pliku kończyło się niepowodzeniem. Uprawnienia do katalogu powstrzymały cię przed zapisaniem pliku, plik został zablokowany, cokolwiek. Jeśli tak się stanie, nie będę w stanie obsłużyć wyjątku w taki sam sposób, jak gdzie indziej (złap, zarejestruj odpowiednie dane , zawiń lepszy typ wyjątku itp.), Ponieważ zarejestrowanie szczegółów wyjątku spowodowało, że program rejestrujący wykonał te same działania ( próba zapisu do pliku), który już się nie powiódł. Kiedy znów zawiodły ... Widzisz, dokąd idę. Wpada w nieskończoną pętlę.

Jeśli upuścisz niektóre bloki try {}, możesz skończyć z wyjątkiem pojawiającym się w oknie komunikatu (nie to, czego potrzebujesz dla aplikacji do cichej konfiguracji). Więc w tej metodzie, która zapisuje do pliku dziennika, mam pustą procedurę obsługi catch. Nie powoduje to zakopania błędu, ponieważ nadal pojawia się kod błędu> 0, po prostu zatrzymuje nieskończoną pętlę i pozwala aplikacji z wdziękiem wyjść.

Jest oczywiście komentarz wyjaśniający, dlaczego jest pusty.

(Zamierzałem to opublikować wcześniej, po prostu bałam się, że stanę w płomieniach zapomnienia. Ponieważ nie jestem jedyny ...)


0

Jest to odpowiednie w sytuacji, gdy trzeba wykonać pewną sekwencję bez względu na to, co ma najbardziej krytyczny element na końcu.

Na przykład. W kontroli ruchu komunikacja hosta ze sterownikiem. W sytuacjach awaryjnych istnieje szereg kroków przygotowujących do przerwania ruchu, zatrzymania generowania trajektorii, spowolnienia silników, umożliwienia przerw, wyłączenia wzmacniacza, a następnie należy zabić moc magistrali. Wszystko musi się zdarzyć sekwencyjnie, a jeśli jeden z kroków się nie powiedzie, pozostałe kroki muszą zostać wykonane mimo to, nawet przy akceptowanym ryzyku uszkodzenia sprzętu. Powiedz, że nawet jeśli wszystkie powyższe zawiodą, moc magistrali zabijania musi się zdarzyć.


To nie znaczy, że nic nie robisz, tylko to, co robisz, nie przerywa przepływu. Złap wyjątki, zapisz je w pamięci (bez opóźnienia zapisu na dysku), a następnie zabij moc w instrukcji w końcu. Następnie rób, co chcesz, z zapisanymi informacjami.
Loren Pechtel

0

Z wdzięcznym zamknięciem zasobów systemowych w celu odzyskania po błędzie

Na przykład. Jeśli używasz usługi w tle, które nie dostarcza informacji zwrotnej do użytkownika, może nie chcieć, aby popchnąć wskazanie błędu dla użytkownika, ale nie trzeba zamykać się zasoby są wykorzystywane w sposób wdzięku.

try
{
    // execution code here
}
catch(Exception e)
{
    // do nothing here
}
finally
{
    // close db connection
    // close file io resource
    // close memory stream
    // etc...
}

Najlepiej byłoby użyć funkcji catch w trybie debugowania, aby wykryć błędy i wydrukować je w konsoli lub w pliku dziennika w celu przetestowania. W środowisku produkcyjnym usługi w tle zasadniczo nie powinny przeszkadzać użytkownikowi, gdy zostanie zgłoszony błąd, więc wyjście błędu powinno zostać stłumione.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.