Domanda

Possibile duplicato:
Programmazione difensiva

Stamattina abbiamo avuto una grande discussione sul tema della programmazione difensiva.Abbiamo effettuato una revisione del codice in cui è stato passato un puntatore e non è stato verificato se fosse valido.

Alcune persone ritenevano che fosse necessario solo un controllo per il puntatore nullo.Mi sono chiesto se potesse essere controllato a un livello superiore, piuttosto che a ogni metodo attraverso il quale viene passato, e che il controllo di null era un controllo molto limitato se l'oggetto all'altra estremità del punto non soddisfaceva determinati requisiti.

Capisco e sono d'accordo che un controllo per null è meglio di niente, ma mi sembra che il controllo solo per null fornisca un falso senso di sicurezza poiché ha una portata limitata.Se vuoi assicurarti che il puntatore sia utilizzabile, controlla se c'è più del null.

Quali sono le vostre esperienze sull'argomento?Come si scrivono le difese nel codice per i parametri passati ai metodi subordinati?

È stato utile?

Soluzione

Nel codice completo 2, nel capitolo sulla gestione degli errori, mi è stato introdotto l'idea di barricate. In sostanza, una barricata è codice che convalida rigorosamente tutti gli input entrano in esso. Codice all'interno la barriera può assumere che qualsiasi input valido è già stato affrontato, e che gli ingressi ricevuti sono buone. All'interno della barricata, codice deve solo preoccuparsi di dati su validi passati ad esso da un altro codice all'interno della barricata. le condizioni e le unità di test giudizioso affermare può aumentare la vostra fiducia nel codice barricato. In questo modo, si programma molto difensiva alla barricata, ma meno all'interno della barricata. Un altro modo di pensare a questo proposito è che alla barricata, a gestire sempre gli errori in modo corretto, e dentro la barricata si limitarsi ad affermare le condizioni nella vostra build di debug.

Per quanto riguarda l'utilizzo di puntatori prime va, di solito il meglio che puoi fare è affermare che il puntatore non è nullo. Se si sa che cosa si suppone sia in quella memoria allora si potrebbe garantire che i contenuti sono coerenti in qualche modo. Ciò solleva la questione del perché che la memoria non è avvolto in un oggetto che può verificare è consistenza stessa.

Quindi, perché stai usando un puntatore RAW in questo caso? Sarebbe meglio usare un riferimento o un puntatore intelligente? Fa il puntatore contiene i dati numerici, e in tal caso, sarebbe meglio per avvolgere in su in un oggetto che ha gestito il ciclo di vita di quel puntatore?

Rispondere a queste domande può aiutare a trovare un modo per essere più difensiva, nel senso che si ritroverà con un design che è più facile da difendere.

Altri suggerimenti

Il modo migliore per essere sulla difensiva non è quello di controllare i puntatori per nulla in fase di esecuzione, ma a evitare l'uso di puntatori che può essere nullo per cominciare

Se l'oggetto viene passato nel mosto non essere nullo, utilizzare un riferimento! O passarlo per valore! Oppure utilizzare un puntatore intelligente di qualche tipo.

Il modo migliore per fare programmazione difensiva è quello di catturare i vostri errori a tempo di compilazione. Se si considera un errore per un oggetto per essere nullo o un punto di spazzatura, allora si dovrebbe fare quelle cose errori di compilazione.

In definitiva, si ha modo di sapere se un puntatore punta a un oggetto valido. Quindi, piuttosto che il controllo di una caso angolo specifico (che è molto meno comune rispetto a quelli realmente pericolosi, puntatori che punta a non validi gli oggetti), fanno l'errore impossibile utilizzando un tipo di dati che garantisce la validità.

Non riesco a pensare ad un altro linguaggio corrente principale che permette di catturare il maggior numero di errori al momento della compilazione come fa C ++. utilizzare questa capacità.

Non c'è modo per verificare se un puntatore è valido.

In tutto serio, dipende da quanti bug ti piacerebbe avere inflitto su di voi.

Verifica di un puntatore nullo è sicuramente qualcosa che vorrei prendere in considerazione necessario ma non sufficiente. Ci sono un sacco di altri solidi principi è possibile utilizzare a partire da punti di ingresso del codice (ad esempio, convalida dell'input = fa quel punto puntatore a qualcosa di utile) e di uscita (ad esempio, si pensava che il puntatore indicò qualcosa di utile, ma è successo a causa il codice per generare un'eccezione).

In breve, se si assume che tutti chiamando il codice sta per fare del loro meglio per rovinare la vostra vita, probabilmente troverete un sacco di peggiori colpevoli.

EDIT per chiarezza: alcune altre risposte stanno parlando di test di unità. Credo fermamente che il codice di test è a volte più di valore rispetto al codice che è il test (a seconda di chi sta misurando il valore). Detto questo, penso anche che i test di unità sono anche necessaria ma non sufficiente per la codifica difensiva.

Esempio concreto: prendere in considerazione un terzo metodo di ricerca partito che è documentato per restituire un insieme di valori che corrispondono alla tua richiesta. Purtroppo, ciò che non era chiaro nella documentazione che metodo è che lo sviluppatore originale ha deciso che sarebbe stato meglio tornare un valore nullo piuttosto che un insieme vuoto se non corrisponde la vostra richiesta.

Così ora, si chiama il proprio apparecchio collaudato metodo di pensiero difensivo e ben (che purtroppo è molto carente un controllo puntatore nullo interno) e boom! NullPointerException che, senza un controllo interno, non si ha modo di trattare con:

defensiveMethod(thirdPartySearch("Nothing matches me")); 
// You just passed a null to your own code.

Sono un grande fan del "lasciar crash" scuola di design. (Disclaimer: io non lavoro su attrezzature mediche, avionica, o il software di potere legati nucleare.) Se il programma fa saltare in aria, il fuoco il debugger e capire perché. Al contrario, se il programma continua a funzionare dopo che sono stati rilevati parametri illegali, per il momento si blocca probabilmente avete idea di cosa è andato storto.

buon codice è costituito da tante piccole funzioni / metodi, e l'aggiunta di una dozzina di righe di parametri-controllo per ognuno di quei frammenti di codice rende più difficile da leggere e più difficile da mantenere. Keep it simple.

Posso essere un po 'estrema, ma non mi piace di programmazione difensiva, penso che sia la pigrizia che ha introdotto il principio.

Per questo esempio, non v'è alcun senso affermare che il puntatore non è nullo. Se si desidera un puntatore nullo, non c'è modo migliore per far rispettare in realtà (e documentare chiaramente allo stesso tempo) piuttosto che utilizzare un riferimento, invece. Ed è la documentazione che sarà effettivamente applicata dal compilatore e non costa uno ziltch in fase di esecuzione !!

In generale, tendono a non utilizzare direttamente tipi 'prime'. Illustriamo:

void myFunction(std::string const& foo, std::string const& bar);

Quali sono i possibili valori di foo e bar? Bene che è praticamente limitato solo da ciò che un std::string può contenere ... che è piuttosto vaga.

D'altra parte:

void myFunction(Foo const& foo, Bar const& bar);

è molto meglio!

  • se la gente erroneamente invertire l'ordine degli argomenti, è rilevato dal compilatore
  • ogni classe è il solo responsabile di verificare che il valore è giusto, gli utenti non sono burdenned.

ho la tendenza a favorire tipizzazione forte. Se ho una voce che deve essere composto solo di caratteri alfabetici ed essere fino a 12 caratteri, preferirei creare una piccola classe avvolgendo un std::string, con un semplice metodo di validate utilizzato internamente per controllare le assegnazioni, e superare quella classe in giro, invece . In questo modo so che se provo la routine di convalida una volta, non c'è bisogno di preoccuparsi in realtà su tutti i percorsi attraverso i quali tale valore può arrivare a me> sarà convalidato quando mi raggiunge.

Naturalmente, questo non me che il codice non deve essere testato. E 'solo che sono favorevole forte incapsulamento, e validazione di un ingresso è parte della conoscenza incapsulamento a mio parere.

E come nessuna regola può venire senza interfaccia un'eccezione ... esposta è necessariamente gonfio con il codice di convalida, perché non si sa mai cosa potrebbe scenderà su di te. Tuttavia, con gli oggetti auto-validazione nel BOM è abbastanza trasparente, in generale.

"Prove di unità verifica il codice fa quello che dovrebbe fare"> "codice di produzione cercando di verificare il suo non fare ciò che la sua non è supposto per fare".

Non vorrei anche verificare la presenza di nulla me stesso, a meno che non parte di un'API pubblicato il suo.

Dipende molto; è il metodo in questione mai chiamato dal codice esterno al gruppo, o è un metodo interno?

Per i metodi interni, è possibile testare abbastanza per fare di questo un punto controverso, e se si sta costruendo codice in cui l'obiettivo è quello migliori prestazioni possibili, non si potrebbe desiderare di trascorrere il tempo sul controllo ingressi siete maledettamente sicuro hanno ragione.

Per i metodi visibili esternamente - se avete qualsiasi - si dovrebbe sempre controllare due ingressi. Sempre.

Dal punto di vista di debug, è più importante che il codice è fail-veloce. Quanto prima il codice non riesce, il più facile trovare il punto di errore.

Per i metodi interni, di solito atteniamo a asserisce per questi tipi di controlli. Ciò ottenere gli errori raccolti nel test di unità (si dispone di una buona copertura di test, giusto?) O almeno in test di integrazione che sono in esecuzione con le asserzioni su.

controllando il puntatore nullo È solo metà della storia, dovresti anche assegnare un valore nullo a ogni puntatore non assegnato.
l'API più responsabile farà lo stesso.
il controllo della presenza di un puntatore nullo costa molto in termini di cicli della CPU, avere un'applicazione che si arresta in modo anomalo una volta consegnata può costare a te e alla tua azienda in denaro e reputazione.

puoi saltare i controlli del puntatore nullo se il codice si trova in un'interfaccia privata di cui hai il controllo completo e/o controlli null eseguendo uno unit test o qualche test di build di debug (ad es.affermare)

Ci sono un paio di cose sul lavoro qui, in questa questione che vorrei affrontare:

  1. le linee guida di codifica dovrebbe specificare che si sia a che fare con un riferimento o un valore invece di utilizzare direttamente i puntatori. Per definizione, i puntatori sono tipi di valore che basta tenere un indirizzo nella memoria - (. Gamma di memoria indirizzabile, piattaforma, ecc) la validità di un puntatore è la piattaforma specifica e significa molte cose
  2. Se vi trovate mai bisogno di un puntatore per qualsiasi motivo (come per gli oggetti generati in modo dinamico e polimorfi) considerare l'utilizzo di puntatori intelligenti. puntatori intelligenti offrono molti vantaggi con la semantica di puntatori "normali".
  3. Se un tipo per esempio ha uno stato "non valido", allora il tipo di per sé dovrebbe prevedere questo. Più in particolare, è possibile implementare il modello NullObject che specifica come un "mal definita" o "non inizializzato" oggetto si comporta (forse gettando le eccezioni o fornendo funzioni membro no-op).

È possibile creare un puntatore intelligente che fa il default NullObject che assomiglia a questo:

template <class Type, class NullTypeDefault>
struct possibly_null_ptr {
  possibly_null_ptr() : p(new NullTypeDefault) {}
  possibly_null_ptr(Type* p_) : p(p_) {}
  Type * operator->() { return p.get(); }
  ~possibly_null_ptr() {}
  private:
    shared_ptr<Type> p;
    friend template<class T, class N> Type & operator*(possibly_null_ptr<T,N>&);
};

template <class Type, class NullTypeDefault>
Type & operator*(possibly_null_ptr<Type,NullTypeDefault> & p) {
  return *p.p;
}

Quindi utilizzare il modello possibly_null_ptr<> nei casi in cui si supporta i puntatori nulli possibilmente ai tipi che hanno un difetto derivato "comportamento null". Questo lo rende esplicito nel disegno che v'è un comportamento accettabile per "oggetti nulli", e questo rende la vostra pratica difensiva documentato nel codice - e più concreto -. Di una linea guida o pratica generale

Pointer deve essere utilizzato solo se avete bisogno di fare qualcosa con il puntatore. Come puntatori a spostamento qualche struttura dati. Quindi, se possibile, che dovrebbero essere incapsulati in una classe.

Se il puntatore viene passato alla funzione di fare qualcosa con l'oggetto a cui punta, poi passa un riferimento invece.

Un metodo per la programmazione difensiva è di affermare quasi tutto ciò che si può. All'inizio del progetto è fastidioso, ma in seguito è una buona aggiunta alla unit testing.

Un certo numero di indirizzo di rispondere alla domanda su come scrivere le difese nel codice, ma non molto è stato detto su "come difensiva si dovrebbe essere?". Questo è qualcosa che si deve valutare in base alla criticità dei componenti software.

Stiamo facendo software di volo e gli impatti di un intervallo di errore del software da un disturbo secondario alla perdita di aerei / equipaggio. Noi classifichiamo diversi pezzi di software in base alle loro potenziali impatti negativi che gli affetti di codifica standard, prove, ecc È necessario valutare come il software saranno utilizzati e gli impatti di errori e impostare il livello di atteggiamento difensivo si vuole (e può permettersi). Il DO-178B standard di chiama "Assurance Level Design".

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top