Dobre praktyki z „Effective Java (3rd Edition)”

Fragmenty książki „Effective Java (3rd Edition) ” Joshuy Blocha

1. Zwracaj puste kolekcje lub tablice, a nie null

Jeśli metoda zwraca Set, przy pustym wyniku nie rób return null; – zamiast tego użyj return Set.of();.

2. Rozważ statyczne metody fabrykujące zamiast konstruktorów

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

Nie zawsze są lepsze, ale w niektórych przypadkach dają wiele korzyści:

Wady:

3. Rozważ budowniczego, gdy konstruktor ma wiele parametrów

Statyczne fabryki i konstruktory mają wspólne ograniczenie: słabo się skalują przy dużej liczbie parametrów opcjonalnych. W kilku projektach, przy których pracowałem, dało się to mocno we znaki.

4. Wymuszaj właściwość singletonu prywatnym konstruktorem lub typem wyliczeniowym

Wszystko, co nie potrzebuje wielu instancji, powinno być tworzone tylko raz – czy to przez @ApplicationScoped, czy przez inny wzorzec singletonu.

5. Wymuszaj brak możliwości tworzenia instancji prywatnym konstruktorem

Czasem zechcesz napisać klasę, która jest jedynie zbiorem statycznych metod i statycznych pól. Takie klasy mają złą reputację, bo niektórzy nadużywają ich, by uniknąć myślenia w kategoriach obiektów, ale mają też uzasadnione zastosowania.

private MyStaticClass() {
    // private constructor to prevent instantiation
}

6. Unikaj tworzenia niepotrzebnych obiektów

String s = new String("bikini");  // DON'T DO THIS!

Albo:

// Hideously slow! Can you spot the object creation?
private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

Rozwiązanie: Ten program daje poprawny wynik, ale jest dużo wolniejszy, niż powinien, z powodu literówki w jednym znaku. Zmienna sum jest zadeklarowana jako Long zamiast long, co oznacza, że program tworzy około 2^31 niepotrzebnych instancji Long (mniej więcej jedną za każdym razem, gdy long i jest dodawane do Long sum).

7. Unikaj finalizatorów i cleanerów

Finalizatory są nieprzewidywalne, często niebezpieczne i zazwyczaj niepotrzebne. Ich użycie może powodować nieregularne zachowanie, słabą wydajność i problemy z przenośnością.

8. Preferuj try-with-resources zamiast try-finally

// try-with-resources - the best way to close resources!
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
}

9. Rozważ implementację Comparable

Implementując Comparable, pozwalasz swojej klasie współpracować z wieloma generycznymi algorytmami i implementacjami kolekcji, które opierają się na tym interfejsie.

10. Ograniczaj dostępność klas i składowych do minimum

Najważniejszym czynnikiem odróżniającym dobrze zaprojektowany komponent od źle zaprojektowanego jest to, w jakim stopniu komponent ukrywa swoje wewnętrzne dane i inne szczegóły implementacji przed innymi komponentami. Dobrze zaprojektowany komponent ukrywa wszystkie szczegóły implementacji, wyraźnie oddzielając swoje API od implementacji. Komponenty komunikują się wtedy wyłącznie przez swoje API i nic nie wiedzą o swoim wewnętrznym działaniu. Ta koncepcja, znana jako ukrywanie informacji albo hermetyzacja, jest podstawową zasadą projektowania oprogramowania.

11. W klasach publicznych używaj metod dostępowych, a nie pól publicznych

Czasem może cię kusić, by napisać zdegenerowane klasy, które służą wyłącznie do grupowania pól instancji:

// Degenerate classes like this should not be public!
class Point {
    public double x;
    public double y;
}

Ponieważ do pól danych takich klas sięga się bezpośrednio, klasy te nie dają korzyści płynących z hermetyzacji. Nie możesz zmienić reprezentacji bez zmiany API, nie możesz wymuszać niezmienników i nie możesz wykonać dodatkowej akcji przy dostępie do pola. Zatwardziali programiści obiektowi uważają takie klasy za herezję, którą zawsze należy zastąpić klasami z prywatnymi polami i publicznymi metodami dostępowymi (getterami), a w przypadku klas mutowalnych – metodami modyfikującymi (setterami):

// Encapsulation of data by accessor methods and mutators
class Point {

    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() { return x; }
    public double getY() { return y; }

    public void setX(double x) { this.x = x; }
    public void setY(double y) { this.y = y; }
}

Z pewnością zatwardziali mają rację w odniesieniu do klas publicznych: jeśli klasa jest dostępna poza swoim pakietem, udostępnij metody dostępowe, by zachować swobodę zmiany wewnętrznej reprezentacji klasy. Jeśli klasa publiczna wystawia swoje pola danych, wszelka nadzieja na zmianę jej reprezentacji przepada, bo kod klientów może być rozsiany po całym świecie.

Jeśli jednak klasa jest pakietowa albo jest prywatną klasą zagnieżdżoną, wystawianie jej pól danych nie jest samo w sobie niczym złym – zakładając, że dobrze opisują one abstrakcję, którą klasa zapewnia.

12. Ograniczaj mutowalność

Klasa niemutowalna to po prostu klasa, której instancji nie można modyfikować. Wszystkie informacje zawarte w każdej instancji są stałe przez cały czas życia obiektu, więc nie da się zaobserwować żadnych zmian. Biblioteki platformy Java zawierają wiele klas niemutowalnych, m.in. String, klasy opakowujące typy proste oraz BigInteger i BigDecimal. Jest ku temu wiele dobrych powodów: klasy niemutowalne łatwiej zaprojektować, zaimplementować i używać niż mutowalne. Są mniej podatne na błędy i bezpieczniejsze. Aby klasa była niemutowalna, przestrzegaj tych pięciu zasad:

  1. Nie udostępniaj metod, które modyfikują stan obiektu (tzw. mutatorów).
  2. Zadbaj o to, by klasy nie dało się rozszerzyć. Zapobiega to sytuacji, w której nieostrożne lub złośliwe podklasy naruszają niemutowalność klasy, zachowując się tak, jakby stan obiektu się zmienił. Dziedziczeniu zapobiega się zwykle np. przez oznaczenie klasy jako final.
  3. Oznacz wszystkie pola jako final. W ten sposób jasno wyrażasz swój zamiar, a system go egzekwuje. Jest to też konieczne do zapewnienia poprawnego działania, gdy referencja do nowo utworzonej instancji jest przekazywana z jednego wątku do drugiego bez synchronizacji, jak opisuje to model pamięci.
  4. Oznacz wszystkie pola jako prywatne. Uniemożliwia to klientom uzyskanie dostępu do mutowalnych obiektów, do których odwołują się pola, i bezpośrednią modyfikację tych obiektów. Choć technicznie klasy niemutowalne mogą mieć publiczne pola final zawierające wartości proste lub referencje do obiektów niemutowalnych, nie jest to zalecane, bo uniemożliwia zmianę wewnętrznej reprezentacji w późniejszej wersji.
  5. Zapewnij wyłączny dostęp do wszelkich mutowalnych komponentów. Jeśli klasa ma pola odwołujące się do obiektów mutowalnych, zadbaj o to, by jej klienci nie mogli uzyskać referencji do tych obiektów. Nigdy nie inicjalizuj takiego pola referencją do obiektu przekazaną przez klienta ani nie zwracaj tego pola z metody dostępowej. Twórz kopie defensywne w konstruktorach, metodach dostępowych i metodach readObject.

13. Przedkładaj kompozycję nad dziedziczenie

Dziedziczenie to potężny sposób na ponowne wykorzystanie kodu, ale nie zawsze jest najlepszym narzędziem do danego zadania. Użyte niewłaściwie prowadzi do kruchego oprogramowania. Dziedziczenie jest bezpieczne w obrębie pakietu, gdzie implementacje podklasy i nadklasy są pod kontrolą tych samych programistów. Bezpieczne jest też przy rozszerzaniu klas zaprojektowanych i udokumentowanych specjalnie z myślą o rozszerzaniu. Natomiast dziedziczenie po zwykłych konkretnych klasach ponad granicami pakietów jest niebezpieczne.

W przeciwieństwie do wywołania metody dziedziczenie narusza hermetyzację. Możesz pomyśleć, że rozszerzanie klasy jest bezpieczne, jeśli tylko dodajesz nowe metody i nie nadpisujesz istniejących. Choć takie rozszerzenie jest znacznie bezpieczniejsze, nie jest wolne od ryzyka. Jeśli nadklasa w kolejnej wersji dostanie nową metodę, a ty masz pecha i nadałeś podklasie metodę o tej samej sygnaturze i innym typie zwracanym, twoja podklasa przestanie się kompilować. Jeśli nadałeś podklasie metodę o tej samej sygnaturze i typie zwracanym co nowa metoda nadklasy, to teraz ją nadpisujesz, więc dotyczą cię opisane wcześniej problemy. Co więcej, wątpliwe jest, by twoja metoda spełniała kontrakt nowej metody nadklasy, bo ten kontrakt nie był jeszcze napisany, gdy pisałeś metodę podklasy.

Na szczęście istnieje sposób, by uniknąć wszystkich opisanych problemów. Zamiast rozszerzać istniejącą klasę, daj nowej klasie prywatne pole, które odwołuje się do instancji istniejącej klasy. Taki projekt nazywa się kompozycją, bo istniejąca klasa staje się komponentem nowej. Każda metoda instancji w nowej klasie wywołuje odpowiadającą jej metodę na zawartej instancji istniejącej klasy i zwraca wyniki. Nazywa się to przekazywaniem (forwarding), a metody w nowej klasie – metodami przekazującymi. Powstała klasa będzie solidna jak skała, bez zależności od szczegółów implementacji istniejącej klasy. Nawet dodanie nowych metod do istniejącej klasy nie będzie miało wpływu na nową klasę.

14. Preferuj interfejsy zamiast klas abstrakcyjnych

Java ma dwa mechanizmy definiowania typu, który dopuszcza wiele implementacji: interfejsy i klasy abstrakcyjne. Od wprowadzenia metod domyślnych w interfejsach w Javie 8 oba mechanizmy pozwalają dostarczać implementacje niektórych metod instancji.

Główna różnica polega na tym, że aby zaimplementować typ zdefiniowany przez klasę abstrakcyjną, klasa musi być podklasą tej klasy abstrakcyjnej. Ponieważ Java dopuszcza tylko dziedziczenie pojedyncze, to ograniczenie klas abstrakcyjnych mocno zawęża ich zastosowanie jako definicji typów. Każda klasa, która definiuje wszystkie wymagane metody i przestrzega ogólnego kontraktu, może zaimplementować interfejs – bez względu na to, gdzie znajduje się w hierarchii klas. Istniejące klasy można łatwo dostosować do implementacji nowego interfejsu.

15. Typy generyczne: nie używaj typów surowych

Błąd odkrywasz dopiero w czasie wykonania, długo po tym, jak wystąpił, i w kodzie, który może być daleko od kodu zawierającego błąd. Gdy zobaczysz ClassCastException, musisz przeszukać cały kod w poszukiwaniu wywołania metody, które wrzuciło monetę do kolekcji znaczków. Kompilator ci nie pomoże.

16. Typy generyczne: eliminuj ostrzeżenia unchecked

Gdy programujesz z użyciem typów generycznych, zobaczysz wiele ostrzeżeń kompilatora:

Im więcej doświadczenia zdobędziesz z typami generycznymi, tym mniej ostrzeżeń dostaniesz, ale nie oczekuj, że świeżo napisany kod skompiluje się bez zastrzeżeń. Wiele ostrzeżeń unchecked łatwo usunąć. Załóżmy na przykład, że przypadkiem napiszesz taką deklarację:

Set<Lark> exaltation = new HashSet();

Zmień ją na:

Set<Lark> exaltation = new HashSet<>();

Niektóre ostrzeżenia będzie dużo trudniej usunąć. Gdy dostajesz ostrzeżenia, które wymagają zastanowienia, nie poddawaj się! Usuń każde ostrzeżenie unchecked, które tylko możesz.

Jeśli usuniesz wszystkie ostrzeżenia, masz pewność, że twój kod jest bezpieczny typowo, a to bardzo dobra rzecz. Oznacza to, że nie dostaniesz ClassCastException w czasie wykonania, i zwiększa twoją pewność, że program będzie działał tak, jak zamierzałeś. Jeśli nie możesz usunąć ostrzeżenia, ale potrafisz udowodnić, że kod, który je wywołał, jest bezpieczny typowo, wtedy (i tylko wtedy) wycisz ostrzeżenie adnotacją @SuppressWarnings("unchecked"). Jeśli wyciszasz ostrzeżenia, nie udowodniwszy najpierw, że kod jest bezpieczny typowo, dajesz sobie fałszywe poczucie bezpieczeństwa. Kod może się skompilować bez żadnych ostrzeżeń, a mimo to rzucić ClassCastException w czasie wykonania.

Za każdym razem, gdy używasz adnotacji @SuppressWarnings("unchecked"), dodaj komentarz wyjaśniający, dlaczego jest to bezpieczne.

17. Typy generyczne: kiedy mają sens

Auć – klasa, która aż prosi się o typy generyczne:

Przykład klasy, której brakuje typów generycznych

Coś takiego powinno zostać wyłapane najpóźniej przy merge requeście!

18. Typy generyczne: jak tego nie robić

Wybacz, bez komentarza. NIGDY tego nie merguj:

public abstract class JpaLearningCardBaseContentService<LC extends LearningCardBaseContent<L, LC, AC>, J extends JpaLearningCardBaseContent<L, LC, AC>, L extends
        LearningCardBase<LC, A>, A extends AnswerOptionBase<L, AC>, AC extends AnswerOptionBaseContent<LC, AC, A>, AS extends AnswerOptionBaseService<A, AC, L, LC>, ACS extends
        AnswerOptionBaseContentService<AC, A, L, LC>, LS extends LearningCardBaseService<L>>
    extends JpaLightPersistenceService<LC, J, Long> implements LearningCardBaseContentService<LC, L, A, AC> {
    // ...
}

Zrozumienie samej deklaracji klasy nie powinno nigdy zajmować 5 minut.

19. Preferuj listy zamiast tablic

Tablice różnią się od typów generycznych w dwóch ważnych aspektach. Po pierwsze, tablice są kowariantne. To groźnie brzmiące słowo oznacza po prostu, że jeśli Sub jest podtypem Super, to typ tablicowy Sub[] jest podtypem typu tablicowego Super[]. Typy generyczne są natomiast inwariantne: dla dowolnych dwóch różnych typów Type1 i Type2 List<Type1> nie jest ani podtypem, ani nadtypem List<Type2>.

20. Preferuj lambdy zamiast klas anonimowych

// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

Zamiast tego zrób tak:

// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

21. Ograniczaj zasięg zmiennych lokalnych

Najskuteczniejszą techniką ograniczania zasięgu zmiennej lokalnej jest deklarowanie jej tam, gdzie zostaje po raz pierwszy użyta...

22. Preferuj pętle for-each zamiast tradycyjnych pętli for

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    ... // Do something with e
}

23. Poznaj biblioteki i z nich korzystaj

Załóżmy, że chcesz generować losowe liczby całkowite od zera do pewnej górnej granicy. W obliczu tego typowego zadania wielu programistów napisałoby małą metodę, która wygląda mniej więcej tak:

// Common but deeply flawed!
static Random rnd = new Random();

static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}

Ta metoda może wyglądać dobrze, ale ma trzy wady. Pierwsza: jeśli n jest małą potęgą dwójki, ciąg liczb losowych zacznie się powtarzać po dość krótkim okresie. Druga wada: jeśli n nie jest potęgą dwójki, niektóre liczby będą średnio zwracane częściej niż inne. Jeśli n jest duże, efekt ten może być bardzo wyraźny.

Trzecia wada metody random polega na tym, że w rzadkich przypadkach może zawieść katastrofalnie, zwracając liczbę spoza zadanego zakresu. Dzieje się tak dlatego, że metoda próbuje odwzorować wartość zwróconą przez rnd.nextInt() na nieujemny int, wywołując Math.abs. Jeśli nextInt() zwróci Integer.MIN_VALUE, to Math.abs również zwróci Integer.MIN_VALUE, a operator reszty z dzielenia (%) zwróci liczbę ujemną, zakładając, że n nie jest potęgą dwójki. To niemal na pewno spowoduje awarię programu, a błąd może być trudny do odtworzenia.

24. Unikaj float i double, jeśli potrzebujesz dokładnych wyników

Do kwot pieniężnych używaj BigDecimal. float i double powodują błędy zaokrągleń.

Załóżmy na przykład, że masz w kieszeni 1,03 $ i wydajesz 42 centy. Ile pieniędzy ci zostało? Oto naiwny fragment programu, który próbuje odpowiedzieć na to pytanie:

System.out.println(1.03 - 0.42);

Niestety wypisuje on 0.6100000000000001. To nie jest odosobniony przypadek. Załóżmy, że masz w kieszeni dolara i kupujesz dziewięć podkładek po dziesięć centów. Ile reszty dostaniesz?

System.out.println(1.00 - 9 * 0.10);

Według tego fragmentu programu dostajesz $0.09999999999999998.

Może ci się wydawać, że problem da się rozwiązać po prostu przez zaokrąglanie wyników przed wypisaniem, ale niestety nie zawsze to działa. Załóżmy na przykład, że masz w kieszeni dolara i widzisz półkę z rzędem pysznych cukierków po 10, 20, 30 centów i tak dalej, aż do dolara. Kupujesz po jednym cukierku każdego rodzaju, zaczynając od tego za 10 centów, aż nie będzie cię stać na kolejny cukierek z półki.

25. Preferuj typy proste zamiast ich opakowań

// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);

26. Odwołuj się do obiektów przez ich interfejsy

Dobry przykład:

Set<Son> sonSet = new LinkedHashSet<>();

Zły przykład:

LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

27. Preferuj interfejsy zamiast refleksji

W przeciwnym razie:

28. Przestrzegaj powszechnie przyjętych konwencji nazewniczych

Platforma Java ma dobrze ugruntowany zestaw konwencji nazewniczych, z których wiele zawiera The Java Language Specification [JLS, 6.1].

29. Używaj wyjątków tylko w wyjątkowych sytuacjach

// Horrible abuse of exceptions. Don't ever do this!
try {
    int i = 0;
    while (true) {
        range[i++].climb();
    }
} catch (ArrayIndexOutOfBoundsException e) {
    // catch it
}

Co robi ten kod? Na pierwszy rzut oka wcale nie jest to oczywiste – i to wystarczający powód, by go nie używać. Okazuje się, że to fatalnie przemyślany idiom przechodzenia po elementach tablicy. Nieskończona pętla kończy się przez rzucenie, przechwycenie i zignorowanie ArrayIndexOutOfBoundsException, gdy następuje próba dostępu do pierwszego elementu poza granicami tablicy. Ma to być odpowiednik standardowego idiomu przechodzenia po tablicy, który natychmiast rozpozna każdy programista Javy:

for (Mountain m : range) {
    m.climb();
}

Wyjątki, jak sama nazwa wskazuje, należy stosować tylko w wyjątkowych sytuacjach; nigdy nie powinny służyć do zwykłego sterowania przepływem.

30. Używaj wyjątków kontrolowanych dla sytuacji, z których można się podnieść, a wyjątków czasu wykonania dla błędów programistycznych

Używaj wyjątków kontrolowanych (checked) w sytuacjach, z których można rozsądnie oczekiwać, że wywołujący się podniesie. Konfrontując użytkownika z wyjątkiem kontrolowanym, projektant API nakłada na niego obowiązek obsłużenia tej sytuacji. Użytkownik może zlekceważyć ten obowiązek, przechwytując wyjątek i ignorując go, ale zwykle to zły pomysł.

Używaj wyjątków czasu wykonania (runtime) do sygnalizowania błędów programistycznych. Ogromna większość wyjątków czasu wykonania wskazuje na naruszenie warunków wstępnych. Naruszenie warunku wstępnego to po prostu niedotrzymanie przez klienta API kontraktu określonego w specyfikacji API. Na przykład kontrakt dostępu do tablicy określa, że indeks musi się mieścić w przedziale od zera do długości tablicy minus jeden włącznie. ArrayIndexOutOfBoundsException sygnalizuje, że ten warunek wstępny został naruszony.

31. Unikaj niepotrzebnego używania wyjątków kontrolowanych

Podsumowując: stosowane oszczędnie, wyjątki kontrolowane mogą zwiększyć niezawodność programów; nadużywane sprawiają, że korzystanie z API staje się udręką.

Pair<Boolean, LearningCardGroup> answerLearningCard(@NotNull LearningCardGroup learningCardGroup,
                                                      LearningCardAssignment learningCardAssignment,
                                                      @NotNull Collection<AnswerOption> answers)
      throws InvalidTrainingStateException, PersistenceException, LearningCardGroupAlreadyCompletedException,
             ConsecutiveCorrectCountExceededException,
             PreviousLearningCardGroupUncompletedException, LearningCardGroupCooldownException,
             LearningCardGroupHasUncompletedLearningCardsException, InvalidParticipantException, TrainingNotSignedException; // but why ???
     // this is a mistake seen a lot with junior devs
     // exceptions are exceptionally slow and should be avoided for mere validation
     // the performance gets even worse if a stacktrace is printed, since logging and printing is one of the slowest possible actions

32. Korzystaj ze standardowych wyjątków

Cechą, która odróżnia ekspertów od mniej doświadczonych programistów, jest to, że eksperci dążą do wysokiego stopnia ponownego wykorzystania kodu – i zwykle go osiągają.

Wyjątki nie są wyjątkiem od reguły, że ponowne wykorzystanie kodu to dobra rzecz. Biblioteki Javy udostępniają zestaw wyjątków, który pokrywa większość potrzeb większości API w zakresie rzucania wyjątków. Ponowne używanie standardowych wyjątków ma kilka zalet. Najważniejsza z nich to, że API staje się łatwiejsze do nauczenia i używania, bo odpowiada utartym konwencjom, które programiści już znają. Niewiele mniej ważne jest to, że programy korzystające z twojego API są czytelniejsze, bo nie są zaśmiecone nieznanymi wyjątkami. Wreszcie (i najmniej ważne): mniej klas wyjątków oznacza mniejsze zużycie pamięci i krótszy czas ładowania klas.

Najczęściej ponownie używanym typem wyjątku jest IllegalArgumentException. To zazwyczaj wyjątek, który należy rzucić, gdy wywołujący przekazuje argument o niewłaściwej wartości. Na przykład należałoby go rzucić, gdyby wywołujący przekazał liczbę ujemną w parametrze określającym, ile razy ma zostać powtórzona jakaś czynność. Innym często używanym wyjątkiem jest IllegalStateException. To zazwyczaj wyjątek, który należy rzucić, gdy wywołanie jest niedozwolone ze względu na stan obiektu, na którym je wykonano. Na przykład należałoby go rzucić, gdyby wywołujący próbował użyć jakiegoś obiektu, zanim ten został poprawnie zainicjalizowany.

Można argumentować, że każde błędne wywołanie metody sprowadza się do niedozwolonego argumentu lub stanu, ale dla pewnych rodzajów niedozwolonych argumentów i stanów standardowo używa się innych wyjątków. Jeśli wywołujący przekazuje null w parametrze, w którym wartości null są zabronione, konwencja nakazuje rzucić NullPointerException, a nie IllegalArgumentException. Podobnie jeśli wywołujący przekaże wartość spoza zakresu w parametrze będącym indeksem w sekwencji, należy rzucić IndexOutOfBoundsException, a nie IllegalArgumentException.

Nie używaj bezpośrednio Exception, RuntimeException, Throwable ani Error. Traktuj te klasy tak, jakby były abstrakcyjne. Wyjątkiem jest RuntimeException, którego można użyć, jeśli chcesz zamienić wyjątek kontrolowany na wyjątek czasu wykonania i naprawdę wiesz, co robisz.

33. Nie ignoruj wyjątków

// Empty catch block ignores exception - Highly suspect!
try {
    ...
} catch (SomeException e) {
}

Pusty blok catch niweczy sens wyjątków, którym jest zmuszenie cię do obsługi wyjątkowych sytuacji. Ignorowanie wyjątku przypomina ignorowanie alarmu przeciwpożarowego – i to jeszcze wyłączenie go, żeby nikt inny nie miał szansy sprawdzić, czy naprawdę się pali.