Efektywne użycie bloku try / catch?

21

Czy bloki catch powinny być używane do zapisywania logiki, tj. Obsługi kontroli przepływu itp.? A może po prostu rzucać wyjątki? Czy wpływa to na wydajność lub łatwość konserwacji kodu?

Jakie są skutki uboczne (jeśli występują) logiki pisania w bloku catch?

EDYTOWAĆ:

Widziałem klasę Java SDK, w której zapisali logikę w bloku catch. Na przykład (fragment kodu pobrany z java.lang.Integerklasy):

        try {
            result = Integer.valueOf(nm.substring(index), radix);
            result = negative ? new Integer(-result.intValue()) : result;
        } catch (NumberFormatException e) {
            String constant = negative ? new String("-" + nm.substring(index))
                                       : nm.substring(index);
            result = Integer.valueOf(constant, radix);
        }

EDYCJA 2 :

Przechodziłem samouczek, w którym liczą to jako zaletę pisania logiki wyjątkowych przypadków w wyjątkach:

Wyjątki pozwalają napisać główny przepływ kodu i poradzić sobie z wyjątkowymi przypadkami gdzie indziej.

Jakieś konkretne wskazówki, kiedy pisać logikę w bloku catch, a kiedy nie?

HashimR
źródło
12
@ Koder Myślę, że trochę przesadzasz. Zwłaszcza, że ​​przy wielu istniejących interfejsach API nie można tego uniknąć.
CodesInChaos
8
@ Koder: W Javie wyjątki są często stosowane jako mechanizm sygnalizowania problemów, które są częścią prawidłowego przepływu kodu. Na przykład, jeśli spróbujesz otworzyć plik, który się nie powiedzie, biblioteka Java poinformuje Cię o tym, zgłaszając wyjątek, ponieważ interfejsy API prowadzące do otwarcia pliku nie mają innego mechanizmu zwracania błędu.
JeremyP,
4
@ Koder Zależy. Myślę, że podczas wykonywania operacji wejścia / wyjścia lub analizowania złożonych danych, które prawie zawsze są poprawnie sformatowane, zgłoszenie wyjątku oznaczającego błąd sprawia, że ​​kod jest znacznie czystszy.
CodesInChaos
1
@Coded Tak, istnieje metoda, która informuje, że nie była w stanie zrobić tego, o co została poproszona. Ale wyjątków nie należy używać do kontroli przepływu, dlatego C # ma również int. Metodę TryParse, która nie rzuca.
Andy,
1
@ Koder: To totalne śmieci. Rzeczy, które są wyjątkowe na jednym poziomie kodu, mogą nie być wyjątkowe na innym - lub możesz na przykład przedstawić lub dodać informacje o błędzie lub debugowaniu przed kontynuowaniem.
DeadMG,

Odpowiedzi:

46

Przytoczony przez ciebie przykład wynika z kiepskiego projektu API (nie ma czystego sposobu na sprawdzenie, czy String jest prawidłową liczbą całkowitą, z wyjątkiem próby jego przeanalizowania i wyłapania wyjątku).

Na poziomie technicznym rzut i try / catch to konstrukcje sterowania przepływem, które pozwalają przeskoczyć stos wywołań, nic więcej i nic mniej. Przeskakiwanie stosu wywołań niejawnie łączy kod, który nie jest blisko siebie w źródle, co jest niekorzystne z uwagi na łatwość konserwacji . Dlatego należy go używać tylko wtedy, gdy trzeba to zrobić, a alternatywy są jeszcze gorsze. Powszechnie akceptowane przypadek gdzie alternatywy są gorsze jest obsługa błędów (specjalne kody powrotne, które muszą być sprawdzone i mijały się każdy poziom stosu wywołań ręcznie).

Jeśli masz przypadek, w którym alternatywy są gorsze (i naprawdę dokładnie rozważyłeś je wszystkie), powiedziałbym, że użycie rzutowania i próbowania / łapania dla kontroli przepływu jest w porządku. Dogmat nie jest dobrym substytutem osądu.

Michael Borgwardt
źródło
1
+1, dobre punkty. Myślę, że pewne przetwarzanie może być poprawne w trakcie połowu, takie jak przygotowanie komunikatu o błędzie i rejestrowanie błędów. Uwolnienie drogich zasobów, takich jak połączenia db oraz w (.NET) odwołaniach COM można dodać do bloku Wreszcie.
NoChance,
@EmmadKareem Myślałem o uwolnieniu zasobów, ale zazwyczaj musisz je w pewnym momencie zwolnić, nawet bez wystąpienia błędu. W ten sposób możesz się powtórzyć. Przygotowanie komunikatu dziennika jest oczywiście dopuszczalne.
szalik
@MichaelBorgwardt Myślę, że interfejs API nie jest tak kiepski (choć mógłby być lepszy). Próba parsowania niepoprawnego ciągu jest wyraźnie warunkiem błędu, a zatem dokładnie „szeroko akceptowanym przypadkiem”, o którym wspomniałeś. Uzgodniony, przydaje się metoda określania, czy łańcuch jest poprawną składniowo liczbą (w tym przypadku może być lepiej). Jednak zmuszanie wszystkich do wywołania tej metody przed faktycznym parsowaniem nie jest możliwe i musimy poradzić sobie z błędem. Mieszanie rzeczywistego wyniku z kodem błędu jest niewygodne, więc nadal będziesz zgłaszać wyjątek. Ale może wyjątek czasu wykonywania.
szalik
3
+1 za ostatnie zdanie.
FrustratedWithFormsDesigner
2
@scarfridge: Całkowicie się z tobą zgadzam :) Brak metody isInteger (String) zwracającej wartość logiczną jest wszystkim, co miałem na myśli w przypadku „złego projektu interfejsu API”
Michael Borgwardt,
9

Jest to coś, co zwykle zależy od języka i paradygmatu.

Większość mojej pracy jest wykonywana w Javie (a czasem w C ++). Często stosuje się wyjątki tylko w wyjątkowych warunkach. Istnieją pewne pytania dotyczące przepełnienia stosu dotyczące narzutu wydajności wyjątków w Javie , i jak widać, nie jest to wcale nieistotne. Istnieją również inne obawy, takie jak czytelność i łatwość konserwacji kodu. Przy prawidłowym użyciu wyjątki mogą przekazywać obsługę błędów odpowiednim komponentom.

Jednak w Pythonie panuje idea, że ​​łatwiej jest prosić o wybaczenie niż pozwolenie. W społeczności Python jest to znane jako EAFP , co kontrastuje z podejściem „patrz przed skokiem” ( LBYL ) w językach w stylu C.

Thomas Owens
źródło
2
+1 za wzmiankę o kosztach ogólnych z wyjątkami. Robię .NET i (przynajmniej na moim komputerze) mogłem nawet wyczuć, kiedy wyjątek ma się wydarzyć, biorąc pod uwagę czas, jaki zajmuje w niektórych przypadkach w porównaniu do innych normalnych operacji.
NoChance,
2
@EmmadKareem Tylko jeśli masz podłączony debuger. Możesz rzucić kilka tysięcy z nich na sekundę bez debuggera.
CodesInChaos
@CodeInChaos, masz rację. Skąd mam wiedzieć, że piszę taki czysty kod, który nigdy nie
pojawia
@EmmadKareem W zależności od tego, jak pisze się aplikację sieciową, awarie połączeń lub protokołów są obsługiwane z wyjątkiem. W takiej aplikacji wyjątki muszą być dość szybkie, aby uniknąć trywialnych ataków DoS.
CodesInChaos
@CodeInChaos, to dobrze wiedzieć. Żartowałem w moim ostatnim komentarzu.
NoChance,
9

To nie jest najlepszy sposób myślenia o blokach try / catch. Sposób myślenia o blokach try / catch jest następujący: nastąpiła awaria, gdzie jest najlepsze miejsce, aby poradzić sobie z TYM konkretnym niepowodzeniem. To może być następny wiersz kodu, może być dwadzieścia poziomów w górę łańcucha połączeń. Gdziekolwiek to jest, tam właśnie powinno być.

To, co robi blok catch, zależy od błędu i tego, co możesz z tym zrobić. Czasami można go po prostu zignorować (brak usunięcia pliku scratch bez nieistotnych danych), czasem zostanie użyty do ustawienia wartości zwracanej funkcji na true lub false (metoda analizy java), czasem wyjdziesz z programu . Wszystko zależy od błędu.

Ważną częścią do zrozumienia jest: catch block == Wiem, jak sobie z tym poradzić.

jmoreno
źródło
6

Bloków połowowych należy używać wyłącznie do obsługi wyjątków, nic więcej i nigdy do kontroli przepływu. W każdej sytuacji, w której chcesz zastosować wyjątki do zarządzania przepływem, lepiej byłoby sprawdzić warunki wstępne.

Obsługa przepływu z wyjątkami jest powolna i semantycznie niepoprawna.

Tom Squires
źródło
4
Wiem, co mówisz, ale warto zauważyć, że jedyne, co robią wyjątki, to obsługa programu - tylko, że powinien on być używany tylko do kontrolowania wyjątkowego przepływu błędów ...
Max.
Czy nie ma żadnych konkretnych wskazówek, kiedy pisać logikę w bloku catch, a kiedy nie?
HashimR
4
Parsery liczb i formatyzatory tekstu Java są znanymi przykładami problematycznego użycia wyjątków: jedynym rozsądnym sposobem sprawdzenia warunku wstępnego (czy ciąg reprezentuje liczbę możliwą do przeanalizowania) jest próba jego przeanalizowania, a jeśli nie powiedzie się wyjątek, jakie są twoje możliwości w praktyce? Musisz złapać wyjątek i zarządzać nim za pomocą przepływu, bez względu na to, że jest on uważany za semantycznie niepoprawny.
Joonas Pulakka
@JoonasPulakka Myślę, że twój komentarz wrt Analizatory liczb i formatory tekstu kwalifikują się jako pełnowymiarowa odpowiedź na to pytanie
gnat,
5

Czy bloki catch powinny być używane do zapisywania logiki, tj. Obsługi kontroli przepływu itp.? A może po prostu rzucać wyjątki? Czy wpływa to na wydajność lub łatwość konserwacji kodu?

Po pierwsze, zapomnij o idei, że „wyjątki należy stosować w wyjątkowych warunkach”. Przestań też martwić się o wydajność, dopóki nie będziesz mieć kodu, który działa nieakceptowalnie, i nie będziesz mierzył, gdzie leży problem.

Kod jest najłatwiejszy do zrozumienia, gdy działania następują w prostej sekwencji, bez warunków warunkowych. Wyjątki poprawiają łatwość konserwacji poprzez usunięcie sprawdzania błędów z normalnego przepływu. Nie ma znaczenia, czy wykonanie przebiega normalnie przez 99,9% czasu, czy 50% czasu, czy 20% czasu.

Zgłaszaj wyjątek, gdy funkcja nie może zwrócić wartości, procedura nie może zakończyć oczekiwanej akcji lub gdy konstruktor nie jest w stanie wytworzyć użytecznego obiektu. Pozwala to programistom założyć, że funkcja zawsze zwraca przydatny wynik, procedura zawsze kończy żądaną akcję, a skonstruowany obiekt jest zawsze w użytecznym stanie.

Największym problemem, jaki widzę z kodem obsługi wyjątków, jest to, że programiści piszą bloki try / catch, kiedy nie powinni. Na przykład w większości aplikacji internetowych, jeśli żądanie bazy danych zgłasza wyjątek, nic nie można zrobić w kontrolerze. Wystarczy jedna ogólna klauzula połowowa na najwyższym poziomie. Następnie kod kontrolera może błogo zignorować możliwość awarii dysku lub baza danych jest w trybie offline lub cokolwiek innego.

Kevin Cline
źródło
1

Bloki catch nie powinny być używane do pisania logiki kodu. Powinny być używane tylko do obsługi błędów. Przykładem jest (1) oczyszczenie wszystkich przydzielonych zasobów, (2) wydrukowanie przydatnego komunikatu i (3) wyjście z gracją.

To prawda, że ​​wyjątki mogą być nadużywane i powodować niepożądane gotoskutki. Ale to nie czyni ich bezużytecznymi. Przy właściwym użyciu mogą poprawić wiele aspektów twojego kodu.

sakisk
źródło