Strategie per aumentare la manutenibilità delle asserzioni nel codice [chiuso]

5

Sfondo

Sto scrivendo un compilatore per una lingua personalizzata per un progetto scolastico e sta andando davvero bene per me.

Se dovessi ricominciare tutto da capo avrei fatto molte architetture software diverse, ma al momento non ho tempo o motivazione per una completa riscrittura, quindi mi concentrerò solo sulla conclusione di questo progetto.

C'è una cosa nel mio codice che voglio refactoring in questo momento ed è il modo in cui organizzo le mie asserzioni.

Sto scrivendo questo compilatore nel C usando flex e bison per generare il mio lexer e il parser. Il codice in questo progetto non è molto pulito e abbastanza disordinato, ma non me ne vergogno (i compilatori sono piuttosto complessi e questo è il mio primo tentativo). Ma ho aggiunto delle asserzioni nel codice per far sì che i bug vengano individuati in tempo e più facili da rintracciare.

Il problema

Il problema che sto avendo con assert in questo progetto è che c'è semplicemente troppo da controllare.

Ecco un esempio di una funzione che aggiunge i parametri della funzione alla tabella dei simboli durante la compilazione di una definizione di funzione:

static void add_function_params_to_symbols(struct context *con, 
        const struct ast_op *function_def) {
    struct ast *child = function_def->childs[0];
    assert(child);
    assert(child->type == SYN_DECLARATOR);
    assert(child->val_type == AST_OP);
    child = child->val.op.childs[1];
    assert(child);
    assert(child->type == SYN_FUNCTION);
    assert(child->val_type == AST_OP);
    child = child->val.op.childs[1];
    assert(child);
    assert(child->type == SYN_PARAMETER_LIST);
    assert(child->val_type == AST_OP);
    parameter_list_to_symbols(con, &child->val.op);
}

Questa funzione ha tre volte tante asserzioni come istruzioni di codice!

Questo è un esempio estremo perché questa funzione deve scendere di tre livelli fino alla fine dell'albero per trovare il PARAMETER_LIST che poi passa a parameter_list_to_symbols che aggiunge i parametri alla tabella dei simboli.

L'albero di analisi per una definizione di funzione nel mio compilatore assomiglia un po 'a questo:

FUNCTION_DEF
 DECLARATOR
  IDENTIFIER [function_name]
  FUNCTION
   [return type]
   PARAMETER_LIST
    [declarators containing type and name for each parameter]
    [...]
 FUNCTION_BODY
  [statements in function body]
  [...]

Per me sono quattro le cose da testare per ogni nodo che utilizzo nel programma.

  1. assert(node) per verificare che il nodo non sia NULL

  2. assert(node->type == SYN_FOOBAR) per verificare che il nodo sia del tipo previsto (come SYN_EXPRESSION o SYN_VARIABLE ).

  3. assert(child->val_type == AST_OP) per verificare che il nodo abbia il giusto tipo di valore union . I nodi possono contenere anche altri valori. Ad esempio nei nodi che memorizzano un valore di stringa child->val_type sarebbe uguale a AST_STRING . AST_OP significa che il nodo è un nodo operatore che è un nodo che ha figli.

  4. Probabilmente dovrei anche affermare che ogni nodo ha il giusto numero di bambini con assert(child->val.op.n_childs == XXX) ma non l'ho fatto in questo esempio di codice perché è già pieno di asserzioni e affermando che anche questo non sembra aggiungi molto valore.

Probabilmente ho scritto centinaia di asserzioni come questa nel mio codice e sembra che questo stia diventando difficile da mantenere, ma non sono sicuro di come dovrei riorganizzarlo.

La domanda

Sto "affermando" cose nel mio codice? Quali cose dovrei affermare e quali cose posso assumere sono OK ?

Come dovrei strutturare le mie asserzioni per renderle più facili da mantenere?

Se hai altri suggerimenti sull'utilizzo di asserzioni e test simili in codice, per favore menzionali nella risposta.

    
posta wefwefa3 28.11.2015 - 23:36
fonte

1 risposta

11

Am I "over asserting" stuff in my code?

No. Hai persino ammesso di sapere almeno una piccola cosa che avresti potuto affermare, ma non l'hai fatto, quindi non stai affermando abbastanza.

What things should I assert and what things can I assume are OK?

Asserisci tutto . Assumi nulla .

How should I structure my assertions to make them easier to maintain?

Una tecnica che uso è che ho delle funzioni speciali di asserzione che devono essere invocate da asserzioni, per eseguire insieme molte asserzioni. Iniziano sempre con il prefisso 'assert', e restituiscono sempre true, in modo che se nessuna delle asserzioni contenute fallisce, allora l'asserzione della chiamata avrà successo. Quindi, ad esempio:

boolean assertNodeOfTypeXYZIsOkay( Node* pNode )
{
    assert( pNode != NULL );
    assert( pNode->something_irrelevant_to_node_type );
    assert( pNode->type == NODE_TYPE_XYZ );
    assert( pNode->one_thing_relevant_to_node_type );
    assert( pNode->another_thing_relevant_to_node_type );
    ...
    return true;
}

Che viene invocato come segue:

assert( assertNodeOfTypeXYZIsOkay( pNode ) );

Questo mi consente di ottenere molte asserzioni fuori strada, godendo allo stesso tempo del vantaggio della compilazione condizionale: se le asserzioni sono disabilitate, assertNodeIsOkay() non sarà solo vuoto, ma non verrà neanche invocato.

If you have more tips on using assertions and similar tests in code please mention it in your answer.

Avere più asserzioni del codice reale è un po 'raro, ma non è inaudito. Dipende davvero da cosa stai facendo, e sembra che il tuo particolare tipo di progetto richieda che questa situazione si verifichi. Va bene.

Una volta che il tuo compilatore è maturo e diventa stabile, smetterà di funzionare ogni tanto a causa di un testo di input stranamente formulato. Quando ciò accade, sarai in grado di avere una versione di build del tuo compilatore su cui le asserzioni sono disabilitate, per il massimo delle prestazioni. Con questo in mente, dovresti applicare le asserzioni il più liberamente possibile, perché praticamente non costano nulla.

Le asserzioni sono uno strumento di documentazione eccellente, dal momento che a) i commenti non sono applicabili dal compilatore, e b) nessun commento è mai stato, o sarà mai, non ambiguo come un pezzo di codice che stabilisce la stessa cosa.

Le asserzioni riducono la complessità del tuo codice, in contrasto con qualsiasi altra istruzione che puoi usare, che in effetti aumenta la complessità del tuo codice.

Le asserzioni aiutano a costruire sistemi che, in caso di errore, possono dirti cosa c'è di sbagliato in se stessi. Quello, proprio lì, non ha prezzo.

Le asserzioni sono la cosa migliore dopo il pane a fette.

La domanda che dovresti sempre porre non è "dovrei affermarlo?" ma "c'è qualcosa che ho dimenticato di affermare?"

    
risposta data 29.11.2015 - 00:47
fonte

Leggi altre domande sui tag