Affermare il male? [chiuso]


199

I Gocreatori della lingua scrivono :

Go non fornisce affermazioni. Sono innegabilmente convenienti, ma la nostra esperienza è stata che i programmatori li usano come stampella per evitare di pensare a una corretta gestione e segnalazione degli errori. La corretta gestione degli errori significa che i server continuano a funzionare dopo errori non fatali invece di arresti anomali. La corretta segnalazione degli errori significa che gli errori sono diretti e diretti, salvando il programmatore dall'interpretazione di una traccia di crash di grandi dimensioni. Gli errori precisi sono particolarmente importanti quando il programmatore che vede gli errori non ha familiarità con il codice.

Qual é la tua opinione riguardo questo?


4
tangente: Go è un linguaggio insolitamente supponente. Questa non è necessariamente una brutta cosa. Tuttavia, significa che dovresti prendere le sue opinioni con un granello di sale più grande. Significa anche che se non sei d'accordo, ti digrignerai i denti mentre usi la lingua. Come prova di come Go si aggrappi alle sue opinioni nonostante la realtà, considera che devi ricorrere alla magia della riflessione per determinare se due collezioni sono uguali.
codice alleato

@allyourcode Se ti riferisci a reflect.DeepEqual, sicuramente non ne hai bisogno . È conveniente, ma a scapito delle prestazioni (i test unitari sono un buon caso d'uso). Altrimenti, puoi implementare qualunque controllo di uguaglianza sia appropriato per la tua "raccolta" senza troppi problemi.
Igor Dubinskiy,

1
No, non è di questo che sto parlando. Non esiste una cosa come slice1 == slice2 senza riflessione. Tutte le altre lingue hanno un equivalente di questa operazione super basilare. L'unica ragione per cui Go non è il pregiudizio.
codice alleato

Puoi confrontare due sezioni senza riflessione usando un forloop in Go (proprio come C). E sarebbe davvero bello avere operazioni fetta generici, anche se il confronto si complica quando i puntatori e le strutture sono coinvolti.
Kbolino,

Risposte:


321

No, non c'è niente di sbagliato assertfintanto che lo usi come previsto.

Cioè, dovrebbe essere per rilevare casi che "non possono accadere", durante il debug, al contrario della normale gestione degli errori.

  • Assert: un errore nella logica del programma stesso.
  • Gestione degli errori: input errato o stato del sistema non dovuto a un bug nel programma.

109

No, gotoassertsono né cattivi. Ma entrambi possono essere usati in modo improprio.

Assert è per i controlli di sanità mentale. Cose che dovrebbero uccidere il programma se non sono corrette. Non per la convalida o in sostituzione della gestione degli errori.


come usare gotosaggiamente?
ar2015,

1
@ ar2015 Trova uno dei modelli assurdamente inventati che alcune persone raccomandano di evitare gotoper motivi puramente religiosi, quindi usa gotoinvece di offuscare ciò che stai facendo. In altre parole: se riesci a dimostrare che hai davvero bisogno goto, e l'unica alternativa è mettere in atto un carico di impalcature inutili che alla fine fanno la stessa cosa ma senza alterare la polizia Goto ... quindi basta usare goto. Ovviamente, un prerequisito è "se puoi dimostrare di aver davvero bisogno goto". Spesso le persone no. Ciò non significa che sia intrinsecamente una cosa negativa.
underscore_d

2
gotoè usato nel kernel Linux per la pulizia del codice
malat,

61

Secondo questa logica, anche i punti di interruzione sono malvagi.

Gli assert dovrebbero essere usati come aiuto per il debug e nient'altro. "Evil" è quando si tenta di utilizzarli anziché la gestione degli errori.

Gli assegni sono lì per aiutare te, il programmatore, a rilevare e risolvere problemi che non devono esistere e verificare che i tuoi presupposti rimangano veri.

Non hanno nulla a che fare con la gestione degli errori, ma sfortunatamente alcuni programmatori li abusano come tali e quindi li dichiarano "cattivi".


40

Mi piace usare assert molto. Lo trovo molto utile quando creo applicazioni per la prima volta (forse per un nuovo dominio). Invece di fare un controllo degli errori molto elaborato (che prenderei in considerazione l'ottimizzazione prematura) codifico velocemente e aggiungo molte affermazioni. Dopo aver saputo di più su come funzionano le cose, faccio una riscrittura e rimuovo alcuni degli asserti e li cambio per una migliore gestione degli errori.

A causa delle asserzioni passo molto meno tempo a programmare / eseguire il debug dei programmi.

Ho anche notato che le affermazioni mi aiutano a pensare a molte cose che potrebbero interrompere i miei programmi.


31

Come ulteriore informazione, go fornisce una funzione integrata panic. Questo può essere usato al posto di assert. Per esempio

if x < 0 {
    panic("x is less than 0");
}

panicstamperà la traccia dello stack, quindi in qualche modo ha lo scopo di assert.


30

Dovrebbero essere utilizzati per rilevare bug nel programma. Input dell'utente non male.

Se usato correttamente, essi sono non male.


13

Questo si presenta molto, e penso che un problema che confonde le difese delle asserzioni sia che spesso si basano sul controllo degli argomenti. Quindi considera questo diverso esempio di quando potresti usare un'asserzione:

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

Si utilizza un'eccezione per l'input perché ci si aspetta che a volte si ottengano input non validi. Afferma che l'elenco è ordinato per aiutarti a trovare un bug nel tuo algoritmo, che per definizione non ti aspetti. L'asserzione è solo nella build di debug, quindi anche se il controllo è costoso, non ti dispiace farlo su ogni singola invocazione della routine.

Devi ancora testare il tuo codice di produzione, ma questo è un modo diverso e complementare per assicurarti che il tuo codice sia corretto. I test unitari assicurano che la tua routine sia all'altezza della sua interfaccia, mentre le asserzioni sono un modo più preciso per assicurarsi che l'implementazione stia facendo esattamente quello che ti aspetti.


8

Le affermazioni non sono malvagie ma possono essere facilmente utilizzate in modo improprio. Concordo con l'affermazione secondo cui "le affermazioni vengono spesso utilizzate come stampella per evitare di pensare a una corretta gestione e segnalazione degli errori". L'ho visto abbastanza spesso.

Personalmente, mi piace usare le asserzioni perché documentano le ipotesi che avrei potuto fare mentre scrivevo il mio codice. Se questi presupposti vengono interrotti mantenendo il codice, il problema può essere rilevato durante il test. Tuttavia, ho intenzione di eliminare ogni affermazione dal mio codice quando eseguo un build di produzione (cioè usando #ifdefs). Eliminando le asserzioni nella build di produzione, elimino il rischio che qualcuno le usi impropriamente come stampella.

C'è anche un altro problema con le asserzioni. Le asserzioni vengono verificate solo in fase di esecuzione. Ma spesso accade che il controllo che si desidera eseguire sia stato eseguito in fase di compilazione. È preferibile rilevare un problema al momento della compilazione. Per i programmatori C ++, boost fornisce BOOST_STATIC_ASSERT che ti consente di farlo. Per i programmatori C, questo articolo ( testo del link ) descrive una tecnica che può essere utilizzata per eseguire asserzioni in fase di compilazione.

In sintesi, la regola empirica che seguo è: non usare asserzioni in una build di produzione e, se possibile, usare solo asserzioni per cose che non possono essere verificate in fase di compilazione (cioè, devono essere verificate in fase di esecuzione).


5

Ammetto di aver usato le affermazioni senza considerare la corretta segnalazione degli errori. Tuttavia, ciò non toglie che siano molto utili se utilizzati correttamente.

Sono particolarmente utili se vuoi seguire il principio "Crash Early". Ad esempio, supponiamo di implementare un meccanismo di conteggio dei riferimenti. In alcune posizioni del tuo codice sai che il refcount dovrebbe essere zero o uno. E supponiamo anche che se il refcount è sbagliato il programma non si arresterà immediatamente ma durante il successivo ciclo di messaggi a quel punto sarà difficile scoprire perché le cose sono andate male. Un'asserzione sarebbe stata utile nel rilevare l'errore più vicino alla sua origine.


5

Preferisco evitare il codice che fa cose diverse nel debug e nel rilascio.

Rompere il debugger su una condizione e avere tutte le informazioni su file / linee è utile, anche l'espressione esatta e il valore esatto.

Avere un'affermazione che "valuterà la condizione solo nel debug" può essere un'ottimizzazione delle prestazioni e, come tale, utile solo nello 0,0001% dei programmi, in cui le persone sanno cosa stanno facendo. In tutti gli altri casi questo è dannoso, poiché l'espressione può effettivamente cambiare lo stato del programma:

assert(2 == ShroedingersCat.GetNumEars()); farebbe in modo che il programma facesse cose diverse nel debug e nel rilascio.

Abbiamo sviluppato una serie di macro assert che genererebbero un'eccezione e lo facciamo sia in versione di debug che di versione. Ad esempio, THROW_UNLESS_EQ(a, 20);genererebbe un'eccezione con quale messaggio () contenente sia file, linea che i valori effettivi di a e così via. Solo una macro avrebbe il potere per questo. Il debugger può essere configurato per interrompersi al "lancio" del tipo di eccezione specifico.


4
Il 90% delle statistiche utilizzate negli argomenti è falso.
João Portela,

5

Non mi piace asserire intensamente. Non vorrei spingermi fino a dire che sono malvagi.

Fondamentalmente un'asserzione farà la stessa cosa di un'eccezione non selezionata, l'unica eccezione è che l'asserzione (normalmente) non dovrebbe essere mantenuta per il prodotto finale.

Se costruisci una rete di sicurezza per te stesso mentre esegui il debug e costruisci il sistema perché dovresti negare questa rete di sicurezza per il tuo cliente, il tuo help desk di supporto o chiunque possa utilizzare il software che stai attualmente costruendo. Utilizzare le eccezioni esclusivamente per affermazioni e situazioni eccezionali. Creando una gerarchia di eccezioni appropriata sarai in grado di discernere molto rapidamente l'uno dall'altro. Tranne che questa volta l'asserzione rimane attiva e può fornire informazioni preziose in caso di guasto che altrimenti andrebbe perso.

Quindi comprendo appieno i creatori di Go rimuovendo del tutto le asserzioni e costringendo i programmatori a utilizzare le eccezioni per gestire la situazione. C'è una semplice spiegazione per questo, le eccezioni sono solo un meccanismo migliore per il lavoro perché attenersi alle affermazioni arcaiche?


Go non ha eccezioni. Il solito motivo per utilizzare un'asserzione piuttosto che un'eccezione è perché si desidera che venga eluso nella distribuzione per motivi di prestazioni. Le asserzioni non sono arcaiche. Mi dispiace sembrare duro, ma questa risposta non è di aiuto remoto alla domanda originale, né corretta.
Nir Friedman,


3

Di recente ho iniziato ad aggiungere alcuni assertori al mio codice, ed è così che l'ho fatto:

Divido mentalmente il mio codice in codice perimetrale e codice interno. Il codice limite è il codice che gestisce l'input dell'utente, legge i file e ottiene i dati dalla rete. In questo codice, richiedo l'input in un ciclo che esce solo quando l'input è valido (nel caso dell'input dell'utente interattivo), o genera eccezioni nel caso di dati corrotti / file non recuperabili.

Il codice interno è tutto il resto. Ad esempio, una funzione che imposta una variabile nella mia classe potrebbe essere definita come

void Class::f (int value) {
    assert (value < end);
    member = value;
}

e una funzione che riceve input da una rete potrebbe leggere come tale:

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

Questo mi dà due livelli di controlli. Qualsiasi cosa in cui i dati vengono determinati in fase di esecuzione ottiene sempre un'eccezione o una gestione immediata degli errori. Tuttavia, quel check in aggiuntivo Class::fcon la assertdichiarazione significa che se un codice interno dovesse mai chiamare Class::f, ho ancora un controllo di integrità. Il mio codice interno potrebbe non passare un argomento valido (perché potrei aver calcolato valueda alcune serie complesse di funzioni), quindi mi piace avere l'affermazione nella funzione di impostazione per documentare che, indipendentemente da chi sta chiamando la funzione, valuenon deve essere maggiore di o uguale a end.

Questo sembra adattarsi a ciò che sto leggendo in alcuni punti, secondo cui le affermazioni dovrebbero essere impossibili da violare in un programma ben funzionante, mentre le eccezioni dovrebbero essere per casi eccezionali ed errati che sono ancora possibili. Poiché in teoria sto convalidando tutti gli input, non dovrebbe essere possibile attivare la mia affermazione. Se lo è, il mio programma è sbagliato.


2

Se le affermazioni di cui stai parlando significano che il programma vomita e poi esiste, le affermazioni possono essere molto negative. Questo non vuol dire che sono sempre la cosa sbagliata da usare, sono un costrutto che può essere facilmente usato male. Hanno anche molte alternative migliori. Cose del genere sono buoni candidati per essere chiamati malvagi.

Ad esempio, un modulo di terze parti (o qualsiasi modulo realmente) non dovrebbe quasi mai uscire dal programma chiamante. Questo non dà al programmatore chiamante alcun controllo sul rischio che il programma dovrebbe correre in quel momento. In molti casi, i dati sono così importanti che persino salvare i dati danneggiati è meglio che perdere quei dati. Gli asserti possono costringerti a perdere dati.

Alcune alternative alle affermazioni:

  • Utilizzando un debugger,
  • Console / database / altra registrazione
  • eccezioni
  • Altri tipi di gestione degli errori

Alcuni riferimenti:

Persino le persone che sostengono l'asserzione pensano che dovrebbero essere usate solo nello sviluppo e non nella produzione:

Questa persona afferma che le asserzioni dovrebbero essere usate quando il modulo ha dati potenzialmente corrotti che persistono dopo il lancio di un'eccezione: http://www.advogato.org/article/949.html . Questo è certamente un punto ragionevole, tuttavia, un modulo esterno non dovrebbe mai prescrivere quanto siano importanti i dati corrotti per il programma chiamante (uscendo "per" loro). Il modo corretto di gestirlo è lanciare un'eccezione che chiarisca che il programma potrebbe ora essere in uno stato incoerente. E poiché i buoni programmi consistono principalmente di moduli (con un po 'di codice colla nell'eseguibile principale), le asserzioni sono quasi sempre la cosa sbagliata da fare.


1

assert è molto utile e può farti risparmiare un sacco di backtracking quando si verificano errori imprevisti arrestando il programma ai primi segni di problemi.

D'altra parte, è molto facile abusare assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

La versione corretta e corretta sarebbe simile a:

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

Quindi ... a lungo termine ... nel quadro generale ... Devo ammettere che si assertpuò abusare. Lo faccio tutto il tempo.


1

assert viene abusato per la gestione degli errori perché è meno digitante.

Quindi, come progettisti del linguaggio, dovrebbero piuttosto vedere che una corretta gestione degli errori può essere fatta con una digitazione ancora minore. Escludere l'asserzione perché il meccanismo di eccezione è dettagliato non è la soluzione. Oh aspetta, anche Go non ha eccezioni. Peccato :)


1
Non male :-) Eccezioni o no, affermazioni o no, i fan di Go stanno ancora parlando di quanto siano brevi i codici.
Moshe Revah,

1

Mi è sembrato di dare un calcio in testa all'autore quando l'ho visto.

Uso assert continuamente nel codice e alla fine li sostituisco tutti quando scrivo più codice. Li uso quando non ho scritto la logica richiesta e voglio essere avvisato quando corro nel codice invece di scrivere un'eccezione che verrà eliminata man mano che il progetto si avvicina al completamento.

Le eccezioni si fondono anche più facilmente con il codice di produzione che non mi piace. Un'asserzione è più facile da notare dithrow new Exception("Some generic msg or 'pretend i am an assert'");


1

Il mio problema con queste risposte a difesa dell'asserzione è che nessuno specifica chiaramente cosa lo rende diverso da un normale errore fatale e perché un'asserzione non può essere un sottoinsieme di un'eccezione . Ora, detto questo, cosa succede se l'eccezione non viene mai rilevata? Ciò la rende un'affermazione per nomenclatura? E perché mai vorresti imporre una restrizione nella lingua che un'eccezione può essere sollevata che / niente / può gestire?


Se guardi la mia risposta. Il mio uso è quello di differenziare le "eccezioni" (asserzioni) di cui desidero sbarazzarmi, usate per il debug e le eccezioni che conservo. Perché dovrei voler sbarazzarmi di loro? perché senza di loro lavorare non sarebbe completo. L'esempio è se gestisco 3 casi e il quarto è todo. Posso facilmente cercare asserire di trovarli nel codice e conoscerne il incompleto piuttosto che usare un'eccezione che potrebbe essere accidentalmente catturata (da un altro programmatore) o difficile dire se è un'eccezione o un controllo logico che dovrei risolvere nel codice.

Ai miei occhi, questa è una cattiva idea, allo stesso livello delle lezioni "sigillate" e per lo stesso motivo. Stai assumendo che le eccezioni che desideri mantenere siano accettabili per gli usi del tuo codice che non conosci ancora. Tutte le eccezioni passano attraverso gli stessi canali e se l'utente non vuole catturarli può scegliere di non farlo. Se lo fa, dovrebbe avere anche l'abilità. Ad ogni modo, stai solo facendo ipotesi o spingendo fuori la tua pratica con un concetto come un'asserzione.
Evan Carroll,

Ho deciso che gli scenari di esempio sono i migliori. Ecco semplice. int func (int i) {if (i> = 0) {console.write ("Il numero è positivo {0}", i); } else {assert (false); // to lazy to do negative ATM} return i * 2; <- Come potrei farlo senza affermazioni ed è davvero un'eccezione migliore? e ricorda, questo codice sarà implementato prima del rilascio.

Sicuramente le eccezioni sono migliori, diciamo che prendo l'input dell'utente e chiamo func()con un numero negativo. Ora, improvvisamente con le tue affermazioni, hai tirato fuori il tappeto da sotto di me e non mi hai dato la possibilità di riprendermi, piuttosto che dirmi educatamente che cosa sto chiedendo non può essere fatto. Non c'è nulla di sbagliato nell'accusare il programma di comportarsi male e nel dargli una citazione: il problema è che offuschi l'atto di far rispettare una legge e condannare il criminale.
Evan Carroll,

Questo è il punto, DOVREBBE dire che ciò che l'utente vuole fare non può essere fatto. L'app dovrebbe terminare con l'errore msg e chiunque esegua il debug ricorderebbe qualcosa che DOVREBBE ESSERE FATTO ma non lo è. NON vuoi che sia gestito. Vuoi una risoluzione e ti viene ricordato che non hai gestito quel caso. ed è estremamente facile vedere quali casi sono rimasti poiché tutto ciò che devi fare è una ricerca di asserzioni nel codice. La gestione dell'errore sarebbe errata poiché il codice dovrebbe essere in grado di farlo una volta pronto per la produzione. Permettere a un programmatore di catturarlo e fare qualcos'altro è sbagliato.

1

Sì, le asserzioni sono malvagie.

Spesso vengono utilizzati in luoghi in cui è necessario utilizzare la corretta gestione degli errori. Abituarsi a scrivere dall'inizio una corretta gestione degli errori di qualità della produzione!

Di solito si intromettono nella scrittura di unit test (a meno che non si scriva un'asserzione personalizzata che interagisce con il cablaggio del test). Ciò è spesso dovuto al fatto che è necessario utilizzare la corretta gestione degli errori.

Principalmente vengono compilati dalle build di rilascio, il che significa che nessuno dei loro "test" è disponibile quando si esegue il codice che si rilascia effettivamente; dato che in situazioni multi-thread spesso i problemi peggiori che compaiono solo nel codice di rilascio possono essere negativi.

A volte sono una stampella per disegni altrimenti rotti; vale a dire che la progettazione del codice consente a un utente di chiamarlo in un modo che non dovrebbe essere chiamato e l'asserzione "impedisce" questo. Correggi il design!

Ne ho scritto di più sul mio blog nel 2005 qui: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html


0

Non tanto male quanto generalmente controproducente. Esiste una separazione tra controllo permanente degli errori e debug. Assert fa pensare alle persone che tutti i debug dovrebbero essere permanenti e causa enormi problemi di leggibilità quando usati molto. La gestione permanente degli errori dovrebbe essere migliore di quella laddove necessario, e poiché assert causa i propri errori è una pratica alquanto discutibile.


5
assert è utile per dichiarare le condizioni preliminari all'inizio di una funzione e, se chiaramente scritto, funge da parte della documentazione della funzione.
Peter Cordes,

0

non uso mai assert (), gli esempi di solito mostrano qualcosa del genere:

int* ptr = new int[10];
assert(ptr);

Questo è male, non lo faccio mai, cosa succede se il mio gioco sta assegnando un gruppo di mostri? perché dovrei interrompere il gioco, invece dovresti gestire gli errori con grazia, quindi fai qualcosa del tipo:

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}

14
newnon ritorna mai nullptr, lancia.
David Stone,

Nota che puoi usare std :: nothrow per questo.
markand
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.