Quando dovrei usare Debug.Assert ()?


220

Sono stato un ingegnere informatico professionista per circa un anno, dopo essermi laureato in CS. Conosco da tempo le asserzioni in C ++ e C, ma non avevo idea che esistessero in C # e .NET fino a poco tempo fa.

Il nostro codice di produzione non contiene affermazioni di sorta e la mia domanda è questa ...

Dovrei iniziare a utilizzare Assert nel nostro codice di produzione? E se è così, quando è il suo uso più appropriato? Avrebbe più senso farlo

Debug.Assert(val != null);

o

if ( val == null )
    throw new exception();

2
La dicotomia che hai impostato è la chiave. Non si tratta di una o di eccezioni e affermazioni, sia di codice difensivo che di entrambi. Quando fare qual è ciò che stai cercando di capire.
Casper Leon Nielsen,

5
Una volta ho letto qualcuno suggerire che un'eccezione o un altro metodo di crash è appropriato per le condizioni in cui "Non c'è modo che io possa riprendermi sensibilmente da questo", e inoltre un'affermazione è appropriata per le condizioni in cui "Questo non dovrebbe mai accadere, mai". Ma quali circostanze realistiche soddisfano le ultime condizioni senza soddisfare anche la prima? Proveniente da un background Python in cui le asserzioni rimangono attive in produzione, non ho mai capito l'approccio Java / C # di disattivare parte della tua validazione in produzione. L'unico caso che posso vedere è se la convalida è costosa.
Mark Amery,


2
Personalmente utilizzo eccezioni per metodi pubblici e affermazioni per metodi privati.
Fred

Risposte:


230

Nel debug delle applicazioni Microsoft .NET 2.0 John Robbins ha una grande sezione sulle asserzioni. I suoi punti principali sono:

  1. Asserire liberamente. Non puoi mai avere troppe asserzioni.
  2. Le asserzioni non sostituiscono le eccezioni. Le eccezioni riguardano le cose richieste dal codice; le affermazioni coprono le cose che assume.
  3. Un'affermazione ben scritta può dirti non solo cosa è successo e dove (come un'eccezione), ma perché.
  4. Un messaggio di eccezione può spesso essere criptico, richiedendo di lavorare indietro attraverso il codice per ricreare il contesto che ha causato l'errore. Un'asserzione può preservare lo stato del programma nel momento in cui si è verificato l'errore.
  5. Le asserzioni raddoppiano come documentazione, dicendo agli altri sviluppatori da quali ipotesi implicite dipende il codice.
  6. La finestra di dialogo che appare quando un'asserzione fallisce ti consente di collegare un debugger al processo, in modo da poter scorrere lo stack come se avessi messo un punto di interruzione lì.

PS: Se ti è piaciuto Code Complete, ti consiglio di seguirlo con questo libro. L'ho comprato per sapere come usare WinDBG e file di dump, ma la prima metà è piena di suggerimenti per aiutare a evitare bug in primo luogo.


3
+1 per il riassunto conciso e utile. Molto direttamente applicabile. La cosa principale che mi manca, però, è quando usare Trace.Assert vs. Trace.Assert. Vale a dire qualcosa su quando li vuoi / non li vuoi nel tuo codice di produzione.
Jon Coombs,

2
JonCoombs è "Trace.Assert vs. Trace.Assert" un errore di battitura?
entro il

1
@thelem Forse Jon voleva dire Debug.Assertcontro Trace.Assert. Quest'ultimo viene eseguito in una build Release e in una build Debug.
DavidRR,

Perché dovrei preferire Debug.Assert all'eccezione?
Barış Akkurt,

86

Inserisci Debug.Assert()ovunque nel codice dove vuoi avere controlli di integrità per garantire gli invarianti. Quando compili una build di rilascio (ovvero nessuna DEBUGcostante del compilatore), le chiamate a Debug.Assert()verranno rimosse in modo da non influire sulle prestazioni.

Dovresti comunque generare eccezioni prima di chiamare Debug.Assert(). L'asserzione si assicura solo che tutto sia come previsto mentre si sta ancora sviluppando.


35
Potresti chiarire perché inserire un'asserzione se lanci ancora un'eccezione prima di chiamarla? O ho frainteso la tua risposta?
Roman Starkov,

2
@romkyns Devi comunque includerli perché se non lo fai, quando costruisci il tuo progetto in modalità Release , tutte le validazioni / il controllo degli errori spariranno.
Oscar Mederos

28
@Oscar Ho pensato che fosse proprio il punto di usare le asserzioni in primo luogo ... OK allora, quindi metti le eccezioni davanti a loro - allora perché le asserzioni dopo?
Roman Starkov,

4
@superjos: Non sono d'accordo: il punto n. 2 nella risposta di MacLeod afferma che in effetti hai bisogno di affermazioni ed eccezioni, ma non nello stesso posto. È inutile lanciare un NullRefEx su una variabile e subito dopo fare un Assert su di esso (il metodo assert non mostrerà mai un dialogBox in questo caso, che è l'intero punto di assert). Ciò che MacLeod significa è che in alcuni casi avrai bisogno di un'eccezione, in altri sarà sufficiente un Assert.
David

1
Può diventare disordinato di interpretare la mia interpretazione di qualcun altro risposta :) Comunque io con voi su questi: è necessario entrambi, e si dovrebbe non mettere l'eccezione prima che l'asserzione. Non sono sicuro del significato di "non nello stesso posto". Ancora una volta, rifiutando di interpretare, dichiarerò solo i miei pensieri / le mie preferenze: metto una o più affermazioni per verificare le condizioni preliminari prima che inizi qualche operazione, o per controllare le condizioni successive dopo l'operazione. Oltre alle affermazioni, e dopo di esse, controlla se qualcosa va storto e deve generare eccezioni.
Superjos

52

Dal codice completo

8 Programmazione difensiva

8.2 Asserzioni

Un'asserzione è il codice utilizzato durante lo sviluppo, in genere una routine o una macro, che consente a un programma di controllarsi mentre viene eseguito. Quando un'asserzione è vera, significa che tutto funziona come previsto. Quando è falso, significa che ha rilevato un errore imprevisto nel codice. Ad esempio, se il sistema presume che un file di informazioni sul cliente non avrà mai più di 50.000 record, il programma potrebbe contenere un'asserzione che il numero di record è inferiore o uguale a 50.000. Finché il numero di record è inferiore o uguale a 50.000, l'affermazione rimarrà silenziosa. Se incontra più di 50.000 record, tuttavia, "affermerà" ad alta voce che c'è un errore nel programma.

Le asserzioni sono particolarmente utili in programmi complessi e di grandi dimensioni e in programmi ad alta affidabilità. Consentono ai programmatori di eliminare più rapidamente i presupposti dell'interfaccia non corrispondenti, gli errori che si insinuano quando il codice viene modificato e così via.

Un'asserzione di solito prende due argomenti: un'espressione booleana che descrive il presupposto che dovrebbe essere vero e un messaggio da visualizzare se non lo è.

(...)

Normalmente, non si desidera che gli utenti vedano i messaggi di asserzione nel codice di produzione; le affermazioni servono principalmente durante lo sviluppo e la manutenzione. Le asserzioni vengono normalmente compilate nel codice in fase di sviluppo e compilate fuori dal codice per la produzione. Durante lo sviluppo, le asserzioni eliminano ipotesi contraddittorie, condizioni impreviste, valori errati passati alle routine e così via. Durante la produzione, vengono compilati fuori dal codice in modo che le asserzioni non degradino le prestazioni del sistema.


7
Quindi, cosa succede se un file di informazioni sui clienti riscontrato in produzione contiene più di 50.000 record? Se l'asserzione viene compilata dal codice di produzione e questa situazione non viene gestita diversamente, questo incantesimo non crea problemi?
DavidRR,

1
@DavidRR Sì davvero. Ma non appena la produzione segnala un problema e alcuni sviluppatori (che potrebbero non conoscere bene questo codice) eseguono il debug del problema, l'asserzione fallirà e lo sviluppatore saprà immediatamente che il sistema non viene utilizzato come previsto.
Marc,

48

FWIW ... Trovo che i miei metodi pubblici tendano a utilizzare il if () { throw; }modello per garantire che il metodo venga chiamato correttamente. I miei metodi privati ​​tendono ad usare Debug.Assert().

L'idea è che con i miei metodi privati, sono quello sotto controllo, quindi se comincio a chiamare uno dei miei metodi privati ​​con parametri errati, allora ho infranto il mio presupposto da qualche parte - non avrei mai avuto in quello stato. Nella produzione, queste affermazioni private dovrebbero idealmente essere un lavoro inutile poiché dovrei mantenere il mio stato interno valido e coerente. Contrasto con i parametri dati ai metodi pubblici, che potrebbero essere chiamati da chiunque in fase di esecuzione: ho ancora bisogno di imporre i vincoli dei parametri lì generando eccezioni.

Inoltre, i miei metodi privati ​​possono comunque generare eccezioni se qualcosa non funziona in fase di esecuzione (errore di rete, errore di accesso ai dati, dati errati recuperati da un servizio di terze parti, ecc.). Le mie affermazioni sono lì solo per assicurarsi di non aver infranto le mie assunzioni interne sullo stato dell'oggetto.


3
Questa è una descrizione molto chiara di una buona pratica e fornisce una risposta molto ragionevole alla domanda posta.
Casper Leon Nielsen,

42

Utilizzare assert per verificare i presupposti e le eccezioni degli sviluppatori per verificare i presupposti ambientali.


31

Se fossi in te farei:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

O per evitare il controllo ripetuto delle condizioni

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}

5
In che modo questo risolve il problema? Con questo il debug.assert diventa inutile.
Quibblesome

43
No, non lo fa: si interrompe nel codice appena prima che venga generata l'eccezione. Se provi / catturi altrove nel tuo codice, potresti non notare nemmeno l'eccezione!
Mark Ingram,

2
+1 Ho avuto molti problemi in cui le persone avrebbero semplicemente provato / catturato le eccezioni senza fare nulla, quindi il tracking bug era un problema
dance2die

5
Suppongo ci siano casi in cui potresti voler fare questo, ma non dovresti mai prendere un'eccezione generale!
Casebash,

8
@MarkIngram -1 alla tua risposta e +1 al tuo commento che lo giustifica. Questo è un bel trucco per certe circostanze particolari, ma sembra una cosa bizzarra da fare in generale per tutta la convalida.
Mark Amery,

24

Se vuoi Assert nel tuo codice di produzione (es. Build di rilascio), puoi usare Trace.Assert invece di Debug.Assert.

Questo ovviamente aggiunge sovraccarico all'eseguibile di produzione.

Inoltre, se l'applicazione è in esecuzione in modalità interfaccia utente, la finestra di dialogo Assertion verrà visualizzata per impostazione predefinita, il che potrebbe essere un po 'sconcertante per i tuoi utenti.

È possibile ignorare questo comportamento rimuovendo DefaultTraceListener: consultare la documentazione per Trace.Listeners in MSDN.

In sintesi,

  • Usa Debug.Assert liberamente per aiutare a rilevare i bug nelle build di Debug.

  • Se si utilizza Trace.Assert in modalità interfaccia utente, probabilmente si desidera rimuovere DefaultTraceListener per evitare di sconcertare gli utenti.

  • Se la condizione che stai testando è qualcosa che la tua app non è in grado di gestire, probabilmente è meglio lanciare un'eccezione, per garantire che l'esecuzione non continui. Tieni presente che un utente può scegliere di ignorare un'asserzione.


1
+1 per evidenziare la distinzione cruciale tra Debug.Assert e Trace.Assert, poiché l'OP ha specificamente chiesto il codice di produzione.
Jon Coombs,

21

Gli avvisi vengono utilizzati per rilevare l'errore del programmatore (tuo), non l'errore dell'utente. Dovrebbero essere utilizzati solo quando non vi è alcuna possibilità che un utente possa far scattare l'asserzione. Se stai scrivendo un'API, ad esempio, le asserzioni non dovrebbero essere utilizzate per verificare che un argomento non sia nullo in alcun metodo che un utente API possa chiamare. Ma potrebbe essere utilizzato in un metodo privato non esposto come parte dell'API per affermare che il TUO codice non passa mai un argomento nullo quando non è previsto.

Di solito preferisco le eccezioni alle asserzioni quando non ne sono sicuro.


11

In breve

Asserts sono usati per le protezioni e per controllare i vincoli di Design by Contract, vale a dire:

  • Assertsdovrebbe essere solo per build di debug e non di produzione. Gli asserti vengono in genere ignorati dal compilatore nelle build di rilascio.
  • Asserts può verificare la presenza di bug / condizioni impreviste che SONO nel controllo del sistema
  • Asserts NON sono un meccanismo per la convalida di prima linea dell'input dell'utente o delle regole aziendali
  • Assertsnon deve essere utilizzato per rilevare condizioni ambientali impreviste (che sono al di fuori del controllo del codice), ad esempio memoria insufficiente, errore di rete, errore del database, ecc. Sebbene siano rare, queste condizioni sono prevedibili (e il codice dell'app non può risolvere problemi come guasto hardware o esaurimento delle risorse). In genere, vengono generate eccezioni: l'applicazione può quindi intraprendere azioni correttive (ad esempio, riprovare un'operazione su un database o in rete, tentare di liberare memoria memorizzata nella cache) o interrompere in modo corretto se l'eccezione non può essere gestita.
  • Un'asserzione fallita dovrebbe essere fatale per il tuo sistema - vale a dire, diversamente da un'eccezione, non provare a catturare o gestire un errore Asserts- il tuo codice funziona in un territorio inaspettato. Stack Traces e crash dump possono essere utilizzati per determinare cosa è andato storto.

Le asserzioni hanno enormi vantaggi:

  • Aiutare a trovare la mancata convalida degli input dell'utente o i bug a monte nel codice di livello superiore.
  • Le affermazioni nella base di codici trasmettono chiaramente al lettore le ipotesi formulate nel codice
  • Assert verrà verificato in fase di esecuzione in Debugbuild.
  • Una volta che il codice è stato testato in modo esauriente, la ricostruzione del codice come Release rimuoverà il sovraccarico prestazionale di verifica del presupposto (ma con il vantaggio che una build di debug successiva ripristinerà sempre i controlli, se necessario).

... Più dettaglio

Debug.Assertesprime una condizione che è stata assunta sullo stato dal resto del blocco di codice all'interno del controllo del programma. Ciò può includere lo stato dei parametri forniti, lo stato dei membri di un'istanza di classe o che il ritorno da una chiamata di metodo è compreso nell'intervallo contrattato / progettato. In genere, le asserzioni dovrebbero bloccare il thread / processo / programma con tutte le informazioni necessarie (Stack Trace, Crash Dump, ecc.), Poiché indicano la presenza di un bug o una condizione non considerata per la quale non è stata progettata (ovvero non provare a catturare o gestire i fallimenti delle asserzioni), con una possibile eccezione di quando una stessa asserzione potrebbe causare più danni rispetto al bug (ad es. i controllori del traffico aereo non vorrebbero un YSOD quando un aereo diventa sottomarino, anche se è discutibile se una build di debug debba essere distribuita a produzione ...)

Quando utilizzare Asserts? - In qualsiasi punto di un sistema, API della libreria o servizio in cui gli input per una funzione o stato di una classe sono considerati validi (ad es. Quando la validazione è già stata effettuata sull'input dell'utente nel livello di presentazione di un sistema , le classi di business e di livello dati in genere presuppongono che siano stati già eseguiti controlli null, controlli di intervallo, controlli di lunghezza delle stringhe ecc. sull'input). - I Assertcontrolli comuni includono dove un'ipotesi non valida comporterebbe una dereferenza di oggetto nulla, un divisore zero, un overflow aritmetico numerico o di data e un generale fuori banda / non progettato per il comportamento (ad esempio se un int a 32 bit è stato utilizzato per modellare l'età di un essere umano , sarebbe prudente Assertche l'età sia effettivamente tra 0 e 125 o giù di lì - i valori di -100 e 10 ^ 10 non sono stati progettati per).

Contratti di codice .Net
Nello stack .Net, i contratti di codice possono essere utilizzati in aggiunta o in alternativa all'utilizzo Debug.Assert. I Contratti di codice possono ulteriormente formalizzare il controllo dello stato e possono aiutare a rilevare violazioni di ipotesi al momento della compilazione (o poco dopo, se eseguite come controllo in background in un IDE).

I controlli Design by Contract (DBC) disponibili includono:

  • Contract.Requires - Condizioni preliminari contrattate
  • Contract.Ensures - PostConditions contrattate
  • Invariant - Esprime un'ipotesi sullo stato di un oggetto in tutti i punti della sua durata.
  • Contract.Assumes - pacifica il controllo statico quando viene effettuata una chiamata a metodi decorati non contrattuali.

Sfortunatamente, i contratti di codice sono praticamente morti da quando la SM ha smesso di svilupparlo.
Mike Lowery,

10

Per lo più mai nel mio libro. Nella stragrande maggioranza delle occasioni, se vuoi controllare se tutto è sano, lancia se non lo è.

Quello che non mi piace è il fatto che rende una build di debug funzionalmente diversa da una build di rilascio. Se un'asserzione di debug fallisce ma la funzionalità funziona in versione, allora che senso ha questo senso? È ancora meglio quando l'assertore ha lasciato l'azienda da lungo tempo e nessuno conosce quella parte del codice. Quindi devi dedicare un po 'del tuo tempo ad esplorare il problema per vedere se è davvero un problema o no. Se è un problema, allora perché la persona non sta gettando in primo luogo?

Per me questo suggerisce usando Debug.Assert che stai rinviando il problema a qualcun altro, affrontalo tu stesso. Se qualcosa dovrebbe essere il caso e non lo è, allora lancia.

Immagino che ci siano probabilmente scenari critici per le prestazioni in cui desideri ottimizzare le tue affermazioni e che sono utili lì, tuttavia non sono ancora riuscito a trovare uno scenario del genere.


4
La tua risposta merita un certo merito, tuttavia, quando metti in evidenza alcune preoccupazioni spesso sollevate nei loro confronti, il fatto che interrompano la sessione di debug e la possibilità di falsi positivi. Tuttavia ti mancano alcune sottigliezze e stai scrivendo "ottimizza assert away" - che può essere basato solo sul pensare che lanciare un'eccezione e fare debug.assert sia lo stesso. Non lo sono, servono a scopi e caratteristiche diversi, come puoi vedere in alcune delle risposte votate. Dw
Casper Leon Nielsen,

+1 per "Quello che non mi piace è il fatto che rende una build di debug funzionalmente diversa da una build di rilascio. Se un'asserzione di debug fallisce ma la funzionalità funziona in versione, allora che senso ha?" In .NET, System.Diagnostics.Trace.Assert()viene eseguito in una build di rilascio e in una build di debug.
DavidRR,

7

Secondo lo standard IDesign , dovresti

Asserire ogni presupposto. In media, ogni quinta riga è un'affermazione.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

Come disclaimer dovrei menzionare che non ho trovato pratico implementare questo IRL. Ma questo è il loro standard.


Sembra che a Juval Lowy piaccia citare se stesso.
Devlord,

6

Utilizzare le asserzioni solo nei casi in cui si desidera rimuovere il controllo per le build di rilascio. Ricorda, le tue asserzioni non verranno attivate se non esegui la compilazione in modalità debug.

Dato il tuo esempio check-for-null, se si trova in un'API solo interna, potrei usare un'asserzione. Se si trova in un'API pubblica, utilizzerei sicuramente il controllo esplicito e il lancio.


In .NET, è possibile utilizzare System.Diagnostics.Trace.Assert()per eseguire un'asserzione in una build di rilascio (produzione).
DavidRR,

Regola di analisi del codice CA1062: Convalida argomenti di metodi pubblici richiede la verifica di un argomento per nullquando: "Un metodo visibile esternamente dereferenzia uno dei suoi argomenti di riferimento senza verificare se tale argomento è nullo ." In tale situazione, il metodo o la proprietà dovrebbero essere lanciati ArgumentNullException.
DavidRR,

6

Tutti gli assert dovrebbero essere codici che potrebbero essere ottimizzati per:

Debug.Assert(true);

Perché sta verificando qualcosa che hai già assunto sia vero. Per esempio:

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

In quanto sopra, ci sono tre diversi approcci ai parametri null. Il primo lo accetta come consentito (semplicemente non fa nulla). Il secondo genera un'eccezione per il codice chiamante da gestire (o meno, con conseguente messaggio di errore). Il terzo presuppone che non possa accadere, e afferma che è così.

Nel primo caso, non ci sono problemi.

Nel secondo caso, c'è un problema con il codice chiamante: non avrebbe dovuto chiamare GetFirstAndConsumecon null, quindi ottiene un'eccezione.

Nel terzo caso, c'è un problema con questo codice, perché avrebbe già dovuto essere verificato che en != nullprima che fosse chiamato, quindi non è vero è un bug. O in altre parole, dovrebbe essere un codice che teoricamente potrebbe essere ottimizzato Debug.Assert(true), sicne en != nulldovrebbe essere sempre true!


1
Quindi, nel terzo caso, cosa succede se en == nullin produzione? Stai forse dicendo che nonen == null può mai accadere in produzione (dal momento che il programma è stato completamente sottoposto a debug)? In tal caso, Debug.Assert(en != null)almeno serve come alternativa a un commento. Naturalmente, se vengono apportate modifiche future, continua anche ad avere valore per rilevare una possibile regressione.
DavidRR,

@DavidRR, sto davvero affermando che non può mai essere nullo, e così è l'affermazione nel codice, da cui il nome. Ovviamente potrei sbagliarmi o essere sbagliato da un cambiamento, e questo è il valore della chiamata di asserzione.
Jon Hanna,

1
Le chiamate a Debug.Assert()vengono rimosse in una build di rilascio. Quindi, se siete male, nel terzo caso , non sarà possibile sapere in produzione (supponendo che l'uso di una build di rilascio in produzione). Tuttavia, il comportamento del primo e del secondo caso è identico nelle build di debug e rilascio.
DavidRR,

@DavidRR, il che lo rende appropriato solo quando ritengo che ciò non possa accadere, poiché di nuovo è un'affermazione di fatto, non un controllo sullo stato. Ovviamente è anche inutile se ha l'affermazione, ha un bug che potrebbe catturare e tuttavia non ha mai colpito quel caso nei test.
Jon Hanna,

4

Ho pensato di aggiungere altri quattro casi, in cui Debug.Assert può essere la scelta giusta.

1) Qualcosa che non ho visto menzionato qui è la copertura concettuale aggiuntiva che gli Assert possono fornire durante i test automatizzati . Come semplice esempio:

Quando un chiamante di livello superiore viene modificato da un autore che ritiene di aver ampliato l'ambito del codice per gestire scenari aggiuntivi, idealmente (!) Scriverà unit test per coprire questa nuova condizione. È quindi possibile che il codice completamente integrato funzioni correttamente.

Tuttavia, in realtà è stato introdotto un sottile difetto, ma non rilevato nei risultati del test. In questo caso, la chiamata è diventata non deterministica e sembra solo fornire il risultato atteso. O forse ha prodotto un errore di arrotondamento che è stato inosservato. O ha causato un errore che è stato compensato ugualmente altrove. O concesso non solo l'accesso richiesto ma privilegi aggiuntivi che non dovrebbero essere concessi. Eccetera.

A questo punto, le istruzioni Debug.Assert () contenute nella chiamata insieme al nuovo caso (o limite) guidato dai test unitari possono fornire una preziosa notifica durante il test che le ipotesi dell'autore originale sono state invalidate e il codice non dovrebbe essere rilasciato senza ulteriore revisione. Le asserzioni con unit test sono i partner perfetti.

2) Inoltre, alcuni test sono semplici da scrivere, ma costosi e non necessari alla luce dei presupposti iniziali . Per esempio:

Se è possibile accedere a un oggetto solo da un determinato punto di ingresso protetto, è necessario eseguire una query aggiuntiva su un database dei diritti di rete da ogni metodo oggetto per garantire che il chiamante disponga delle autorizzazioni? Sicuramente no. Forse la soluzione ideale include la memorizzazione nella cache o qualche altra espansione di funzionalità, ma il design non lo richiede. Un Debug.Assert () mostrerà immediatamente quando l'oggetto è stato collegato a un punto di ingresso non sicuro.

3) Successivamente, in alcuni casi il prodotto potrebbe non avere alcuna interazione diagnostica utile per tutte o parte delle sue operazioni quando distribuito in modalità di rilascio . Per esempio:

Supponiamo che sia un dispositivo incorporato in tempo reale. Generare eccezioni e riavviare quando incontra un pacchetto non valido è controproducente. Al contrario, il dispositivo può trarre vantaggio dall'operazione di massimo sforzo, fino al punto di rendere il rumore nella sua uscita. Inoltre, potrebbe non avere un'interfaccia umana, un dispositivo di registrazione o addirittura essere fisicamente accessibile dall'essere umano quando distribuito in modalità di rilascio e la consapevolezza degli errori è meglio fornita valutando lo stesso output. In questo caso, le asserzioni liberali e i test approfonditi prima del rilascio sono più preziosi delle eccezioni.

4) Infine, alcuni test sono inutili solo perché la chiamata è percepita come estremamente affidabile . Nella maggior parte dei casi, più codice riutilizzabile è, maggiore è stato lo sforzo di renderlo affidabile. Pertanto è comune a Exception per parametri imprevisti dei chiamanti, ma asserire risultati imprevisti da callees. Per esempio:

Se un'operazione principale String.Findindica che restituirà a -1quando i criteri di ricerca non vengono trovati, potresti essere in grado di eseguire in sicurezza un'operazione anziché tre. Tuttavia, se è effettivamente tornato -2, potresti non avere un corso d'azione ragionevole. Sarebbe inutile sostituire il calcolo più semplice con uno che verifica separatamente un -1valore e irragionevole nella maggior parte degli ambienti di rilascio sporcare il codice con test che assicurano che le librerie di base funzionino come previsto. In questo caso gli Assert sono ideali.


4

Citazione tratta da The Pragmatic Programmer: da Journeyman a Master

Lascia asserzioni attivate

C'è un malinteso comune sulle asserzioni, promulgato dalle persone che scrivono compilatori e ambienti linguistici. Va qualcosa del genere:

Le asserzioni aggiungono un certo sovraccarico al codice. Perché controllano cose che non dovrebbero mai accadere, verranno attivate solo da un bug nel codice. Una volta che il codice è stato testato e spedito, non sono più necessari e devono essere disattivati ​​per velocizzare l'esecuzione del codice. Le asserzioni sono una funzione di debug.

Ci sono due ipotesi palesemente sbagliate qui. Innanzitutto, presumono che i test trovino tutti i bug. In realtà, per qualsiasi programma complesso è improbabile che verifichi anche una percentuale minuscola delle permutazioni che il tuo codice verrà sottoposto (vedi Test spietati).

In secondo luogo, gli ottimisti stanno dimenticando che il tuo programma viene eseguito in un mondo pericoloso. Durante il test, i ratti probabilmente non rosicchiano attraverso un cavo di comunicazione, qualcuno che gioca non esaurisce la memoria e i file di registro non riempiono il disco rigido. Queste cose potrebbero accadere quando il programma viene eseguito in un ambiente di produzione. La tua prima linea di difesa sta verificando la presenza di eventuali errori e la seconda utilizza le asserzioni per cercare di rilevare quelle che hai perso.

Disattivare le affermazioni quando si consegna un programma alla produzione è come attraversare un filo alto senza rete perché in pratica lo hai superato una volta . C'è un valore drammatico, ma è difficile ottenere un'assicurazione sulla vita.

Anche se hai problemi di prestazioni, disattiva solo quelle asserzioni che ti colpiscono davvero .


2

Dovresti sempre usare il secondo approccio (gettando eccezioni).

Inoltre, se sei in produzione (e hai una build di rilascio), è meglio generare un'eccezione (e lasciare che l'app si blocchi nel peggiore dei casi) piuttosto che lavorare con valori non validi e forse distruggere i dati dei tuoi clienti (che possono costare migliaia di dollari).


1
No, come altre risposte qui: non capisci davvero la differenza, quindi annulli una delle offerte, creando una falsa dicotomia tra loro nel processo. Dw
Casper Leon Nielsen,

3
Questa è l'unica risposta giusta su questo elenco IMO. Non licenziarlo così facilmente Casper. Debug Assert è un anti-pattern. Se è invariante al momento del debug è invariante in fase di esecuzione. Consentire all'app di continuare con un invariante rotto ti lascia in uno stato non deterministico e problemi potenzialmente peggiori rispetto all'arresto anomalo. IMO è meglio avere lo stesso codice in entrambe le build che falliscono rapidamente con contratti rotti e quindi implementano una solida gestione degli errori al massimo livello. ad es. Isolare i componenti e implementare la possibilità di riavviarli (come una scheda che si arresta in modo anomalo in un browser non arresta in modo anomalo l'intero browser).
justin.m.chase

1
Potrebbe essere utile includere Trace.Assert nella tua discussione qui, poiché non può essere respinto dallo stesso argomento.
Jon Coombs,

0

È necessario utilizzare Debug.Assert per verificare errori logici nei programmi. Il compilatore può solo informarti di errori di sintassi. Quindi dovresti assolutamente usare le istruzioni Assert per verificare errori logici. Ad esempio, testando un programma che vende auto che solo le BMW blu dovrebbero ottenere uno sconto del 15%. Il complier non può dirti nulla se il tuo programma è logicamente corretto nell'esecuzione ma un'affermazione assert potrebbe farlo.


2
scusate ma le eccezioni fanno tutte le stesse cose, quindi questa risposta non affronta la vera domanda.
Roman Starkov,

0

Ho letto le risposte qui e ho pensato di aggiungere una distinzione importante. Esistono due modi molto diversi in cui vengono utilizzate le asserzioni. Uno è come scorciatoia temporanea per gli sviluppatori di "Questo non dovrebbe davvero accadere, quindi se mi fa sapere in modo che io possa decidere cosa fare", un po 'come un breakpoint condizionale, per i casi in cui il tuo programma è in grado di continuare. L'altro è un modo per inserire nel codice ipotesi su stati di programma validi.

Nel primo caso, le affermazioni non devono nemmeno essere nel codice finale. Dovresti usarlo Debug.Assertdurante lo sviluppo e puoi rimuoverlo se / quando non è più necessario. Se si desidera lasciarli o se si dimentica di rimuoverli, nessun problema, poiché non avranno alcuna conseguenza nelle compilation di Release.

Ma nel secondo caso, le asserzioni fanno parte del codice. Asseriscono che le tue assunzioni sono vere e le documentano. In tal caso, vuoi davvero lasciarli nel codice. Se il programma è in uno stato non valido, non dovrebbe essere consentito di continuare. Se non potessi permetterti il ​​colpo di prestazione, non utilizzeresti C #. Da un lato, potrebbe essere utile essere in grado di collegare un debugger se accade. Dall'altro, non vuoi che la traccia dello stack venga visualizzata sui tuoi utenti e forse più importante non vuoi che possano ignorarlo. Inoltre, se è in un servizio verrà sempre ignorato. Pertanto nella produzione il comportamento corretto sarebbe quello di lanciare un'eccezione e utilizzare la normale gestione delle eccezioni del programma, che potrebbe mostrare all'utente un bel messaggio e registrare i dettagli.

Trace.Assertha il modo perfetto per raggiungere questo obiettivo. Non verrà rimosso durante la produzione e può essere configurato con diversi listener utilizzando app.config. Quindi per lo sviluppo il gestore predefinito va bene, e per la produzione è possibile creare un semplice TraceListener come sotto che genera un'eccezione e attivarlo nel file di configurazione della produzione.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

E nel file di configurazione della produzione:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>

-1

Non so come sia in C # e .NET, ma in C assert () funzionerà solo se compilato con -DDEBUG - l'utente finale non vedrà mai un assert () se è compilato senza. È solo per sviluppatori. Lo uso molto spesso, a volte è più facile rintracciare i bug.


-1

Non li userei nel codice di produzione. Genera eccezioni, cattura e registra.

Bisogna anche fare attenzione in asp.net, poiché un'asserzione può apparire sulla console e bloccare le richieste.

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.