Verifica delle condizioni preliminari o meno

8

Volevo trovare una risposta solida alla domanda se disporre o meno di controlli di runtime per convalidare l'input allo scopo di garantire che un client si sia attenuto alla fine dell'accordo in fase di progettazione per contratto. Ad esempio, considera un semplice costruttore di classi:

class Foo
{
public:
  Foo( BarHandle bar )
  {
    FooHandle handle = GetFooHandle( bar );
    if( handle == NULL ) {
      throw std::exception( "invalid FooHandle" );
    }
  }
};

In questo caso direi che un utente non dovrebbe tentare di costruire un Foo senza un BarHandle valido. Non sembra giusto verificare che bar sia valido all'interno del costruttore di Foo . Se mi limito a documentare che il costruttore di Foo richiede un valido BarHandle , non è abbastanza? È un modo corretto per far rispettare la mia condizione preliminare nella progettazione per contratto?

Finora, tutto ciò che ho letto ha opinioni contrastanti su questo. Sembra che il 50% delle persone direbbe che bar è valido, l'altro 50% direbbe che non dovrei farlo, ad esempio consideri un caso in cui l'utente verifica che il BarHandle sia corretto, ma un il secondo (e non necessario) controllo viene eseguito anche all'interno del costruttore di Foo .

    
posta void.pointer 09.04.2012 - 19:12
fonte

3 risposte

10

Non penso che ci sia una sola risposta a questo. Penso che la cosa principale che è necessaria sia la coerenza: o si impongono le precondizioni tutte su una funzione, oppure non si tenta di imporre alcun di esse. Sfortunatamente, questo è abbastanza raro - ciò che accade normalmente è che invece di pensare alle precondizioni e farle rispettare, i programmatori aggiungono bit di codice per far rispettare le precondizioni la cui violazione ha causato guasti durante i test, ma lasciano spesso aperte altre possibilità che potrebbero causare errori ma non si è verificato nei test.

In molti casi, è abbastanza ragionevole fornire due livelli: uno per l'uso "interno" che non fa alcun tentativo di far rispettare le precondizioni, e poi un secondo per "fuori" usare che solo applica le precondizioni , quindi invoca il primo.

Tuttavia, ritengo sia preferibile applicare le precondizioni nel nodo di origine, non solo documentate. Un'eccezione o affermazione è molto più difficile da ignorare rispetto alla documentazione e molto più probabile che rimanga sincronizzata con il resto del codice.

    
risposta data 09.04.2012 - 19:19
fonte
4

È una domanda molto difficile, perché ci sono molti concetti diversi:

  • Correttezza
  • Documentazione
  • Prestazioni

Tuttavia questo è principalmente un artefatto di un errore tipo , in questo caso. La nullità è meglio applicata dai vincoli di tipo, perché il compilatore li controlla effettivamente. Tuttavia, poiché non tutto può essere catturato in un sistema di tipi, specialmente in C ++, la domanda stessa vale comunque la pena.

Personalmente, ritengo che la correttezza e la documentazione siano di primaria importanza. Essere veloci e sbagliati è inutile. Essere veloce e solo sbagliato a volte è un po 'meglio, ma non porta molto in tavola.

Sebbene le prestazioni possano essere critiche in alcune parti dei programmi, alcuni controlli possono essere piuttosto estesi (ad esempio: provare che un grafico diretto ha tutti i suoi nodi accessibili e accessibili). Pertanto voterei per un duplice approccio.

Principio uno: Fail Fast . Questo è un principio guida nella programmazione difensiva in generale, che sostiene la rilevazione di errori nella prima fase possibile. Aggiungerei Fail Hard all'equazione.

if (not bar) { abort(); }

Sfortunatamente, in un ambiente di produzione il guasto non è necessariamente la soluzione migliore. In questo caso, un'eccezione specifica può aiutare a uscire di lì in fretta, e lasciare che qualche gestore di alto livello riesca a cogliere il problema e gestirlo in modo appropriato (molto probabilmente logging e forgiare avanti con un nuovo caso).

Questo tuttavia non affronta il problema dei costosi test. Nei punti caldi, questi test possono costare troppo. In questo caso, è ragionevole abilitare il test solo nei build DEBUG.

Questo ci lascia con una soluzione semplice e carina:

  • SOFT_ASSERT(Cond_, Text_)
  • DEBUG_ASSERT(Cond_, Text_)

Dove i due macro sono definiti così:

 #ifdef NDEBUG
 #  define SOFT_ASSERT(Cond_, Text_)                                                \
        while (not (Cond_)) { throw Exception(Text_, __FILE__, __LINE__); }
 #  define DEBUG_ASSERT(Cond_, Text_) while(false) {}
 #else // NDEBUG
 #  define SOFT_ASSERT(Cond_, Text_)                                                \
        while (not (Cond_)) {                                                       \
            std::cerr << __FILE__ << '#' << __LINE__ << ": " << Text_ << std::endl; \
            abort();                                                                \
        }
 #  define DEBUG_ASSERT(Cond_, Text_) SOFT_ASSERT(Cond_, Text_)
 #endif // NDEBUG
    
risposta data 09.04.2012 - 19:34
fonte
0

Una citazione che ho sentito su questo è:

"Sii prudente in ciò che fai e liberale in ciò che accetti."

Che si riduce a seguire i contratti per gli argomenti quando si chiamano funzioni e si controllano tutti gli input prima di agire quando si scrivono le funzioni.

In definitiva dipende dal dominio. Se stai facendo un'API del sistema operativo, dovresti controllare ogni input, non fidarti di tutti i dati in entrata come validi prima di iniziare ad agire su di esso. Se stai facendo una libreria che altri possono usare, vai avanti, lascia che l'utente si covi da solo (OpenGL viene in mente per primo per qualche motivo sconosciuto).

EDIT: nel senso OO, sembrano esserci due approcci - uno che dice che un oggetto non dovrebbe mai essere malformato (tutti i suoi invarianti devono essere veri) per tutto il tempo in cui un oggetto è accessibile, e un altro approccio che ti dice avere un costruttore che non imposta tutti gli invarianti, quindi si imposta qualche altro valore e si ha una seconda funzione di inizializzazione che completa init.

In un certo senso mi piace il primo poiché non richiede conoscenze magiche o fa affidamento sulla documentazione corrente per sapere quali parti dell'inizializzazione il costruttore non fa.

    
risposta data 10.04.2012 - 05:06
fonte

Leggi altre domande sui tag