30 sierpnia 2008

SCJP – Asercje

Dzisiaj będzie o asercjach, ostatnim temacie z rozdziału piątego Książki. Samo używanie asercji jest dosyć proste – nie ma tu żadnych haczyków –, trudność polega zaś na tym, aby wiedzieć, w jakich okolicznościach powinniśmy je stosować, a w jakich nie. Egzamin SCJP w tym zakresie skupia się na sprawdzeniu czy wiemy, jakie są zalecenia SUN’a.

Asercje to mechanizm pozwalający upewnić się, że nasze założenie, co do stanu programu w danej chwili jest prawidłowe. Przykładowo, implementując pewną metodę możemy zakładać, że wszystkie parametry wejściowe są liczbami dodatnimi, albo że referencje nie mają wartości ‘null’. Możemy przecież założyć, że metoda wywołująca przekaże odpowiednie wartości, a jeśli nie, to oznacza to błąd w kodzie. Właśnie dokładnie do tego służą asercje – do upewnienia się, że w kodzie nie ma błędów. Ważną cechą asercji jest, że mogą być one włączane i wyłączane. Intencją asercji jest, by były one sprawdzane tylko w fazie implementacji i testów, zaś w gotowej aplikacji, uruchomionej w systemie produkcyjnym już nie, tak więc domyślnie mechanizm asercji jest wyłączony – asercje nie są sprawdzane. Poniżej przykład użycia asercji.

private static float div(float x, float y) {
assert y != 0 : "y equals 0";

return x / y;
}

Po słowie kluczowym ‘assert’ umieszczamy wyrażenie typu logicznego, którego prawdziwość chcemy przetestować. Dwukropek i następujący po nim komentarz (tj. dowolne wyrażenie typu napisowego, np. wywołanie funkcji zwracającej String) to elementy opcjonalne. Jeśli asercja nie jest prawdziwa JVM rzuca wyjątek java.lang.AssertionError. Wspomniany komentarz zostanie wykorzystany jako opis wyjątku. Wywołanie powyższej funkcji z drugim argumentem równym 0 spowoduje wyświetlenie następującego komunikatu.

Exception in thread "main" java.lang.AssertionError: y equals 0
at next.Test.div(Test.java:12)

Asercje zostały wprowadzone do języka Java począwszy od wersji 1.4. Oznacza to, że do tego momentu ‘assert’ nie było słowem kluczowym a więc w szczególności słowo to mogło być używane jako identyfikator… i pewnie czasami było. Próba kompilacji takiego kodu kompilatorem w wersji 1.5 się naturalnie nie powiedzie, chyba że powiemy explicite, że mamy do czynienia ze starym kodem, tj. dodamy do polecenia ‘javac’ flagę ‘–source 1.3’. Kod wówczas skompiluje się poprawnie, aczkolwiek wygenerowane będą ostrzeżenia.

Aby uruchomić program w trybie sprawdzania asercji należy do polecenia ‘java’ dodać flagę ‘-enableassertions’, lub w formie skróconej ‘-ea’. Asercje można włączać także selektywnie, dla konkretnych klas lub pakietów, podając nazwę klasy lub pakietu po fladze. Polecenia te dotyczą wszystkich managerów ładowania klas (ang. class loader) a więc wszystkich klas, z wyjątkiem klas systemowych, które obsługiwane są przez inny mechanizm ładowania. Aby włączyć sprawdzanie asercji dla klas systemowych należy użyć flagi ‘-enablesystemassertions’ lub ‘-esa’.

Asercje wyłączone są domyślnie, ale można je wyłączyć także explicite – ma to sens wówczas, gdy włączamy asercje globalnie, lub dla pewnego pakietu klas, a wyłączamy selektywnie dla niektórych klas lub pod pakietów. Do wyłączenia asercji używamy flagi ‘-disableassertions’ lub ‘-da’, oraz ‘-disablesystemassertions’ lub ‘-dsa’ dla klas systemowych. Kilka przykładów poniżej.

java –ea my.package.MainClass

java –ea:my.package.MyClass my.package.MainClass

// uwaga na trójkropek po nazwie pakietu!
java –ea:my.package… my.package.MainClass

java –esa –ea –da:my.package.MyClass my.package.MainClass

Wiemy już jak pracuje się z asercjami, przejdźmy więc w końcu do tego, no z największym prawdopodobieństwem może pojawić się na egzaminie SCJP – wytycznych SUN’a, kiedy asercji używać, a kiedy nie. Oto garść prostych reguł:

Nie używaj asercji do walidacji parametrów wywołania funkcji publicznych. Walidacja ta powinna być wykonywana zawsze, więc mechanizm asercji nie jest odpowiedni, jako że można go włączać i wyłączać, a co więcej, w gotowych aplikacjach przeważnie jest wyłączony.

Nie używaj asercji do walidacji parametrów przekazanych z linii poleceń. Powód jest dokładnie taki sam, jak dla wywołań funkcji publicznych – w końcu funkcja main(…) to funkcja publiczna.

Używaj natomiast asercji do walidacji parametrów wywołania funkcji prywatnych. W przeciwieństwie do funkcji publicznych, te wywołania są w pełni kontrolowane przez osobę, która zaimplementowała klasę. Walidacja parametrów ma tu znaczenie tylko kontrolne, na etapie testowania, więc asercje są OK.

Używaj asercji do upewnienia się, że nie wystąpiła sytuacja, która nie powinna była wystąpić w żadnych warunkach - jeśli wystąpiła to znaczy, że program zawiera błąd. Przykładowo, jeśli z naszych kalkulacji wynika, że nie jest możliwe, aby wykonanie programu dotarło do pewnego miejsca (np. gąszcz if-else) to umieśćmy w tym miejscu instrukcję ‘assert false;’ aby zwalidować poprawność naszych kalkulacji.

Wyrażenia używane przez asercje nie powinny mieć efektów ubocznych. Zarówno wyrażenie logiczne jak i komentarz mogą być wywołaniami funkcji, a te mogą mieć skutki uboczne. Mogą mieć, ale zdecydowanie nie powinny.

1 komentarz:

morisil pisze...

"Używaj asercji do upewnienia się, że nie wystąpiła sytuacja, która nie powinna była wystąpić w żadnych warunkach"

Dobry przykład to łapanie IOException po zamknięciu StringWritera. Taki exception nigdy nie zostanie rzucony. Ale co jeśli nasz kod operuje na StringWriter podanym jako parametr wywołania metody. Możliwy jest przypadkowy lub celowy atak na nasz kod polegający na przekazaniu instancji dziedziczącej ze StringWriter z nadpisaną metodą close(), która jednak rzuci IOException.


Do czego zmierzam - jeśli jakaś sekcja kodu (obsługa wyjątku, switch-case, if) jest typu "should never happen", a jednak z jakichś powodów wystapi i to na produkcji, to chcielibyśmy się o tym dowiedzieć. Zastosowanie po prostu assert false tego nie gwarantule. Ja w takich sytuacjach robię po prostu:

throw new AssertionError();

Wzięło mi się to z przeglądania kodów źródłowych bibliotek systemowych Javy. tam jest bardzo dużo tego typu zastosowań. O ile rozumiem rekomendacje Suna w tym względzie, to rzucanie AssertionError przy "should never happen" należy traktować jako "zalecaną alternatywę" dla assert false

http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html