Co robi słowo kluczowe aserta Java i kiedy należy go użyć?

601

Jakie są przykłady z życia, aby zrozumieć kluczową rolę twierdzeń?

Praveen
źródło
8
W prawdziwym życiu prawie nigdy ich nie widzisz. Przypuszczenie: Jeśli używasz asercji, musisz pomyśleć o trzech stanach: Assert przechodzi, assert kończy się niepowodzeniem, assert jest wyłączony, a nie tylko dwa. Asercja jest domyślnie wyłączona, więc jest to najbardziej prawdopodobny stan i trudno jest upewnić się, że jest włączona dla Twojego kodu. To, co składa się na to, to, że stwierdzenia są przedwczesną optymalizacją, która miałaby ograniczone zastosowanie. Jak widać w odpowiedzi @ Bjorna, ciężko jest nawet wymyślić przypadek użycia, w którym nie chciałbyś cały czas zawieść twierdzenia.
Yishai
35
@Yishai: „musisz pomyśleć o… twierdzenie jest wyłączone” Jeśli musisz to zrobić, robisz to źle. „twierdzenia są przedwczesną optymalizacją ograniczonego użycia” . Oto stanowisko firmy Sun: „ Korzystanie z asercji w technologii Java ”, a także warto przeczytać: „ Korzyści z programowania z
asercjami
5
@DavidTonhofer, w prawdziwym życiu prawie nigdy ich nie widzisz. To jest weryfikowalne. Sprawdź tyle projektów open source, ile chcesz. Nie twierdzę, że nie weryfikujesz niezmienników. To nie to samo. Innymi słowy. Jeśli twierdzenia są tak ważne, dlaczego są domyślnie wyłączone?
Yishai
17
Odniesienie, FWIW: Związek między twierdzeniami oprogramowania a jakością kodu : „Porównujemy również skuteczność twierdzeń z popularnymi technikami wykrywania błędów, takimi jak narzędzia do analizy statycznej kodu źródłowego. Z naszego studium przypadku wynika, że ​​wraz ze wzrostem gęstości asercji w pliku występuje statystycznie istotny spadek gęstości uszkodzeń. ”
David Tonhofer
4
@DavidTonhofer David, myślę, że twoja miłość do asercji dotyczy bardzo specyficznego rodzaju programowania, który robicie, w mojej dziedzinie, która współpracuje z aplikacjami internetowymi wychodzącymi z programu z KAŻDEGO powodu jest największym NIE NIE - osobiście nigdy nie używane twierdzenie inne niż badanie jednostkowe /
całkowite

Odpowiedzi:

426

Asercje (za pomocą słowa kluczowego assert ) zostały dodane w Javie 1.4. Służą do weryfikacji poprawności niezmiennika w kodzie. Nigdy nie powinny być uruchamiane w kodzie produkcyjnym i wskazują na błąd lub niewłaściwe użycie ścieżki kodu. Można je aktywować w czasie wykonywania za pomocą -eaopcji w javapoleceniu, ale domyślnie nie są włączone.

Przykład:

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}
Ophidian
źródło
71
W rzeczywistości Oracle mówi, aby nie assertsprawdzać parametrów metody publicznej ( docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html ). To powinno rzucić Exceptionzamiast zabijać program.
SJuan76,
10
Ale nadal nie wyjaśniasz, dlaczego one istnieją. Dlaczego nie możesz wykonać sprawdzenia if () i zgłosić wyjątek?
El Mac
7
@ElMac - twierdzenia dotyczą części cyklu tworzenia / debugowania / testowania - nie są przeznaczone do produkcji. Blok if działa w prod. Proste twierdzenia nie złamią banku, ale drogie twierdzenia, które dokonują złożonej weryfikacji danych, mogą obniżyć środowisko produkcyjne, dlatego są tam wyłączane.
hoodaticus
2
@hoodaticus masz na myśli wyłącznie fakt, że mogę włączać / wyłączać wszystkie asercje dla kodu prod jest powodem? Ponieważ i tak mogę przeprowadzić złożoną walidację danych, a następnie obsłużyć ją z wyjątkami. Jeśli mam kod produkcyjny, mógłbym wyłączyć złożone (i być może drogie) twierdzenia, ponieważ powinien on działać i był już przetestowany? Teoretycznie nie powinny obniżać programu, ponieważ wtedy i tak miałbyś problem.
El Mac
8
This convention is unaffected by the addition of the assert construct. Do not use assertions to check the parameters of a public method. An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. It can throw only an AssertionError. docs.oracle.com/javase/8/docs/technotes/guides/language/…
Bakhshi
325

Załóżmy, że masz napisać program do sterowania elektrownią jądrową. Jest oczywiste, że nawet najmniejszy błąd może mieć katastrofalne skutki, dlatego twój kod musi być wolny od błędów (zakładając, że JVM jest wolny od błędów dla samego argumentu).

Java nie jest językiem weryfikowalnym, co oznacza: nie można obliczyć, że wynik Twojej operacji będzie doskonały. Głównym tego powodem są wskaźniki: mogą wskazywać gdziekolwiek lub nigdzie, dlatego nie można ich obliczyć tak, aby miały tę dokładną wartość, przynajmniej nie w rozsądnym zakresie kodu. Biorąc pod uwagę ten problem, nie można w żaden sposób udowodnić, że kod jest poprawny. Ale co możesz zrobić, to udowodnić, że przynajmniej znajdziesz każdy błąd, kiedy to się stanie.

Pomysł ten oparty jest na paradygmacie Design-by-Contract (DbC): najpierw definiujesz (z matematyczną precyzją), co ma zrobić twoja metoda, a następnie weryfikujesz to, testując ją podczas faktycznego wykonania. Przykład:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

Chociaż jest to dość oczywiste, że działa dobrze, większość programistów nie zobaczy ukrytego błędu w tym (podpowiedź: Ariane V uległa awarii z powodu podobnego błędu). Teraz DbC definiuje, że zawsze musisz sprawdzić wejście i wyjście funkcji, aby sprawdzić, czy działała ona poprawnie. Java może to zrobić za pomocą asercji:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
    assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
  final int result = a + b;
    assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
  return result;
}

Jeśli ta funkcja kiedykolwiek zawiedzie, zauważysz ją. Będziesz wiedział, że w kodzie jest problem, wiesz, gdzie on jest i wiesz, co go spowodowało (podobnie jak w wyjątkach). A co jeszcze ważniejsze: przestajesz wykonywać poprawnie, gdy tak się dzieje, aby uniemożliwić dalszemu kodowi pracę z nieprawidłowymi wartościami i potencjalnie spowodować uszkodzenie wszystkiego, co kontroluje.

Wyjątki Java są podobną koncepcją, ale nie wszystko weryfikują. Jeśli chcesz jeszcze więcej kontroli (kosztem szybkości wykonania), musisz użyć asercji. Spowoduje to wzdęcie kodu, ale ostatecznie możesz dostarczyć produkt w zaskakująco krótkim czasie programowania (im wcześniej naprawisz błąd, tym niższy koszt). A ponadto: jeśli w kodzie jest jakiś błąd, to go wykryjesz. Nie ma sposobu, aby błąd prześlizgnął się i spowodował problemy później.

To wciąż nie jest gwarancją kodu wolnego od błędów, ale jest o wiele bliżej niż zwykłe programy.

TwoThe
źródło
29
Wybrałem ten przykład, ponieważ bardzo dobrze prezentuje ukryte błędy w pozornie wolnym od błędów kodzie. Jeśli jest to podobne do tego, co przedstawił ktoś inny, być może mieli na myśli ten sam pomysł. ;)
Two
8
Wybierz aser, ponieważ nie powiedzie się, gdy asercja jest fałszywa. I może mieć dowolne zachowanie. Uderzenie w grzywkę jest zadaniem testów jednostkowych. Korzystanie z funkcji projektowania według umowy dość dobrze określa umowę, ale podobnie jak w przypadku rzeczywistych umów, potrzebujesz kontroli, aby mieć pewność, że są przestrzegane. W przypadku stwierdzeń wstawiany jest organ nadzorczy, który będzie wtedy, gdy umowa zostanie naruszona. Pomyśl o tym jako o dokuczliwym prawniku krzyczącym „NIEWŁAŚCIWY” za każdym razem, gdy robisz coś na zewnątrz lub wbrew podpisanej umowie, a następnie odsyła cię do domu, abyś nie mógł dalej pracować i dalej naruszać umowę!
Eric,
5
Niezbędne w tym prostym przypadku: nie, ale DbC określa, że każdy wynik musi być sprawdzony. Wyobraź sobie, że ktoś modyfikuje teraz tę funkcję w coś znacznie bardziej złożonego, a następnie musi również dostosować kontrolę po sprawdzeniu, a wtedy nagle staje się użyteczna.
TwoThe
4
Przepraszam, że to wskrzeszam, ale mam konkretne pytanie. Jaka jest różnica między tym, co @TwoThe zrobił i zamiast używać aser, po prostu rzucając new IllegalArgumentExceptionwiadomość z? To znaczy, oprócz dodawania o throwsdo deklaracji metody i kodu do zarządzania tym wyjątkiem w innym miejscu. Dlaczego assertinsetad rzucania nowy wyjątek? A może ifzamiast assert? Naprawdę nie mogę tego dostać :(
Blueriver
14
-1: Twierdzenie sprawdzające przepełnienie jest błędne, jeśli amoże być negatywne. Drugie twierdzenie jest bezużyteczne; dla wartości int zawsze jest tak, że a + b - b == a. Ten test może się nie powieść tylko wtedy, gdy komputer jest zasadniczo uszkodzony. Aby bronić się przed tą nieprzewidzianą sytuacją, musisz sprawdzić spójność na wielu procesorach.
kevin cline
63

Asercje to narzędzie w fazie programowania do wychwytywania błędów w kodzie. Zostały zaprojektowane z myślą o łatwym usuwaniu, więc nie będą istnieć w kodzie produkcyjnym. A zatem twierdzenia nie są częścią „rozwiązania”, które dostarczasz klientowi. Są to kontrole wewnętrzne, aby upewnić się, że przyjęte założenia są prawidłowe. Najczęstszym przykładem jest testowanie na zero. Wiele metod zapisano w ten sposób:

void doSomething(Widget widget) {
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

Bardzo często w takiej metodzie widżet po prostu nigdy nie powinien mieć wartości zerowej. Jeśli więc ma wartość zero, w kodzie jest jakiś błąd, który należy wyśledzić. Ale powyższy kod nigdy tego nie powie. Więc w dobrej intencji pisania „bezpiecznego” kodu ukrywasz również błąd. O wiele lepiej jest napisać taki kod:

/**
 * @param Widget widget Should never be null
 */
void doSomething(Widget widget) {
  assert widget != null;
  widget.someMethod(); // ...
    ... // do more stuff with this widget
}

W ten sposób z pewnością złapiesz ten błąd wcześniej. (Przydatne jest również określenie w umowie, że ten parametr nigdy nie powinien mieć wartości zerowej). Pamiętaj, aby włączyć asercje podczas testowania kodu podczas programowania. (Przekonanie do tego kolegów również jest często trudne, co jest dla mnie bardzo denerwujące).

Teraz niektórzy z twoich kolegów sprzeciwiają się temu kodowi, argumentując, że powinieneś nadal sprawdzać wartość zerową, aby uniknąć wyjątku w produkcji. W takim przypadku twierdzenie jest nadal przydatne. Możesz to napisać w ten sposób:

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

W ten sposób Twoi koledzy będą zadowoleni, że istnieje kod zerowy dla kodu produkcyjnego, ale podczas programowania nie ukrywasz już błędu, gdy widget jest zerowy.

Oto przykład z realnego świata: Kiedyś napisałem metodę, która porównała dwie arbitralne wartości równości, przy czym każda z nich może być zerowa:

/**
 * Compare two values using equals(), after checking for null.
 * @param thisValue (may be null)
 * @param otherValue (may be null)
 * @return True if they are both null or if equals() returns true
 */
public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = thisValue.equals(otherValue);
  }
  return result;
}

Ten kod deleguje działanie equals()metody w przypadku, gdy thisValue nie ma wartości null. Zakłada jednak, że equals()metoda poprawnie wypełnia kontrakt equals(), odpowiednio obsługując parametr zerowy.

Kolega sprzeciwił się mojemu kodowi, mówiąc mi, że wiele naszych klas ma błędne equals()metody, które nie sprawdzają wartości zerowej, więc powinienem sprawdzić tę metodę. Jest to dyskusyjne, jeśli jest to rozsądne lub powinniśmy wymusić błąd, abyśmy mogli go wykryć i naprawić, ale odroczyłem się do mojego kolegi i sprawdziłem zerowo, co zaznaczyłem komentarzem:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = otherValue != null && thisValue.equals(otherValue); // questionable null check
  }
  return result;
}

Dodatkowe sprawdzenie tutaj other != nulljest konieczne tylko wtedy, gdy equals()metoda nie sprawdza, czy wartość null jest wymagana w umowie.

Zamiast angażować się w bezowocną debatę z moim kolegą na temat mądrości pozwalającej błędnemu kodowi pozostać w naszej bazie kodu, po prostu umieszczam w nim dwa stwierdzenia. Te twierdzenia dadzą mi znać, na etapie programowania, jeśli jedna z naszych klas nie zaimplementuje się equals()poprawnie, więc mogę to naprawić:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
    assert otherValue == null || otherValue.equals(null) == false;
  } else {
    result = otherValue != null && thisValue.equals(otherValue);
    assert thisValue.equals(null) == false;
  }
  return result;
}

Należy pamiętać o następujących kwestiach:

  1. Asercje są tylko narzędziami w fazie rozwoju.

  2. Istotą tego stwierdzenia jest powiadomienie użytkownika, jeśli wystąpi błąd, nie tylko w kodzie, ale w bazie kodu . (Asercje tutaj będą oznaczać błędy w innych klasach).

  3. Nawet jeśli mój kolega był przekonany, że nasze zajęcia zostały poprawnie napisane, stwierdzenia tutaj nadal byłyby przydatne. Zostaną dodane nowe klasy, które mogą nie przetestować pod kątem wartości NULL, a ta metoda może oznaczać te błędy za nas.

  4. Podczas programowania zawsze powinieneś włączać asercje, nawet jeśli napisany kod nie używa asercji. Moje IDE jest ustawione tak, aby zawsze domyślnie robiło to dla każdego nowego pliku wykonywalnego.

  5. Asercje nie zmieniają zachowania kodu w środowisku produkcyjnym, więc mój kolega jest szczęśliwy, że istnieje kontrola zerowa i że ta metoda wykona się poprawnie, nawet jeśli equals()metoda jest błędna. Cieszę się, bo złapię jakąkolwiek błędną equals()metodę w fazie rozwoju.

Powinieneś również przetestować swoje zasady asercji, wprowadzając tymczasowe potwierdzenie, które się nie powiedzie, dzięki czemu będziesz mieć pewność, że otrzymasz powiadomienie za pośrednictwem pliku dziennika lub śledzenia stosu w strumieniu wyjściowym.

MiguelMunoz
źródło
Dobre punkty na temat „ukrywania błędu” i tego, jak zapewnia ujawnianie błędów podczas programowania!
nobar
Żadna z tych kontroli nie jest powolna, więc nie ma powodu, aby wyłączać je w produkcji. Powinny zostać przekonwertowane na instrukcje rejestrowania, aby można było wykryć problemy, które nie pojawiają się w „fazie rozwoju”. (Naprawdę nie ma czegoś takiego jak faza programowania. Programowanie kończy się, gdy zdecydujesz się w ogóle przestać utrzymywać swój kod.)
Aleksandr Dubinsky
20

Wiele dobrych odpowiedzi wyjaśniających, na czym assertpolega słowo kluczowe, ale niewiele osób odpowiada na prawdziwe pytanie: „kiedy należy assertużywać słowa kluczowego w prawdziwym życiu?”

Odpowiedź: prawie nigdy .

Twierdzenia, jako koncepcja, są cudowne. Dobry kod ma wiele if (...) throw ...instrukcji (i ich krewnych, takich jak Objects.requireNonNulliMath.addExact ). Jednak niektóre decyzje projektowe znacznie ograniczyły użyteczność samego assert słowa kluczowego .

Ideą przewodnią tego assertsłowa kluczowego jest przedwczesna optymalizacja, a główną funkcją jest łatwe wyłączenie wszystkich kontroli. W rzeczywistości assertkontrole są domyślnie wyłączone.

Jednak niezwykle ważne jest, aby nadal przeprowadzano kontrole niezmiennicze w produkcji. Wynika to z faktu, że doskonałe pokrycie testowe jest niemożliwe, a cały kod produkcyjny będzie zawierał błędy, których stwierdzenia powinny pomóc w zdiagnozowaniu i złagodzeniu.

Dlatego if (...) throw ...należy preferować użycie , podobnie jak jest to wymagane do sprawdzania wartości parametrów metod publicznych i do rzucania IllegalArgumentException.

Czasami można pokusić się o napisanie niezmiennego czeku, którego przetworzenie zajmuje niepożądanie dużo czasu (i jest często wywoływane, aby miało znaczenie). Jednak takie kontrole spowolnią testowanie, co również jest niepożądane. Takie czasochłonne kontrole są zwykle zapisywane jako testy jednostkowe. Niemniej jednak czasami warto używać asserttego powodu.

Nie używaj assertpo prostu dlatego, że jest czystszy i ładniejszy niż if (...) throw ...(i mówię to z wielkim bólem, ponieważ lubię czyste i ładne). Jeśli po prostu nie możesz sobie pomóc i możesz kontrolować sposób uruchamiania aplikacji, możesz swobodnie korzystać, assertale zawsze włączaj asercje w środowisku produkcyjnym. Trzeba przyznać, że tak właśnie robię. Jestem naciska na adnotacji lombok że spowoduje assertdziałać bardziej jak if (...) throw .... Głosuj na to tutaj.

(Rant: deweloperzy JVM byli bandą okropnych, przedwcześnie optymalizujących programistów. Dlatego słyszysz o tylu problemach bezpieczeństwa we wtyczce Java i JVM. Odmówili włączenia podstawowych kontroli i asercji w kodzie produkcyjnym, a my nadal kontynuujemy zapłacić cene.)

Aleksandr Dubinsky
źródło
2
@aberglas Klauzula catch-all jest catch (Throwable t). Nie ma powodu, aby nie próbować łapać w pułapkę, rejestrować lub próbować / odzyskiwać z OutOfMemoryError, AssertionError itp.
Aleksandr Dubinsky
1
Złapałem i odzyskałem z OutOfMemoryError.
MiguelMunoz,
1
Nie zgadzam się Wiele moich twierdzeń jest używanych do upewnienia się, że moje API jest poprawnie wywoływane. Na przykład mogę napisać prywatną metodę, która powinna być wywoływana tylko wtedy, gdy obiekt posiada blokadę. Jeśli inny programista wywoła tę metodę z części kodu, która nie blokuje obiektu, asercja natychmiast powie, że popełniła błąd. Istnieje wiele takich błędów, które z pewnością mogą zostać złapane w fazie rozwoju, a stwierdzenia są bardzo przydatne w takich przypadkach.
MiguelMunoz
2
@MiguelMunoz W mojej odpowiedzi powiedziałem, że idea twierdzeń jest bardzo dobra. To wdrożenie assertsłowa kluczowego jest złe. Zmienię swoją odpowiedź, aby wyjaśnić, że mam na myśli słowo kluczowe, a nie pojęcie.
Aleksandr Dubinsky
2
Podoba mi się fakt, że zgłasza błąd AssertionError zamiast wyjątku. Zbyt wielu programistów wciąż nie nauczyło się, że nie powinni wychwytywać wyjątku, jeśli kod może wygenerować tylko coś takiego jak wyjątek IOException. Mam błędy w moim kodzie, które zostały całkowicie połknięte, ponieważ ktoś złapał wyjątek. Asercje nie zostają uwięzione w tej pułapce. Wyjątek stanowią sytuacje, których można się spodziewać w kodzie produkcyjnym. Jeśli chodzi o rejestrowanie, powinieneś także rejestrować wszystkie swoje błędy, nawet jeśli błędy są rzadkie. Na przykład, czy naprawdę chcesz pozwolić OutOfMemoryError przejść bez logowania?
MiguelMunoz
14

Oto najczęstszy przypadek użycia. Załóżmy, że włączasz wartość wyliczania:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
}

Tak długo, jak załatwisz każdą sprawę, nic ci nie będzie. Ale pewnego dnia ktoś doda figę do twojego wyliczenia i zapomni dodać go do instrukcji switch. Powoduje to błąd, który może być trudny do złapania, ponieważ efekty nie będą odczuwalne, dopóki nie opuścisz instrukcji switch. Ale jeśli napiszesz swój przełącznik w ten sposób, możesz go natychmiast złapać:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}
MiguelMunoz
źródło
4
Dlatego powinieneś mieć włączone ostrzeżenia, a ostrzeżenia traktowane jako błędy. Każdy przyzwoity kompilator jest w stanie powiedzieć ci, jeśli tylko pozwolisz mu powiedzieć, że brakuje ci sprawdzania wyliczenia, i zrobi to w czasie kompilacji, co jest niewiarygodnie lepsze niż (być może pewnego dnia) odkrycie o czas pracy.
Mike Nakis,
9
dlaczego warto tu zastosować stwierdzenie, a nie jakiś wyjątek, np. nielegalny wyjątek?
liltitus27
4
Wyrzuci to, AssertionErrorjeśli asercje są włączone ( -ea). Jakie jest pożądane zachowanie w produkcji? Cichy brak operacji i potencjalna katastrofa na późniejszym etapie egzekucji? Prawdopodobnie nie. Sugerowałbym wyraźne throw new AssertionError("Missing enum value: " + fruit);.
aioobe,
1
Istnieje dobry argument za rzuceniem AsercjiError. Jeśli chodzi o prawidłowe zachowanie w produkcji, cały sens twierdzeń polega na tym, aby nie dopuścić do tego, aby miało to miejsce w produkcji. Asercje to narzędzie do opracowywania błędów, które można łatwo usunąć z kodu produkcyjnego. W takim przypadku nie ma powodu, aby usunąć go z kodu produkcyjnego. Ale w wielu przypadkach testy integralności mogą spowolnić proces. Umieszczając te testy w asercjach, które nie są używane w kodzie produkcyjnym, możesz pisać dokładne testy, nie martwiąc się, że spowolnią Twój kod produkcyjny.
MiguelMunoz,
To wydaje się nie tak. IMHO nie należy używać, defaultaby kompilator ostrzegał Cię o brakujących przypadkach. Możesz returnzamiast tego break(może to wymagać wyodrębnienia metody), a następnie obsłużyć brakującą literę po przełączeniu. W ten sposób otrzymujesz zarówno ostrzeżenie, jak i okazję assert.
maaartinus
12

Asercje służą do sprawdzania warunków wstępnych i warunki wstępne „nigdy nie powinny zawieść”. Prawidłowy kod nigdy nie powinien zawieść twierdzenia; po uruchomieniu powinny wskazać błąd (mam nadzieję, że w miejscu, które jest blisko miejsca, w którym znajduje się faktyczne miejsce problemu).

Przykładem takiego stwierdzenia może być sprawdzenie, czy określona grupa metod jest wywoływana we właściwej kolejności (np. Która hasNext()jest wywoływana wcześniej next()w an Iterator).

Donal Fellows
źródło
1
Nie musisz wywoływać hasNext () przed next ().
DJClayworth,
6
@DJClayworth: Nie musisz też unikać wyzwalania twierdzeń. :-)
Donal Fellows
8

Do czego służy słowo kluczowe assert w Javie?

Spójrzmy na skompilowany kod bajtowy.

Dochodzimy do wniosku, że:

public class Assert {
    public static void main(String[] args) {
        assert System.currentTimeMillis() == 0L;
    }
}

generuje prawie taki sam kod bajtowy jak:

public class Assert {
    static final boolean $assertionsDisabled =
        !Assert.class.desiredAssertionStatus();
    public static void main(String[] args) {
        if (!$assertionsDisabled) {
            if (System.currentTimeMillis() != 0L) {
                throw new AssertionError();
            }
        }
    }
}

gdzie Assert.class.desiredAssertionStatus()jest truekiedy-ea jest przekazywany w wierszu poleceń, a false w przeciwnym razie.

Używamy, System.currentTimeMillis()aby upewnić się, że nie zostanie zoptymalizowany ( assert true;zrobił).

Pole syntetyczne jest generowane tak, że Java musi wywoływać Assert.class.desiredAssertionStatus()tylko raz w czasie ładowania, a następnie buforuje tam wynik. Zobacz także: Co oznacza „syntetyczny materiał statyczny”?

Możemy to sprawdzić za pomocą:

javac Assert.java
javap -c -constants -private -verbose Assert.class

W Oracle JDK 1.8.0_45 wygenerowano syntetyczne pole statyczne (patrz także: Co to znaczy „statyczny syntetyczny”? ):

static final boolean $assertionsDisabled;
  descriptor: Z
  flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

wraz ze statycznym inicjatorem:

 0: ldc           #6                  // class Assert
 2: invokevirtual #7                  // Method java/lang Class.desiredAssertionStatus:()Z
 5: ifne          12
 8: iconst_1
 9: goto          13
12: iconst_0
13: putstatic     #2                  // Field $assertionsDisabled:Z
16: return

a główną metodą jest:

 0: getstatic     #2                  // Field $assertionsDisabled:Z
 3: ifne          22
 6: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
 9: lconst_0
10: lcmp
11: ifeq          22
14: new           #4                  // class java/lang/AssertionError
17: dup
18: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return

Stwierdzamy, że:

  • nie ma obsługi poziomu kodu bajtowego dla assert: jest to koncepcja języka Java
  • assertmogłyby być naśladowane całkiem dobrze z właściwości systemu -Pcom.me.assert=true, aby zastąpić -eaw linii poleceń, a throw new AssertionError().
Ciro Santilli
źródło
2
Więc catch (Throwable t)klauzula jest w stanie wychwycić również naruszenia twierdzeń? Dla mnie ogranicza to ich użyteczność tylko do przypadku, gdy treść twierdzenia jest czasochłonna, co jest rzadkością.
Evgeni Sergeevev,
1
Nie jestem pewien, dlaczego ogranicza to przydatność tego stwierdzenia. Nie powinieneś nigdy łapać Ogniwa, z wyjątkiem bardzo rzadkich przypadków. Jeśli musisz złapać Throwable, ale nie chcesz, aby łapał on twierdzenia, możesz po prostu złapać AssertionErrorpierwszy i ponownie go rzucić.
MiguelMunoz,
7

Przykład ze świata rzeczywistego, z klasy stosu (z twierdzeń w artykułach Java )

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}
Björn
źródło
80
Byłoby to źle oceniane w C: Asercja jest czymś, czego NIGDY NIGDY nie powinno się zdarzyć - wyrzucenie pustego stosu powinno wyrzucić NoElementsException lub coś w tym stylu. Zobacz odpowiedź Donala.
Konerak
4
Zgadzam się. Mimo że pochodzi z oficjalnego samouczka, jest to zły przykład.
DJClayworth
7
Prawdopodobnie jest tam wyciek pamięci. Powinieneś ustawić stos [num] = null; w celu prawidłowego wykonania GC.
H.Rabiee
3
Myślę, że w metodzie prywatnej poprawne byłoby użycie twierdzenia, ponieważ byłoby dziwnie mieć wyjątki dotyczące nieprawidłowego działania klasy lub metody. W metodzie publicznej, wywołując ją z zewnątrz, nie można tak naprawdę powiedzieć, w jaki sposób korzysta z niej inny kod. Czy to naprawdę sprawdza, czy isEmpty () czy nie? Nie wiesz
Vlasec,
7

Asercja pozwala wykryć defekty w kodzie. Możesz włączyć asercje do testowania i debugowania, pozostawiając je wyłączone, gdy program jest w produkcji.

Po co coś potwierdzać, skoro wiesz, że to prawda? Jest to prawdą tylko wtedy, gdy wszystko działa poprawnie. Jeśli program ma wadę, może nie być prawdą. Wykrywanie tego na wcześniejszym etapie pozwala stwierdzić, że coś jest nie tak.

assertOświadczenie zawiera to oświadczenie wraz z opcjonalnym Stringwiadomości.

Składnia instrukcji assert ma dwie formy:

assert boolean_expression;
assert boolean_expression: error_message;

Oto kilka podstawowych zasad, które określają, gdzie należy stosować twierdzenia, a gdzie nie. Asercje należy stosować do:

  1. Sprawdzanie poprawności parametrów wejściowych metody prywatnej. NIE dla metod publicznych. publicmetody powinny generować regularne wyjątki, gdy zostaną przekazane złe parametry.

  2. W dowolnym miejscu w programie, aby zapewnić ważność faktu, który prawie na pewno jest prawdziwy.

Na przykład, jeśli masz pewność, że będzie to tylko 1 lub 2, możesz zastosować takie twierdzenie:

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...
  1. Sprawdzanie poprawności warunków pocztowych na końcu dowolnej metody. Oznacza to, że po wykonaniu logiki biznesowej można użyć asercji, aby upewnić się, że wewnętrzny stan zmiennych lub wyników jest zgodny z oczekiwaniami. Na przykład metoda, która otwiera gniazdo lub plik, może użyć potwierdzenia na końcu, aby upewnić się, że gniazdo lub plik są rzeczywiście otwarte.

Asercji nie należy stosować do:

  1. Sprawdzanie poprawności parametrów wejściowych metody publicznej. Ponieważ twierdzenia nie zawsze mogą być wykonywane, należy zastosować mechanizm regularnych wyjątków.

  2. Sprawdzanie poprawności ograniczeń wprowadzanych przez użytkownika. Tak samo jak powyżej.

  3. Nie powinien być stosowany w przypadku działań niepożądanych.

Na przykład nie jest to właściwe zastosowanie, ponieważ w tym przypadku twierdzenie jest używane ze względu na efekt uboczny wywołania doSomething()metody.

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

Jedynym przypadkiem, w którym można to uzasadnić, jest próba ustalenia, czy asercje są włączone w kodzie:   

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}
solomkinmv
źródło
5

Oprócz wszystkich wspaniałych odpowiedzi tutaj podanych, oficjalny przewodnik po programowaniu Java SE 7 zawiera dość zwięzłą instrukcję obsługi assert; z kilkoma przykładowymi przykładami, kiedy dobrym (i, co ważniejsze, złym) pomysłem jest używanie asercji, i czym różni się od zgłaszania wyjątków.

Połączyć

Ivan Bartsov
źródło
1
Zgadzam się. Artykuł zawiera wiele doskonałych przykładów. Szczególnie podobało mi się to, aby upewnić się, że metoda jest wywoływana tylko wtedy, gdy obiekt posiada blokadę.
MiguelMunoz
4

Assert jest bardzo przydatny podczas programowania. Używasz go, gdy coś po prostu nie może wydarzyć, jeśli Twój kod działa poprawnie. Jest łatwy w użyciu i może pozostać w kodzie na zawsze, ponieważ zostanie wyłączony w prawdziwym życiu.

Jeśli istnieje jakakolwiek szansa, że ​​stan ten może wystąpić w prawdziwym życiu, musisz sobie z tym poradzić.

Uwielbiam to, ale nie wiem, jak go włączyć w Eclipse / Android / ADT. Wydaje się, że jest wyłączony nawet podczas debugowania. (Jest w tym wątek, ale odnosi się do „Java vm”, który nie pojawia się w konfiguracji uruchamiania ADT).

John White
źródło
1
Aby włączyć asercję w środowisku IDE zaćmienia, postępuj zgodnie z tutoringcenter.cs.usfca.edu/resources/…
Ayaz Pasha
Nie sądzę, że istnieje sposób na włączenie asercji w Androidzie. To jest bardzo rozczarowujące.
MiguelMunoz
3

Oto stwierdzenie, które napisałem na serwerze dla projektu Hibernacja / SQL. Komponent bean miał dwie właściwości typu boolean, o nazwie isActive i isDefault. Każdy może mieć wartość „Y” lub „N” lub null, co zostało potraktowane jako „N”. Chcemy mieć pewność, że klient przeglądarki jest ograniczony do tych trzech wartości. Tak więc w moich ustawieniach dla tych dwóch właściwości dodałem to stwierdzenie:

assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;

Zwróć uwagę na następujące kwestie.

  1. To twierdzenie dotyczy tylko fazy rozwoju. Jeśli klient wyśle ​​złą wartość, złapiemy to wcześnie i naprawimy na długo przed osiągnięciem produkcji. Twierdzenia dotyczą wad, które można wcześnie wykryć.

  2. To twierdzenie jest powolne i nieefektywne. W porządku Asercje mogą być wolne. Nie obchodzi nas to, ponieważ są to narzędzia przeznaczone tylko do programowania. Nie spowolni to kodu produkcyjnego, ponieważ asercje zostaną wyłączone. (W tej kwestii jest trochę nieporozumień, do których dojdę później.) To prowadzi do mojego następnego punktu.

  3. To twierdzenie nie ma skutków ubocznych. Mógłbym przetestować swoją wartość w stosunku do niezmodyfikowanego statycznego zestawu końcowego, ale ten zestaw pozostałby w produkcji, gdzie nigdy by się nie wykorzystał.

  4. To twierdzenie istnieje, aby zweryfikować poprawne działanie klienta. Zanim dotrzemy do produkcji, będziemy mieć pewność, że klient działa poprawnie, dzięki czemu możemy bezpiecznie wyłączyć potwierdzenie.

  5. Niektóre osoby pytają: jeśli twierdzenie nie jest potrzebne w produkcji, dlaczego nie po prostu je wyjąć, gdy skończysz? Ponieważ nadal będziesz ich potrzebować, gdy zaczniesz pracę nad kolejną wersją.

Niektórzy twierdzą, że nigdy nie powinieneś używać twierdzeń, ponieważ nigdy nie możesz być pewien, że wszystkie błędy zniknęły, więc musisz je zachować, nawet podczas produkcji. Dlatego nie ma sensu używać instrukcji assert, ponieważ jedyną zaletą stwierdzeń jest to, że można je wyłączyć. Dlatego, zgodnie z tym tokiem myślenia, nie powinieneś (prawie) nigdy używać twierdzeń. Nie zgadzam się. Z pewnością prawdą jest, że jeśli test należy do produkcji, nie powinieneś używać twierdzenia. Ale ten test nie należy do produkcji. Ten służy do wychwytywania błędu, który prawdopodobnie nigdy nie osiągnie produkcji, więc można go bezpiecznie wyłączyć, gdy skończysz.

BTW, mogłem napisać tak:

assert value == null || value.equals("Y") || value.equals("N") : value;

Jest to w porządku tylko dla trzech wartości, ale jeśli liczba możliwych wartości rośnie, wersja HashSet staje się wygodniejsza. Wybrałem wersję HashSet, aby zwrócić uwagę na wydajność.

MiguelMunoz
źródło
Wątpię, czy użycie tak małego a HashSetprzynosi jakąkolwiek przewagę prędkości nad ArrayList. Co więcej, zestaw i lista dominują w czasie wyszukiwania. Przydałyby się przy użyciu stałej. To wszystko powiedział +1.
maaartinus
Wszystko prawda. Zrobiłem to w nieefektywny sposób, aby zilustrować moją tezę, że twierdzenia mogą być wolne. Ten może być bardziej wydajny, ale są inni, którzy nie mogą. W doskonałej książce zatytułowanej „Pisanie kodu stałego” Steve Maguire mówi o twierdzeniu w programie Microsoft Excel dotyczącym przetestowania nowego kodu aktualizacji przyrostowej, który pomija komórki, które nie powinny się zmieniać. Za każdym razem, gdy użytkownik dokonuje zmiany, asercja ponownie oblicza cały arkusz kalkulacyjny, aby upewnić się, że wyniki są zgodne z funkcją aktualizacji przyrostowej. To naprawdę spowolniło wersję debugowania, ale wcześnie wykryli wszystkie swoje błędy.
MiguelMunoz
W pełni uzgodnione. Asercje są rodzajem testów - są mniej uniwersalne niż zwykłe testy, ale mogą obejmować metody prywatne i są znacznie tańsze w pisaniu. Spróbuję ich użyć jeszcze bardziej.
maaartinus
2

Asercja jest zasadniczo używana do debugowania aplikacji lub jest używana w zamian za obsługę wyjątków dla niektórych aplikacji w celu sprawdzenia ważności aplikacji.

Asercja działa w czasie wykonywania. Prosty przykład, który może wyjaśnić całą koncepcję w prosty sposób, znajduje się tutaj - Co robi słowo kluczowe assert w Javie? (WikiAnswers).

SBTec
źródło
2

Asercje są domyślnie wyłączone. Aby je włączyć, musimy uruchomić program z -eaopcjami (szczegółowość można zmieniać). Na przykład java -ea AssertionsDemo.

Istnieją dwa formaty korzystania z asercji:

  1. Proste: np. assert 1==2; // This will raise an AssertionError.
  2. Lepiej: assert 1==2: "no way.. 1 is not equal to 2"; podniesie błąd AssertionError z wyświetlonym komunikatem, a zatem jest lepszy. Chociaż w rzeczywistej składni assert expr1:expr2wyrażeniem2 może być dowolne wyrażenie zwracające wartość, używałem go częściej tylko do drukowania wiadomości.
Chandan Purohit
źródło
1

Podsumowując (dotyczy to wielu języków, nie tylko Java):

„assert” jest przede wszystkim używany przez programistów jako pomoc przy debugowaniu podczas procesu debugowania. Komunikaty assert nigdy nie powinny się pojawiać. Wiele języków udostępnia opcję czasu kompilacji, która spowoduje, że wszystkie „twierdzenia” zostaną zignorowane, do użycia przy generowaniu kodu „produkcyjnego”.

„wyjątki” są wygodnym sposobem radzenia sobie z wszelkimi rodzajami błędów, niezależnie od tego, czy reprezentują one błędy logiczne, ponieważ jeśli napotkasz taki błąd, że nie możesz kontynuować, możesz po prostu „wyrzucić je w powietrze, „gdziekolwiek jesteś, oczekując, że ktoś będzie gotowy„ złapać ”ich. Kontrola jest przekazywana w jednym kroku, prosto z kodu, który wyrzucił wyjątek, prosto do rękawicy łapacza. (I łapacz może zobaczyć pełny ślad połączeń, które miały miejsce.)

Co więcej, osoby dzwoniące z tego podprogramu nie muszą sprawdzać, czy podprogram się powiódł: „jeśli już tu jesteśmy, to musiałby się udać, ponieważ w przeciwnym razie wygenerowałby wyjątek i nie byłoby nas tutaj!”Ta prosta strategia znacznie ułatwia projektowanie i debugowanie kodu.

Wyjątki dogodnie pozwalają, aby warunki błędu krytycznego były takimi, jakimi są: „wyjątki od reguły”. I dla nich będzie obsługiwana przez ścieżkę kodu, która jest również „wyjątkiem od reguły… ” fly ball! ”

Mike Robinson
źródło
1

Asercje to kontrole, które mogą zostać wyłączone. Są rzadko używane. Dlaczego?

  • Nie wolno ich używać do sprawdzania argumentów metody publicznej, ponieważ nie masz nad nimi kontroli.
  • Nie należy ich używać do prostych kontroli, result != nullponieważ takie kontrole są bardzo szybkie i nie ma prawie nic do zapisania.

Co zostało? Drogie sprawdza warunkach naprawdę oczekiwać , aby mogło być prawdziwe. Dobrym przykładem mogą być niezmienniki struktury danych, takie jak drzewo RB. W rzeczywistości w ConcurrentHashMapJDK8 istnieje kilka takich znaczących twierdzeń dotyczących TreeNodes.

  • Naprawdę nie chcesz włączać ich w produkcji, ponieważ mogą z łatwością zdominować czas pracy.
  • Możesz je włączyć lub wyłączyć podczas testów.
  • Na pewno chcesz je włączyć podczas pracy z kodem.

Czasami czek nie jest naprawdę drogi, ale jednocześnie jesteś pewien, że minie. W moim kodzie jest np.

assert Sets.newHashSet(userIds).size() == userIds.size();

gdzie jestem całkiem pewien, że właśnie utworzona lista zawiera unikalne elementy, ale chciałem ją udokumentować i dokładnie sprawdzić.

maaartinus
źródło
0

Zasadniczo „potwierdź prawda” przejdzie, a „potwierdź fałsz” nie powiedzie się. Zobaczmy, jak to będzie działać:

public static void main(String[] args)
{
    String s1 = "Hello";
    assert checkInteger(s1);
}

private static boolean checkInteger(String s)
{
    try {
        Integer.parseInt(s);
        return true;
    }
    catch(Exception e)
    {
        return false;
    }
}
Peter Mortensen
źródło
-8

assertjest słowem kluczowym. Został wprowadzony w JDK 1.4. Istnieją dwa rodzaje asserts

  1. Bardzo proste assertstwierdzenia
  2. Proste assertinstrukcje.

Domyślnie wszystkie assertinstrukcje nie zostaną wykonane. Jeśli assertinstrukcja otrzyma wartość false, wówczas automatycznie zgłosi błąd asercji.

pavani
źródło
1
Nie podaje żadnego prawdziwego przykładu, który jest celem pytania
rubenafo,
1
Czy właśnie skopiowałeś wklej z: amazon.com/Programmer-Study-1Z0-803-1Z0-804-Certification/dp/… ?
Koray Tugay