Używasz „wyjątków”, aby poprawić czytelność, dobrą czy złą?

9

W sekcji Kiedy stosować wyjątek w The Pragmatic Programmer , książka pisze, że zamiast:

retcode = OK;
     if (socket.read(name) != OK) {
      retcode = BAD_READ;
    }
     else {
      processName(name);
       if (socket.read(address) != OK) {
        retcode = BAD_READ;
      }
       else {
        processAddress(address);
         if (socket.read(telNo) != OK) {
          retcode = BAD_READ;
        }
         else {
          //  etc, etc...
        }
      }
    }
     return retcode;

, oni wolą:

 retcode = OK;
     try {
      socket.read(name);
      process(name);
      socket.read(address);
      processAddress(address);
      socket.read(telNo);
      //  etc, etc...
    }
     catch (IOException e) {
      retcode = BAD_READ;
      Logger.log( "Error reading individual: " + e.getMessage());
    }
     return retcode;

po prostu dlatego, że wygląda ładniej. Jestem wszystkim dla kodu schludny, jednak nie jest konieczne łapanie wyjątków wąskim gardłem wydajności?

Rozumiem, że powinniśmy zrezygnować z drobnej optymalizacji starszego kodu (przynajmniej 99% razy), jednak z tego, co wiem, wychwytywanie wyjątków należy do klasy kodu, która ma zauważalne opóźnienie w czasie wykonywania. Dlatego zastanawiałem się, jakie jest uzasadnienie, że drugi fragment kodu jest lepszy od pierwszego?

A raczej jaki kod wolisz?

Pacerier
źródło
migrował z Code Review, ponieważ jest to pytanie dotyczące najlepszych praktyk, a nie pytanie przeglądu kodu
Winston Ewert
1
IMO, jeśli oczekujesz, że będziesz w stanie odczytać te wartości z gniazda, to IOEx jest wyjątkowy.
Steven Evers,
Odnoszący się do tej dyskusji: Używanie Throwable do celów innych niż wyjątki
Daniel R Hicks,
Zmierzyłeś?
@ ThorbjørnRavnAndersen oczywiście czytałem książkę i nie mam prawdziwego „przypadku testowego” do zmierzenia .. ale jeśli dokonamy pomiaru w zmyślonym kodzie, wtedy oczywiście będzie miała miejsce kara wydajności
Pacerier

Odpowiedzi:

18

Wyjątki są zwykle wolniejsze tylko wtedy, gdy zostaną zgłoszone. Zwykle wyjątki w takich sytuacjach są rzadkie, więc nie należy się tym martwić. Powinieneś naprawdę martwić się o skuteczność wyjątków, jeśli zdarzają się one cały czas, a zatem są znaczącym czynnikiem.

Drugi kod jest lepszy, ponieważ znacznie łatwiej jest podążać za logiką. Obsługa błędów jest ładnie zlokalizowana. Jedynym zastrzeżeniem jest to, że jeśli używasz wyjątków, to używaj wyjątków. Nie wychwytuj wyjątków, a następnie zwróć kod błędu.

Winston Ewert
źródło
4
Zgadzam się, nienawidzę widzieć kodów błędów w języku, który ma wyjątki. Przez większość czasu powinieneś łapać, rejestrować, ponownie rzucać bez zrywania stosu wywołań. I przez większość czasu nie ma znaczenia, czy przypadek „wyjątku” jest wolniejszy. Wystąpił problem z aplikacją. Poświęć trochę czasu, aby go poprawnie obsłużyć. Dopóki wyjątek jest wyjątkowy i nie jest częścią normalnej pracy aplikacji, ani szczęśliwej ścieżki, zwykle nie jest to zapach kodu.
CaffGeek,
Przy okazji zastanawiałem się, jak uzasadnić wyjątek EOFException, którego DataStreams używa do wykrywania końca linii (koniec nie jest wyjątkowy, prawda), jak pokazano w docs.oracle.com/javase/tutorial/essential/io/datastreams.html ?
Pacerier,
@Pacerier, może być wyjątkowy. Brak danych wejściowych w linii może oznaczać złe dane lub coś, co dzieje się bardzo źle. Podejrzewam, że zamierzonym zastosowaniem jest parsowanie kilku pól poza linią.
Winston Ewert,
@Downvoter, jeśli masz problem z odpowiedzią, wyjaśnij to!
Winston Ewert,
5

Cóż, jak mówią również, wyjątki powinny zajmować się wyjątkowymi przypadkami , a ponieważ jest to wyjątkowy przypadek (tj. Coś, co zdarza się rzadko i pomiędzy nimi), powiedziałbym, że dotyczy to również tutaj.

Także radzę przed mikro optymalizacji rzeczy, takich jak ten. Skoncentruj się bardziej na czytelności kodu , ponieważ będziesz spędzać znacznie więcej czasu na czytaniu kodu niż na pisaniu nowego kodu.

Aby naprawdę upewnić się, że jest to wąskie gardło w zakresie wydajności, wykonaj profilowanie oraz przeanalizuj i skoncentruj się na bardziej krytycznych zadaniach opartych na tej analizie.

Andreas Johansson
źródło
5

Rozumiem, że powinniśmy zrezygnować z drobnej optymalizacji starszego kodu (przynajmniej 99% razy), jednak z tego, co wiem, wychwytywanie wyjątków należy do klasy kodu, która ma zauważalne opóźnienie w czasie wykonywania.

Skąd to wiesz? Jak zdefiniujesz „zauważalne opóźnienie”?

Jest to dokładnie taki niejasny (i całkowicie błędny) mit wydajności, który prowadzi do bezużytecznych przedwczesnych optymalizacji.

Zgłaszanie i wyławianie wyjątku może być powolne w porównaniu do dodawania dwóch liczb, ale jest całkowicie nieistotne w porównaniu do odczytu danych z gniazda. Prawdopodobnie możesz rzucić i złapać tysiąc wyjątków w tym samym czasie, gdy czytasz pojedynczy pakiet sieciowy. Nie mówimy tu o tysiącach wyjątków, tylko o jednym.

Michael Borgwardt
źródło
Pochodzę z .NET, więc wybacz moją wiedzę o Javie, ale osobiście doświadczyłem wychwytywania wyjątków w .NET powodujących szalone opóźnienia (wielkość sekund), które zostały naprawione przez usunięcie „kodu wyjątku przechwytywania” ..
Pacerier
@Pacerier, poważnie? wielkość sekund? Chyba może łapało to wiele wyjątków? Wtedy mogłem to zobaczyć.
Winston Ewert,
3
@Pacerier: Jeśli wyjątki powodują szalone opóźnienia, jasne jest, że to, co je powoduje, nie jest wyjątkowe i dlatego należy je traktować w inny sposób. Żaden system nie powinien zająć sekund na przetworzenie wyjątku i uważam, że Microsoft jest wystarczająco kompetentny, aby tego uniknąć.
David Thornley,
5

Zgadzam się z odpowiedziami, które już otrzymałeś, mówiąc, że jest to wyjątkowy przypadek, więc rozsądnie jest użyć obsługi wyjątków.

Zajmując się sprawami dotyczącymi wydajności w bardziej bezpośredni sposób, myślę, że musisz zachować poczucie skali w różnych kwestiach. Zgłoszenie / wyłapanie wyjątku w tych okolicznościach może potrwać jeden mikrosekundę. Gdy kontrola przepływu idzie, jest to powolne - wiele razy wolniejsze niż zwykłe ifstwierdzenia, nie ma co do tego wątpliwości.

Jednocześnie pamiętaj, że to, co tu robisz, to odczytywanie danych z sieci. Przy prędkości 10 megabitów na sekundę (wolna dla sieci lokalnej, ale dość szybko w miarę połączenia z Internetem), jest to ta sama kolejność, co czas odczytania jednego bajtu informacji.

Oczywiście, narzut ten powstaje tylko wtedy, gdy faktycznie wyrzucisz wyjątek - gdy nie zostanie wyrzucony, zwykle jest on w ogóle niewielki lub wcale (przynajmniej z tego, co widziałem, najbardziej znaczący narzut nie pochodzi z sama obsługa wyjątków, ponieważ od dodania większej liczby potencjalnych przepływów kontroli, co utrudnia kompilatorowi również optymalizację).

W takim przypadku, gdy zostanie zgłoszony wyjątek, dane zostaną zapisane w dzienniku. Biorąc pod uwagę naturę rejestrowania, istnieje duże prawdopodobieństwo, że opróżnienie tego wyniku natychmiast po jego zapisaniu (aby upewnić się, że nie zostanie utracone). Zapisywanie na dysku jest (znowu) dość powolną operacją - potrzebujesz dość szybkiego dysku SSD klasy korporacyjnej, aby uzyskać nawet dziesiątki tysięcy procesorów IOP na sekundę. Dysk używany do logowania można łatwo ograniczyć do setek procesorów IOP na sekundę.

Podsumowując: są przypadki, w których narzut obsługi wyjątków może być znaczący, ale prawie na pewno nie jest to jeden z nich.

Jerry Coffin
źródło