Java – sprawdzanie warunków wejściowych

W każdym języku programowania jednym z ważniejszych wzorców jest sprawdzanie w metodzie warunków wejściowych. Nazywa się to „fail fast”, czyli możliwie szybkie i czytelne zwrócenie błędu przed wykonaniem właściwego ciała metody. Dzięki temu łatwiej się taki wyjątek analizuje i poprawia błąd.

screenshot_java

W samym języku Java oraz z wykorzystaniem popularnych bibliotek można to zrobić na kilka sposobów. Przedstawie to na prostym przykładzie sprawdzenia czy argument liczbowy jest większy od 0.

1. Sprawdzenie warunku i rzucenie wyjątku

private void checkByExceptions(int arg1) {
    if(arg1 <= 0) {
        throw new IllegalArgumentException(
            "arg1 should be > 0, is: " + arg1);
    }
}

Metoda znana od początku języka Java: sami sprawdzamy warunek i rzucamy wyjątek: IllegalArgumentException, IllegalStateException, itp.

Exception in thread "main" java.lang.IllegalArgumentException: arg1 should be > 0, is: -1
	at checks.SimpleApp.checkByExceptions(SimpleApp.java:18)
	at checks.SimpleApp.main(SimpleApp.java:10)

Plusy:
– zawsze działa
Minusy:
– trzy linijki kodu zamiast jednej
– konieczne sklejanie napisów żeby komunikat zawierał dane

2. Asercje

private void checkByAssertions(int arg1) {
    assert arg1 > 0 : "arg1 should be > 0, is: " + arg1;
}

Wprowadzone w Javie 1.4 dało ładny i czytelny zapis. Jednak rzucany obiekt to AssertionError który dziedziczy z Error, co jest mało „koszerne”. Przez długi czas nie przeszkadzało, jednak po prowadzeniu statycznej analizy kodu jest to wykazywane jako błąd i ciężko z tym dyskutować.

Drugi problem to fakt że asercje wymagają włączenia w JVM zmiennej -enableassertions (-ea), co łatwo zapewnić w serwerach aplikacyjnych ale wielokrotnie zdarza się zapomnieć przy uruchamianiu standalone’wych aplikacji czy testów, np. JUnit. A brak tej flagi sprawia że kod wykonuje się dalej i prowadzi do trudno analizowanych problemów, co powoduje frustracje.

Trzeci problem, związany z pierwszym, to obsługa takich błędów w testach jednostkowych – nieładnie jest łapać Throwable (klasa bazowa dla Exception i Error).

Exception in thread "main" java.lang.AssertionError: arg1 should be > 0, is: -1
	at checks.SimpleApp.checkByAssertions(SimpleApp.java:24)
	at checks.SimpleApp.main(SimpleApp.java:11)

Plusy:
– zwięzły zapis
– działa bez dodatkowych bibliotek
– konieczne sklejanie napisów żeby komunikat zawierał dane
Minusy:
– rzuca Error a nie Exception
– wymaga ustawienia flagi -enableassertions

3. Biblioteka Apache commons-lang3

private void checkByApacheCommonsValidate(int arg1) {
    Validate.isTrue(arg1 > 0, "arg1 should be > 0, is: %s", arg1);
}

Wykorzystując metody statyczne z klasy Validate z biblioteki commons-lang można uzyskać zwięzły zapis oraz umieszczać parametry w opisie problemu.

Exception in thread "main" java.lang.IllegalArgumentException: arg1 should be > 0, is: -1
	at org.apache.commons.lang3.Validate.isTrue(Validate.java:106)
	at checks.SimpleApp.checkByApacheCommonsValidate(SimpleApp.java:28)
	at checks.SimpleApp.main(SimpleApp.java:12)

Plusy:
– zwięzły zapis
– możliwość użycia parametrów w opisie problemu
– rzuca wyjątki dziedziczące z Exception
Minusy:
– wymaga dodatkowej biblioteki

4. Biblioteka Google Guava

private void checkByGuavaPreconditions(int arg1) {
    Preconditions.checkArgument(arg1 > 0,
            "arg1 should be > 0, is: %s", arg1);
}

Biblioteka o funkcji analogicznej co commons-lang, tylko bardziej „trendy”.

Exception in thread "main" java.lang.IllegalArgumentException: arg1 should be > 0, is: -1
	at com.google.common.base.Preconditions.checkArgument(Preconditions.java:145)
	at checks.SimpleApp.checkByGuavaPreconditions(SimpleApp.java:32)
	at checks.SimpleApp.main(SimpleApp.java:13)

Plusy:
– zwięzły zapis
– możliwość użycia parametrów w opisie problemu
– rzuca wyjątki dziedziczące z Exception
Minusy:
– wymaga dodatkowej biblioteki

Podsumowanie

Jeżeli już używamy Guavy do innych celów, to powinniśmy z dnia na dzień zacząć używać Preconditions. Jeżeli nie, to powinniśmy zastanowić się czy jej nie zacząć używać :-).

Migracja z asercji do Google Guava

Najprościej:

  1. sed’em albo w swoim IDE zastąpić napis „assert (.*):(.*);” na „Preconditions.checkArgument($1, $2);”
    screenshot_2014-12-03_002
  2. sed’em albo w swoim IDE zastąpić napis „assert (.*);” na
    „Preconditions.checkArgument($1);”
  3. przejść się po klasach które przestały się kompilować i uzupełnić import klasy: „import com.google.common.base.Preconditions;”
  4. sprawdzić pozostałe wystąpienia słowa „assert ” i zastąpić na „Preconditions.checkArgument(” – ten problem występuje w wystąpieniach wieloliniowych.

Przy wykorzystywaniu Guavy (commons-lang zresztą też) należy się w każdym wypadku zastanowić jaką klasę wyjątku chcemy rzucać, bo dostępnych jest kilka:

  • Preconditions.checkArgument -> IllegalArgumentException – rzucamy w sytuacji gdy argument jest niezgodny z oczekiwanym
  • Preconditions.checkState -> IllegalStateException – rzucamy w sytuacji gdy warunek oznacza wejście w nieobsługiwany przypadek
  • Preconditions.checkNotNull -> NullPointerException – rzucamy w sytuacji gdy zmienna okaże się nullowa
  • Preconditions.checkElementIndex -> IndexOutOfBoundsException – rzucamy gdybyśmy próbowali odwołać się do nieistniejącego typu tablicy.

W praktyce powinniśmy używać tylko tych dwóch pierwszych metod.

O autorze

Marek Berkan Marek Berkan: programista, entuzjasta tworzenia oprogramowania, zarządzania zespołami technicznymi. Prywatnie motocyklista, kolarz MTB, biegacz, żeglarz, rekreacyjny wspinacz, zamiłowany turysta. Witryny: , , .

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *