Cosa fa la parola chiave assert Java e quando deve essere usata?


602

Quali sono alcuni esempi di vita reale per comprendere il ruolo chiave delle asserzioni?


8
Nella vita reale non li vedi quasi mai. Congettura: se usi asserzioni devi pensare a tre stati: l'asserzione passa, l'asserzione fallisce, l'asserzione viene disattivata, invece che solo due. E assert è disattivato di default in modo che sia lo stato più probabile ed è difficile assicurarsi che sia abilitato per il tuo codice. Ciò che si aggiunge è che le asserzioni sono un'ottimizzazione prematura che sarebbe di utilità limitata. Come vedi nella risposta di @ Bjorn, è persino difficile trovare un caso d'uso in cui non vorresti fallire un'affermazione in ogni momento.
Yishai,

35
@Yishai: "devi pensare ... l'asserzione è disattivata" Se devi farlo, stai sbagliando. "Le asserzioni sono un'ottimizzazione prematura di un uso limitato" Questo è praticamente fuori dai binari. Ecco il punto di vista di Sun su questo: " Uso delle asserzioni nella tecnologia Java " e anche questo è buono da leggere: " I vantaggi della programmazione con asserzioni (aka assert statement) "
David Tonhofer,

5
@DavidTonhofer, nella vita reale non li vedi quasi mai. Questo è verificabile. Controlla tutti i progetti open source che desideri. Non sto dicendo che non convalidi gli invarianti. Non è la stessa cosa. Dirlo in un altro modo. Se le affermazioni sono così importanti, perché sono disattivate per impostazione predefinita?
Yishai,

17
Un riferimento, FWIW: La relazione tra asserzioni software e qualità del codice : "Confrontiamo anche l'efficacia delle asserzioni con quella delle popolari tecniche di ricerca dei bug come gli strumenti di analisi statica del codice sorgente. Osserviamo dal nostro caso di studio che con un aumento della densità delle asserzioni in un file c'è una riduzione statisticamente significativa della densità di errore ".
David Tonhofer,

4
@DavidTonhofer David, penso che il tuo amore per l'asserzione sia per un tipo molto specifico di programmazione che state facendo, nel mio campo che funziona con le applicazioni web che escono dal programma per QUALSIASI motivo è il più grande NO NO - personalmente non l'ho mai fatto usato asserire altro che test unit / integ
nightograph

Risposte:


426

Le asserzioni (tramite la parola chiave assert ) sono state aggiunte in Java 1.4. Sono utilizzati per verificare la correttezza di un invariante nel codice. Non devono mai essere attivati ​​nel codice di produzione e sono indicativi di un bug o di un uso improprio di un percorso del codice. Possono essere attivati ​​in fase di esecuzione tramite l' -eaopzione sul javacomando, ma non sono attivati ​​per impostazione predefinita.

Un esempio:

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;
}

71
In effetti, Oracle ti dice di non usare assertper controllare i parametri del metodo pubblico ( docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html ). Questo dovrebbe lanciare Exceptioninvece di uccidere il programma.
SJuan76,

10
Ma non spieghi ancora perché esistono. Perché non puoi fare un controllo if () e generare un'eccezione?
El Mac

7
@ElMac - le asserzioni sono per le parti dev / debug / test del ciclo - non sono per la produzione. Un blocco if viene eseguito in prod. Le asserzioni semplici non rovineranno la banca, ma asserzioni costose che rendono complessa la convalida dei dati potrebbero far crollare l'ambiente di produzione, motivo per cui vengono disattivate lì.
Hoodaticus,

2
@hoodaticus intendi solo il fatto che posso attivare / disattivare tutte le asserzioni per il codice prod? Perché posso comunque eseguire complesse convalide dei dati e quindi gestirle con eccezioni. Se ho un codice di produzione, potrei disattivare le asserzioni complesse (e forse costose), perché dovrebbe funzionare ed è già stato testato? In teoria non dovrebbero far cadere il programma perché allora avresti comunque un problema.
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

Supponiamo che tu debba scrivere un programma per controllare una centrale nucleare. È abbastanza ovvio che anche l'errore più piccolo potrebbe avere risultati catastrofici, quindi il tuo codice deve essere privo di bug (supponendo che JVM sia privo di bug per il bene dell'argomento).

Java non è un linguaggio verificabile, il che significa che non è possibile calcolare che il risultato dell'operazione sarà perfetto. Il motivo principale di ciò sono i puntatori: possono puntare ovunque o in nessun luogo, quindi non possono essere calcolati per avere questo valore esatto, almeno non entro un ragionevole intervallo di codice. Dato questo problema, non c'è modo di provare che il tuo codice sia nel complesso corretto. Ma quello che puoi fare è dimostrare che almeno trovi ogni bug quando succede.

Questa idea si basa sul paradigma Design-by-Contract (DbC): per prima cosa definisci (con precisione matematica) ciò che il tuo metodo dovrebbe fare, e poi lo verifichi testandolo durante l'esecuzione effettiva. Esempio:

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

Mentre questo è abbastanza ovvio per funzionare bene, la maggior parte dei programmatori non vedrà il bug nascosto all'interno di questo (suggerimento: Ariane V si è schiantato a causa di un bug simile). Ora DbC definisce che è sempre necessario controllare l'input e l'output di una funzione per verificarne il corretto funzionamento. Java può farlo attraverso affermazioni:

// 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;
}

Se questa funzione ora non dovesse funzionare, la noterai. Saprai che c'è un problema nel tuo codice, sai dove si trova e sai cosa lo ha causato (simile a Eccezioni). E ciò che è ancora più importante: smetti di eseguire correttamente quando succede per impedire a qualsiasi altro codice di lavorare con valori errati e potenzialmente causare danni a qualsiasi cosa controlli.

Le eccezioni Java sono un concetto simile, ma non riescono a verificare tutto. Se desideri ancora più controlli (a scapito della velocità di esecuzione) devi utilizzare le asserzioni. In questo modo si gonfia il codice, ma alla fine è possibile consegnare un prodotto in un tempo di sviluppo sorprendentemente breve (prima si corregge un bug, minore è il costo). E inoltre: se c'è qualche bug nel tuo codice, lo rileverai. Non è possibile che un bug scivoli e causi problemi in seguito.

Questo non è ancora una garanzia per il codice privo di bug, ma è molto più vicino a questo, rispetto ai normali programmi.


29
Ho scelto questo esempio perché presenta molto bene i bug nascosti in un codice apparentemente privo di bug. Se questo è simile a quello presentato da qualcun altro, allora forse avevano in mente la stessa idea. ;)
DueIl

8
Scegli asserisci perché fallisce quando l'asserzione è falsa. Un if può avere qualsiasi comportamento. Colpire i casi marginali è il compito del Test unitario. L'uso di Design by Contract ha specificato il contratto piuttosto bene, ma come per i contratti di vita reale, è necessario un controllo per assicurarsi che siano rispettati. Con le asserzioni viene inserito un cane da guardia che ti farà quindi mancare di rispetto al contratto. Pensalo come un avvocato fastidioso che urla "SBAGLIATO" ogni volta che fai qualcosa che è fuori o contro un contratto che hai firmato e poi ti rimanda a casa in modo da non poter continuare a lavorare e violare ulteriormente il contratto!
Eric

5
Necessario in questo semplice caso: no, ma il DbC definisce che ogni risultato deve essere verificato. Immagina che qualcuno ora modifichi quella funzione in qualcosa di molto più complesso, quindi deve adattare anche il post-check, e poi improvvisamente diventa utile.
Due Il

4
Mi dispiace risorgere, ma ho una domanda specifica. Qual è la differenza tra ciò che ha fatto @TwoThe e invece di usare assert semplicemente lanciando un new IllegalArgumentExceptionmessaggio? Voglio dire, oltre ad avere o aggiungere throwsalla dichiarazione del metodo e al codice per gestire quell'eccezione da qualche altra parte. Perché assertinsetad di lanciare una nuova eccezione? O perché non un ifinvece di assert? Non riesco davvero a ottenere questo :(
Blueriver

14
-1: l'affermazione per verificare l'overflow è errata se apuò essere negativa. La seconda affermazione è inutile; per i valori int, è sempre il caso che a + b - b == a. Quel test può fallire solo se il computer è sostanzialmente rotto. Per difendersi da tale imprevisto, è necessario verificare la coerenza tra più CPU.
Kevin Cline,

63

Le asserzioni sono uno strumento in fase di sviluppo per rilevare i bug nel codice. Sono progettati per essere facilmente rimossi, quindi non esistono nel codice di produzione. Quindi le asserzioni non fanno parte della "soluzione" che offri al cliente. Sono controlli interni per assicurarsi che le ipotesi che stai facendo siano corrette. L'esempio più comune è testare null. Molti metodi sono scritti in questo modo:

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

Molto spesso in un metodo come questo, semplicemente il widget non dovrebbe mai essere nullo. Quindi, se è nullo, c'è un bug nel tuo codice da qualche parte che devi rintracciare. Ma il codice sopra non ti dirà mai questo. Quindi, nel tentativo ben intenzionato di scrivere un codice "sicuro", nascondi anche un bug. È molto meglio scrivere codice in questo modo:

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

In questo modo, sarai sicuro di catturare questo bug in anticipo. (È anche utile specificare nel contratto che questo parametro non deve mai essere nullo.) Accertarsi di attivare le asserzioni quando si verifica il codice durante lo sviluppo. (E persuadere i tuoi colleghi a farlo anche questo è spesso difficile, che trovo molto fastidioso.)

Ora, alcuni dei tuoi colleghi si opporranno a questo codice, sostenendo che dovresti comunque effettuare il controllo null per evitare un'eccezione nella produzione. In tal caso, l'affermazione è ancora utile. Puoi scriverlo in questo modo:

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

In questo modo, i tuoi colleghi saranno felici che il controllo null sia disponibile per il codice di produzione, ma durante lo sviluppo, non stai più nascondendo il bug quando il widget è null.

Ecco un esempio del mondo reale: una volta ho scritto un metodo che confrontava due valori arbitrari per l'uguaglianza, in cui entrambi i valori potevano essere nulli:

/**
 * 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;
}

Questo codice delega il lavoro del equals()metodo nel caso in cui thisValue non sia nullo. Ma assume ilequals() metodo soddisfi correttamente il contratto equals()gestendo correttamente un parametro null.

Un collega ha obiettato al mio codice, dicendomi che molte delle nostre classi hanno equals()metodi errati che non testano per null, quindi dovrei mettere quel controllo in questo metodo. È discutibile se questo è saggio, o se dovremmo forzare l'errore, quindi possiamo individuarlo e risolverlo, ma ho rinviato al mio collega e messo un controllo nullo, che ho contrassegnato con un commento:

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;
}

Il controllo aggiuntivo qui, other != null è necessario solo se il equals()metodo non riesce a verificare la null come richiesto dal suo contratto.

Piuttosto che impegnarmi in un fruttuoso dibattito con il mio collega sulla saggezza di lasciare che il codice errato rimanga nella nostra base di codice, ho semplicemente inserito due affermazioni nel codice. Queste affermazioni mi faranno sapere, durante la fase di sviluppo, se una delle nostre classi non riesce a implementarsi equals()correttamente, quindi posso risolverlo:

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;
}

I punti importanti da tenere a mente sono questi:

  1. Le asserzioni sono solo strumenti in fase di sviluppo.

  2. Il punto di un'affermazione è farti sapere se c'è un bug, non solo nel tuo codice, ma nella tua base di codice . (Le asserzioni qui in realtà contrassegneranno i bug in altre classi.)

  3. Anche se il mio collega fosse sicuro che le nostre lezioni fossero state scritte correttamente, le affermazioni qui sarebbero comunque utili. Verranno aggiunte nuove classi che potrebbero non essere verificate per null e questo metodo può contrassegnare tali bug per noi.

  4. Nello sviluppo, dovresti sempre attivare le asserzioni, anche se il codice che hai scritto non usa asserzioni. Il mio IDE è impostato per farlo sempre per impostazione predefinita per qualsiasi nuovo eseguibile.

  5. Le asserzioni non cambiano il comportamento del codice in produzione, quindi il mio collega è contento che il controllo null sia presente e che questo metodo verrà eseguito correttamente anche se il equals()metodo è difettoso. Sono felice perché prenderò qualsiasi equals()metodo con errori durante lo sviluppo.

Inoltre, dovresti testare la tua politica di asserzione inserendo un'asserzione temporanea che fallirà, quindi puoi essere certo di essere avvisato, sia attraverso il file di registro o una traccia dello stack nel flusso di output.


Aspetti positivi su "nascondere un bug" e su come le affermazioni espongono i bug durante lo sviluppo!
nobar,

Nessuno di questi controlli è lento, quindi non c'è motivo di spegnerli in produzione. Dovrebbero essere convertiti in istruzioni di registrazione, in modo da poter rilevare problemi che non si presentano nella "fase di sviluppo". (In realtà, comunque, non esiste una fase di sviluppo. Lo sviluppo termina quando decidi di interrompere il mantenimento del codice.)
Aleksandr Dubinsky,

20

Molte buone risposte spiegano cosa fa la assertparola chiave, ma pochi rispondono alla vera domanda "quando dovrebbeassert essere usata parola chiave nella vita reale?"

La risposta: quasi mai .

Le asserzioni, come concetto, sono meravigliose. Il buon codice ha molte if (...) throw ...affermazioni (e ai loro parenti piace Objects.requireNonNulle Math.addExact). Tuttavia, alcune decisioni di progettazione hanno notevolmente limitato l'utilità della assert parola chiave stessa.

L'idea alla base della assertparola chiave è l'ottimizzazione prematura e la caratteristica principale è quella di poter facilmente disattivare tutti i controlli. In effetti, ilassert controlli sono disattivati ​​per impostazione predefinita.

Tuttavia, è di fondamentale importanza che i controlli invarianti continuino a essere effettuati in produzione. Questo perché una perfetta copertura dei test è impossibile e tutto il codice di produzione avrà bug che le asserzioni dovrebbero aiutare a diagnosticare e mitigare.

Pertanto, l'uso di if (...) throw ...dovrebbe essere preferito, così come è richiesto per il controllo dei valori dei parametri dei metodi pubblici e per il lancio IllegalArgumentException.

Occasionalmente, si potrebbe essere tentati di scrivere un controllo invariante che richiede un tempo indesiderabilmente lungo per l'elaborazione (e viene chiamato abbastanza spesso perché contenga). Tuttavia, tali controlli rallenteranno i test, il che è anche indesiderabile. Tali controlli che richiedono molto tempo sono generalmente scritti come test unitari. Tuttavia, a volte può avere senso utilizzare assertper questo motivo.

Non usare assertsemplicemente perché è più pulito e più bello di if (...) throw ...(e lo dico con grande dolore, perché mi piace pulito e carino). Se semplicemente non puoi aiutarti e puoi controllare come viene avviata la tua applicazione, sentiti libero di usare assertma abilita sempre le asserzioni in produzione. Certo, questo è quello che tendo a fare. Sto spingendo per un'annotazione lombok che farà comportare assertdi più if (...) throw .... Vota qui.

(Rant: gli sviluppatori JVM erano un sacco di programmatori terribili e prematuramente ottimizzati. Ecco perché si sentono così tanti problemi di sicurezza nel plugin Java e JVM. Si sono rifiutati di includere controlli di base e asserzioni nel codice di produzione, e stiamo continuando a paga il prezzo.)


2
@aberglas Una clausola generale è catch (Throwable t). Non vi è alcun motivo per non tentare di intercettare, registrare o riprovare / ripristinare da OutOfMemoryError, AssertionError, ecc.
Aleksandr Dubinsky

1
Ho catturato e recuperato da OutOfMemoryError.
MiguelMunoz,

1
Non sono d'accordo Molte delle mie asserzioni vengono utilizzate per assicurarsi che la mia API venga chiamata correttamente. Ad esempio, potrei scrivere un metodo privato che dovrebbe essere chiamato solo quando un oggetto contiene un lucchetto. Se un altro sviluppatore chiama quel metodo da parte del codice che non blocca l'oggetto, l'asserzione dirà subito che hanno fatto un errore. Ci sono molti errori come questo che, con certezza, possono rimanere intrappolati nella fase di sviluppo e le affermazioni sono molto utili in questi casi.
MiguelMunoz,

2
@MiguelMunoz Nella mia risposta ho detto che l'idea delle asserzioni è molto buona. È l'implementazione della assertparola chiave è male. Modificherò la mia risposta per chiarire che mi riferisco alla parola chiave, non al concetto.
Aleksandr Dubinsky,

2
Mi piace il fatto che genera un errore Assertion anziché un'eccezione. Troppi sviluppatori non hanno ancora imparato che non dovrebbero intercettare Eccezione se il codice può generare qualcosa come IOException. Ho avuto errori nel mio codice completamente inghiottiti perché qualcuno ha rilevato l'eccezione. Le asserzioni non vengono catturate da questa trappola. Le eccezioni sono per le situazioni che ti aspetti di vedere nel codice di produzione. Per quanto riguarda la registrazione, dovresti registrare anche tutti i tuoi errori, anche se gli errori sono rari. Ad esempio, vuoi davvero lasciare passare un OutOfMemoryError senza registrarlo?
MiguelMunoz,

14

Ecco il caso d'uso più comune. Supponiamo di attivare un valore enum:

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

Finché gestisci ogni caso, stai bene. Ma un giorno qualcuno aggiungerà fico al tuo enum e dimenticherà di aggiungerlo alla tua dichiarazione switch. Questo produce un bug che può essere difficile da rilevare, perché gli effetti non saranno avvertiti fino a quando non avrai lasciato l'istruzione switch. Ma se scrivi l'interruttore in questo modo, puoi prenderlo immediatamente:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}

4
Ecco perché dovresti avere gli avvisi abilitati e gli avvisi trattati come errori. Qualsiasi compilatore a metà decente è in grado di dirti, se solo glielo permetti, che ti manca un controllo enum, e lo farà in fase di compilazione, che è indicibilmente migliore di (forse, un giorno) scoprendo a tempo di esecuzione.
Mike Nakis,

9
perché usare qui un'asserzione piuttosto che un'eccezione di qualche tipo, ad esempio un'argomentazione illegale?
liltitus27,

4
Questo genererà un AssertionErrorerrore se le asserzioni sono abilitate ( -ea). Qual è il comportamento desiderato in produzione? Un silenzioso no-op e un potenziale disastro più tardi nell'esecuzione? Probabilmente no. Vorrei suggerire un esplicito throw new AssertionError("Missing enum value: " + fruit);.
aioobe,

1
C'è una buona argomentazione da fare semplicemente per lanciare un AssertionError. Per quanto riguarda il comportamento corretto in produzione, il punto centrale delle affermazioni è quello di impedire che ciò accada in produzione. Le asserzioni sono uno strumento della fase di sviluppo per rilevare i bug, che possono essere facilmente rimossi dal codice di produzione. In questo caso, non c'è motivo di rimuoverlo dal codice di produzione. Ma in molti casi, i test di integrità possono rallentare le cose. Inserendo questi test all'interno di asserzioni, che non sono utilizzate nel codice di produzione, sei libero di scrivere test approfonditi, senza preoccuparti che rallenteranno il tuo codice di produzione.
MiguelMunoz,

Questo sembra essere sbagliato. IMHO non dovresti usarlo in defaultmodo che il compilatore possa avvisarti di casi mancanti. È possibile returninvece break(potrebbe essere necessario estrarre il metodo) e quindi gestire il caso mancante dopo il passaggio. In questo modo ottieni sia l'avvertimento che l'opportunità di farlo assert.
maaartino

12

Le asserzioni vengono utilizzate per controllare le post-condizioni e le pre-condizioni "non dovrebbero mai fallire". Il codice corretto non dovrebbe mai fallire un'asserzione; quando si innescano, dovrebbero indicare un bug (si spera in un posto vicino a dove si trova il luogo reale del problema).

Un esempio di un'asserzione potrebbe essere quello di verificare che un particolare gruppo di metodi sia chiamato nell'ordine giusto (ad esempio, che hasNext()è chiamato prima next()in un Iterator).


1
Non è necessario chiamare hasNext () prima di next ().
DJClayworth,

6
@DJClayworth: non è nemmeno necessario evitare l'attivazione di asserzioni. :-)
Donal Fellows

8

Cosa fa la parola chiave assert in Java?

Diamo un'occhiata al bytecode compilato.

Concluderemo che:

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

genera quasi lo stesso bytecode esattamente come:

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();
            }
        }
    }
}

dove Assert.class.desiredAssertionStatus()è truequando-ea viene passato dalla riga di comando e false altrimenti.

Ci System.currentTimeMillis()assicuriamo che non venga ottimizzato (assert true; fatto).

Il campo sintetico viene generato in modo che Java debba solo chiamare Assert.class.desiredAssertionStatus()una volta al momento del caricamento e quindi memorizza nella cache il risultato. Guarda anche: Qual è il significato di "sintetico statico"?

Possiamo verificarlo con:

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

Con Oracle JDK 1.8.0_45, è stato generato un campo statico sintetico (vedi anche: Qual è il significato di "sintetico statico"? ):

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

insieme a un inizializzatore statico:

 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

e il metodo principale è:

 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

Concludiamo che:

  • non esiste un supporto a livello di bytecode per assert : è un concetto di linguaggio Java
  • assertpotrebbe essere emulato abbastanza bene con le proprietà di sistema -Pcom.me.assert=trueda sostituire -easulla riga di comando e a throw new AssertionError().

2
Quindi la catch (Throwable t)clausola è in grado di rilevare anche le violazioni delle asserzioni? Per me ciò limita la loro utilità solo nel caso in cui il corpo dell'asserzione richieda tempo, il che è raro.
Evgeni Sergeev,

1
Non sono sicuro del motivo per cui limita l'utilità dell'asserzione. Non dovresti mai prendere un lanciatore tranne in casi molto rari. Se devi catturare Throwable ma vuoi che non catturi le asserzioni, puoi semplicemente catturare il AssertionErrorprimo e ripeterlo.
MiguelMunoz,

7

Un esempio del mondo reale, da una classe Stack (da Asserzione in articoli Java )

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}

80
Questo sarebbe disapprovato in C: un'affermazione è qualcosa che NON VERAMENTE MAI dovrebbe accadere - fare scoppiare una pila vuota dovrebbe lanciare una NoElementsException o qualcosa del genere. Vedi la risposta di Donal.
Konerak,

4
Sono d'accordo. Anche se questo è tratto da un tutorial ufficiale, è un cattivo esempio.
DJClayworth,


7
Probabilmente c'è una perdita di memoria lì. Dovresti impostare stack [num] = null; affinché il GC faccia correttamente il suo lavoro.
H.Rabiee,

3
Penso che in un metodo privato sarebbe corretto usare un'asserzione, dato che sarebbe strano avere eccezioni per un malfunzionamento di una classe o di un metodo. In un metodo pubblico, chiamandolo da qualche parte all'esterno, non puoi davvero dire come l'altro codice lo utilizza. Controlla davvero isEmpty () o no? Non lo sai.
Vlasec,

7

Un'asserzione consente di rilevare difetti nel codice. È possibile attivare le asserzioni per i test e il debug lasciandole fuori quando il programma è in produzione.

Perché affermare qualcosa quando sai che è vero? È vero solo quando tutto funziona correttamente. Se il programma presenta un difetto, potrebbe non essere vero. Rilevarlo prima nel processo ti fa sapere che qualcosa non va.

Un'istruzione assertcontiene questa istruzione insieme a un Stringmessaggio opzionale .

La sintassi per un'istruzione assert ha due forme:

assert boolean_expression;
assert boolean_expression: error_message;

Ecco alcune regole di base che regolano dove dovrebbero essere usate le asserzioni e dove non dovrebbero essere usate. Le asserzioni dovrebbero essere utilizzate per:

  1. Convalida dei parametri di input di un metodo privato. NON per metodi pubblici. publici metodi dovrebbero generare eccezioni regolari quando vengono passati parametri errati.

  2. Ovunque nel programma per garantire la validità di un fatto che è quasi certamente vero.

Ad esempio, se sei sicuro che sarà solo 1 o 2, puoi usare un'asserzione come questa:

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...
  1. Convalida delle condizioni postali alla fine di qualsiasi metodo. Ciò significa che, dopo aver eseguito la logica aziendale, è possibile utilizzare le asserzioni per garantire che lo stato interno delle variabili o dei risultati sia coerente con ciò che ci si aspetta. Ad esempio, un metodo che apre un socket o un file può utilizzare un'asserzione alla fine per garantire che il socket o il file sia effettivamente aperto.

Le asserzioni non devono essere utilizzate per:

  1. Convalida dei parametri di input di un metodo pubblico. Poiché le asserzioni potrebbero non essere sempre eseguite, dovrebbe essere utilizzato il normale meccanismo di eccezione.

  2. Convalida dei vincoli su qualcosa che viene inserito dall'utente. Come sopra.

  3. Non dovrebbe essere usato per effetti collaterali.

Ad esempio, questo non è un uso corretto perché qui l'asserzione viene utilizzata per il suo effetto collaterale della chiamata del doSomething()metodo.

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

L'unico caso in cui ciò potrebbe essere giustificato è quando stai cercando di scoprire se le asserzioni sono abilitate o meno nel tuo codice:   

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}

5

Oltre a tutte le ottime risposte fornite qui, la guida ufficiale alla programmazione Java SE 7 ha un manuale piuttosto conciso sull'uso assert; con diversi esempi chiari di quando è una buona (e, soprattutto, cattiva) idea usare asserzioni e come è diverso dal lanciare eccezioni.

collegamento


1
Sono d'accordo. L'articolo contiene molti esempi eccellenti. Mi è particolarmente piaciuto quello di assicurarmi che un metodo venga chiamato solo quando l'oggetto contiene un lucchetto.
MiguelMunoz,

4

Assert è molto utile durante lo sviluppo. Lo usi quando qualcosa semplicemente non può succedere se il tuo codice funziona correttamente. È facile da usare e può rimanere nel codice per sempre, perché verrà disattivato nella vita reale.

Se c'è qualche possibilità che la condizione possa verificarsi nella vita reale, allora devi gestirla.

Lo adoro, ma non so come accenderlo in Eclipse / Android / ADT. Sembra essere spento anche durante il debug. (C'è un thread su questo, ma si riferisce a 'Java vm', che non appare nella configurazione di esecuzione ADT).


1
Per abilitare l'asserzione nell'IDE di Eclipse, seguire tutoringcenter.cs.usfca.edu/resources/…
Ayaz Pasha,

Non credo che ci sia un modo per attivare assert in Android. Questo è molto deludente.
MiguelMunoz,

3

Ecco un'asserzione che ho scritto su un server per un progetto Hibernate / SQL. Un bean di entità aveva due proprietà effettivamente booleane, chiamate isActive e isDefault. Ciascuno potrebbe avere un valore di "Y" o "N" o null, che è stato trattato come "N". Vogliamo assicurarci che il client del browser sia limitato a questi tre valori. Quindi, nei miei setter per queste due proprietà, ho aggiunto questa affermazione:

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

Si noti quanto segue.

  1. Questa affermazione è solo per la fase di sviluppo. Se il cliente invia un valore negativo, lo prenderemo presto e lo ripareremo, molto prima di raggiungere la produzione. Le affermazioni riguardano difetti che è possibile rilevare in anticipo.

  2. Questa affermazione è lenta e inefficiente. Va bene. Le affermazioni sono libere di essere lente. Non ci interessa perché sono solo strumenti di sviluppo. Ciò non rallenterà il codice di produzione perché le asserzioni saranno disabilitate. (C'è un certo disaccordo su questo punto, che vedrò più avanti.) Questo porta al mio prossimo punto.

  3. Questa affermazione non ha effetti collaterali. Avrei potuto testare il mio valore contro un Set finale statico non modificabile, ma quel set sarebbe rimasto in produzione, dove non sarebbe mai stato usato.

  4. Questa affermazione esiste per verificare il corretto funzionamento del client. Quindi, quando avremo raggiunto la produzione, saremo sicuri che il cliente funzioni correttamente, in modo da poter disattivare in modo sicuro l'affermazione.

  5. Alcune persone lo chiedono: se l'asserzione non è necessaria nella produzione, perché non toglierli quando hai finito? Perché ne avrai ancora bisogno quando inizi a lavorare alla versione successiva.

Alcune persone hanno sostenuto che non dovresti mai usare asserzioni, perché non puoi mai essere sicuro che tutti i bug siano spariti, quindi devi tenerli in giro anche durante la produzione. E quindi non ha senso usare l'istruzione assert, poiché l'unico vantaggio da asserire è che puoi disattivarli. Quindi, secondo questo pensiero, non dovresti (quasi) mai usare assert. Non sono d'accordo. È certamente vero che se un test appartiene alla produzione, non dovresti usare un'asserzione. Ma questo test no appartiene alla produzione. Questo è per catturare un bug che probabilmente non raggiungerà mai la produzione, quindi potrebbe essere tranquillamente spento quando hai finito.

A proposito, avrei potuto scriverlo in questo modo:

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

Questo vale solo per tre valori, ma se il numero di valori possibili aumenta, la versione di HashSet diventa più conveniente. Ho scelto la versione di HashSet per esprimere il mio punto di vista sull'efficienza.


Dubito fortemente che l'uso di una così piccola a HashSetporta un vantaggio di velocità rispetto a una ArrayList. Inoltre, le creazioni di set ed elenchi dominano il tempo di ricerca. Stanno bene quando usano una costante. Detto questo, +1.
maaartinus,

Tutto vero. L'ho fatto in modo inefficiente per illustrare il mio punto che le affermazioni sono libere di essere lente. Questo potrebbe essere reso più efficiente, ma ce ne sono altri che non possono. In un eccellente libro intitolato "Scrivere codice solido", Steve Maguire racconta un'asserzione in Microsoft Excel per testare il nuovo codice di aggiornamento incrementale che saltava le celle che non dovevano cambiare. Ogni volta che l'utente ha apportato una modifica, l'asserzione ricalcola l'intero foglio di calcolo per assicurarsi che i risultati corrispondano a quelli della funzione di aggiornamento incrementale. Ha davvero rallentato la versione di debug, ma hanno rilevato tutti i loro bug in anticipo.
MiguelMunoz,

Completamente concordato Le asserzioni sono una sorta di test: sono meno versatili dei test ordinari, ma possono coprire metodi privati ​​e sono molto più economici da scrivere. Proverò ad usarli ancora di più.
maaartinus,

2

Le asserzioni sono fondamentalmente utilizzate per eseguire il debug dell'applicazione o vengono utilizzate in sostituzione della gestione delle eccezioni per alcune applicazioni per verificare la validità di un'applicazione.

L'asserzione funziona in fase di esecuzione. Un semplice esempio, che può spiegare l'intero concetto in modo molto semplice, è qui: cosa fa la parola chiave assert in Java? (WikiAnswers).


2

Le asserzioni sono disabilitate per impostazione predefinita. Per abilitarli dobbiamo eseguire il programma con -eaopzioni (la granularità può essere variata). Ad esempio java -ea AssertionsDemo,.

Esistono due formati per l'utilizzo delle asserzioni:

  1. Semplice: ad es. assert 1==2; // This will raise an AssertionError.
  2. Migliore: assert 1==2: "no way.. 1 is not equal to 2"; questo genererà un errore di asserzione con anche il messaggio dato visualizzato ed è quindi migliore. Sebbene la sintassi effettiva sia assert expr1:expr2dove expr2 può essere qualsiasi espressione che restituisce un valore, l'ho usata più spesso solo per stampare un messaggio.

1

Ricapitolando (e questo vale per molte lingue, non solo per Java):

"assert" viene utilizzato principalmente come aiuto per il debug dagli sviluppatori di software durante il processo di debug. I messaggi di assert non dovrebbero mai apparire. Molte lingue forniscono un'opzione di compilazione che farà ignorare tutti gli "assert", per usarli nella generazione del codice di "produzione".

le "eccezioni" sono un modo pratico per gestire tutti i tipi di condizioni di errore, indipendentemente dal fatto che rappresentino o meno errori logici, perché, se ti imbatti in una condizione di errore tale da non poter continuare, puoi semplicemente "gettarli in aria, "ovunque tu sia, aspettandoti che qualcun altro sia pronto a" catturarli ". Il controllo viene trasferito in un passaggio, direttamente dal codice che ha generato l'eccezione, direttamente al mitt del ricevitore. (E il ricevitore può vedere il backtrace completo delle chiamate che hanno avuto luogo.)

Inoltre, i chiamanti di quella subroutine non devono controllare per vedere se la subroutine è riuscita: "se siamo qui adesso, deve esserci riuscita, perché altrimenti avrebbe gettato un'eccezione e non saremmo qui adesso!" Questa semplice strategia rende molto più semplice la progettazione del codice e il debug.

Le eccezioni consentono convenientemente che le condizioni di errore fatale siano quelle che sono: "eccezioni alla regola". E, per loro, devono essere gestiti da un percorso di codice che è anche "un'eccezione alla regola ... " fly ball! "


1

Le asserzioni sono assegni che possono essere disattivati. Sono usati raramente. Perché?

  • Non devono essere utilizzati per controllare gli argomenti del metodo pubblico poiché non si ha alcun controllo su di essi.
  • Non dovrebbero essere usati per controlli semplici result != nullcome questi controlli sono molto veloci e non c'è quasi nulla da salvare.

Quindi, cosa rimane? Controlli costosi per le condizioni che dovrebbero essere vere. Un buon esempio sarebbero gli invarianti di una struttura di dati come RB-tree. In realtà, in ConcurrentHashMapJDK8, ci sono alcune affermazioni così significative per il TreeNodes.

  • Non vuoi davvero accenderli in produzione in quanto potrebbero facilmente dominare il tempo di esecuzione.
  • Si consiglia di accenderli o spegnerli durante i test.
  • Vuoi sicuramente accenderli quando lavori sul codice.

A volte, l'assegno non è molto costoso, ma allo stesso tempo, sei abbastanza sicuro, passerà. Nel mio codice, ad esempio,

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

dove sono abbastanza sicuro che l'elenco che ho appena creato abbia elementi unici, ma volevo documentarlo e ricontrollarlo.


0

Fondamentalmente, passerà "asserire vero" e "asserire falso" fallirà. Vediamo come funzionerà:

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;
    }
}

-8

assertè una parola chiave. È stato introdotto in JDK 1.4. Sono due tipi di asserts

  1. assertDichiarazioni molto semplici
  2. assertDichiarazioni semplici .

Per impostazione predefinita, tutte le assertistruzioni non verranno eseguite. Se assertun'istruzione riceve false, genererà automaticamente un errore di asserzione.


1
Non fornisce alcun esempio di vita reale, che è lo scopo della domanda
rubenafo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.