Best practice di programmazione difensiva preferita (intelligente) [chiuso]


148

Se dovessi scegliere le tue tecniche preferite (intelligenti) per la codifica difensiva, quali sarebbero? Sebbene le mie lingue attuali siano Java e Objective-C (con un background in C ++), sentiti libero di rispondere in qualsiasi lingua. L'enfasi qui sarebbe sulle tecniche difensive intelligenti diverse da quelle che il 70% + di noi qui già conosce. Quindi ora è il momento di scavare in profondità nella tua borsa di trucchi.

In altre parole, prova a pensare ad altro che a questo esempio poco interessante :

  • if(5 == x) anziché if(x == 5) : per evitare incarichi non intenzionali

Ecco alcuni esempi di alcuni intriganti migliori pratiche di programmazione difensiva (esempi lingua-specifici sono in Java):

- Blocca le variabili fino a quando non sai che è necessario modificarle

Cioè, puoi dichiarare tutte le variabili finalfino a quando non sai che dovrai cambiarle, a quel punto puoi rimuovere il final. Un fatto comunemente sconosciuto è che questo è valido anche per i parametri del metodo:

public void foo(final int arg) { /* Stuff Here */ }

- Quando succede qualcosa di brutto, lascia dietro di sé una scia di prove

Ci sono molte cose che puoi fare quando hai un'eccezione: ovviamente registrarlo ed eseguire un po 'di pulizia sarebbe un paio. Ma puoi anche lasciare una scia di prove (ad esempio impostare variabili su valori sentinella come "IMPOSSIBILE CARICARE IL FILE" o 99999 sarebbe utile nel debugger, nel caso in cui ti catchimbatti in un blocco di eccezioni ).

- Quando si tratta di coerenza: il diavolo è nei dettagli

Sii coerente con le altre librerie che stai utilizzando. Ad esempio, in Java, se si sta creando un metodo che estrae un intervallo di valori, rendono compreso il limite inferiore e il limite superiore esclusivi . Ciò lo renderà coerente con metodi come quelli String.substring(start, end)che funzionano allo stesso modo. Troverai tutti questi tipi di metodi nel Sun JDK per comportarsi in questo modo in quanto esegue varie operazioni tra cui l'iterazione di elementi coerenti con gli array, in cui gli indici vanno da Zero ( incluso ) alla lunghezza dell'array ( esclusivo ).

Quali sono le tue pratiche difensive preferite?

Aggiornamento: se non lo hai già fatto, sentiti libero di intervenire. Sto dando la possibilità che arrivino più risposte prima di scegliere la risposta ufficiale .


Vorrei rappresentare l'ignorante 30% di noi qui che potrebbe non conoscere le semplici tecniche. Qualcuno ha un collegamento alle tecniche "ovvie" che tutti dovrebbero conoscere come base?
elliot42


Perché dovresti scegliere una risposta ufficiale? Sembra solo totalmente non-amante.
bzlm,

Bene, quando si tratta di programmazione, anche l'intelligenza dovrebbe fare un passo indietro in onore della "regola del minimo stupore". Per me rompere con lo spirito di Stack Overflow di contrassegnare la risposta migliore violerebbe il protocollo Stack Overflow (quindi infrangere detta regola). A parte questo: mi piace la chiusura :-)
Ryan Delucchi,

Risposte:


103

In c ++, una volta mi piaceva ridefinire il nuovo in modo che fornisse un po 'di memoria in più per catturare gli errori post-recinzione.

Attualmente, preferisco evitare la programmazione difensiva a favore di Test Driven Development . Se si rilevano errori rapidamente ed esternamente, non è necessario confondere il codice con manovre difensive, il codice è DRY -er e si finisce con meno errori che si devono difendere.

Come ha scritto WikiKnowledge :

Evita invece la programmazione difensiva, fallisci velocemente.

Per programmazione difensiva intendo l'abitudine di scrivere codice che tenti di compensare qualche errore nei dati, di scrivere codice che presume che i chiamanti possano fornire dati non conformi al contratto tra chiamante e subroutine e che la subroutine deve in qualche modo far fronte con esso.


8
La programmazione difensiva sta tentando di affrontare le condizioni illegali introdotte da altre parti di un programma. Gestire un input dell'utente non corretto è una cosa completamente diversa.
Joe Soul-portatore

5
Errr ... nota che questa definizione di programmazione difensiva non è nemmeno vicina alla definizione implicitamente usata nella domanda.
Sol,

15
Non evitare mai la programmazione difensiva. La cosa non è "compensare" i guasti nei dati, ma proteggersi da dati dannosi progettati per far sì che il codice faccia cose che non dovrebbe fare. Vedere Overflow buffer, Iniezione SQL. Niente fallisce più velocemente di una pagina Web sotto XSS ma non è carino
Jorge Córdoba,

6
Direi che le procedure "fail fast" sono una forma di programmazione difensiva.
Ryan Delucchi,

6
@ryan ha esattamente ragione, fallire velocemente è un buon concetto difensivo. Se lo stato in cui ti trovi non è possibile, non cercare di continuare a zoppicare, FAIL FAST AND LOUD! Molto importante se sei guidato da meta-dati. La programmazione difensiva non sta solo controllando i tuoi parametri ...
Bill K

75

SQL

Quando devo cancellare i dati, scrivo

select *    
--delete    
From mytable    
Where ...

Quando lo eseguirò, saprò se ho dimenticato o pasticciato la clausola where. Ho una sicurezza. Se tutto va bene, metto in evidenza tutto dopo i token di commento '-' ed eseguo.

Modifica: se sto eliminando molti dati, userò count (*) anziché solo *


L'ho scritto sul mio iPhone, dove non riesco a selezionare il testo. Se qualcuno potesse contrassegnare il mio codice come codice, sarebbe molto apprezzato. Grazie.
John MacIntyre,

6
Lo avvolgo in una transazione in modo da poter tornare indietro se
sbaglio

36
INIZIA TRANSAZIONE | TRANSAZIONE ROLLBACK
Dalin Seivewright,

3
+1 Sì, suppongo che questo potrebbe essere sostituito con una transazione, ma mi piace la semplicità di farlo in questo modo.
Ryan Delucchi,

3
Usare una transazione è meglio; significa che puoi eseguirlo dozzine di volte e vedere gli effetti reali , fino a quando non sei sicuro di aver capito bene e di impegnarti.
Ryan Lundy,

48

Allocare una porzione ragionevole di memoria all'avvio dell'applicazione - Penso che Steve McConnell si riferisse a questo come un paracadute di memoria in Code Complete.

Questo può essere usato nel caso in cui qualcosa di grave vada storto e ti viene richiesto di terminare.

Allocare questa memoria in anticipo fornisce una rete di sicurezza, in quanto è possibile liberarla e quindi utilizzare la memoria disponibile per effettuare le seguenti operazioni:

  • Salva tutti i dati persistenti
  • Chiudi tutti i file appropriati
  • Scrivi messaggi di errore in un file di registro
  • Presenta un errore significativo all'utente

Ho visto questo chiamato un fondo di giorno di pioggia.
plinto

42

In ogni istruzione switch che non ha un caso predefinito, aggiungo un caso che interrompe il programma con un messaggio di errore.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}

2
O un tiro in una moderna lingua confusa.
Tom Hawtin - affronta il

2
Assert ha il vantaggio che i suoi effetti possono essere disabilitati a livello globale in fase di compilazione. Tuttavia, in alcune situazioni il tiro può essere più appropriato, se la tua lingua lo supporta.
Diomidis Spinellis,

2
Assert ha un altro vantaggio che lo rende utile per il codice di produzione: quando qualcosa va storto, ti dice esattamente cosa è fallito e da quale riga del programma è venuto l'errore. Una segnalazione di bug come quella è meravigliosa!
Mason Wheeler,

2
@Diomidis: un altro punto di vista è: Assert ha lo svantaggio che i suoi effetti possono essere disabilitati globalmente in fase di compilazione.
Disilluso il

1
lanciare è davvero meglio. E poi ti assicuri che la copertura del test sia adeguata.
Dominic Cronin,

41

Quando gestisci i vari stati di un enum (C #):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Quindi, all'interno della routine ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

Ad un certo punto aggiungerò un altro tipo di account a questa enum. E quando lo farò, dimenticherò di correggere questa istruzione switch. Quindi gli Debug.Failarresti anomali (in modalità Debug) attirano la mia attenzione su questo fatto. Quando aggiungo case AccountType.MyNewAccountType:, l'orribile arresto si interrompe ... fino a quando non aggiungo un altro tipo di account e dimentico di aggiornare i casi qui.

(Sì, il polimorfismo è probabilmente meglio qui, ma questo è solo un esempio dalla parte superiore della mia testa.)


4
La maggior parte dei compilatori è abbastanza intelligente da emettere un avviso se non gestisci alcuni enum in un case case. Ma impostare il default come fallito è comunque buono - un enum è solo un numero e se si ottiene un danneggiamento della memoria è possibile che appaia un valore non valido.
Adam Hawes,

in c, alla fine aggiungevo un invalidAccountType. questo è utile a volte.
Ray Tayek,

1
@Adam: i compilatori emetteranno un avviso se si ricompila tutto . Se aggiungi nuove classi e ricompili solo parzialmente, potresti non notare qualcosa di simile al precedente e il caso predefinito ti salverà. Non fallirà silenziosamente.
Eddie,

4
La pausa è implicita nei commenti, Slashene. : P
Ryan Lundy,

2
Dopo il debug dovrebbe essere un 'lancio nuovo NotSupportedException ()' per il codice di produzione.
user7116

35

Quando stampo messaggi di errore con una stringa (in particolare uno che dipende dall'input dell'utente), utilizzo sempre virgolette singole ''. Per esempio:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

Questa mancanza di virgolette %sè davvero negativa, perché dire il nome del file è una stringa vuota o solo uno spazio bianco o qualcosa del genere. Il messaggio stampato sarebbe ovviamente:

ERROR: Could not open file

Quindi, sempre meglio fare:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Quindi almeno l'utente vede questo:

ERROR: Could not open file ''

Trovo che ciò faccia un'enorme differenza in termini di qualità delle segnalazioni di bug inviate dagli utenti finali. Se c'è un messaggio di errore dall'aspetto divertente come questo invece di qualcosa di generico, allora è molto più probabile che lo copi / incolli invece di scrivere semplicemente "non aprire i miei file".


4
buono, ho visto anche questo problema
Alex Baranosky,

28

Sicurezza SQL

Prima di scrivere qualsiasi SQL che modificherà i dati, avvolgo il tutto in una transazione rollback:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

Questo ti impedisce di eseguire definitivamente una cancellazione / aggiornamento errato. Inoltre, puoi eseguire tutto e verificare conteggi ragionevoli dei record o aggiungere SELECTistruzioni tra il tuo SQL e il ROLLBACK TRANSACTIONper assicurarti che tutto appaia corretto.

Quando sei completamente sicuro che fa quello che ti aspettavi, cambia ROLLBACKin COMMITe corri per davvero.


Mi hai battuto sul pugno su questo! :-)
Howard Pinsley,

2
Lo facevo sempre, ma causa un sovraccarico extra sui cluster DB che ho dovuto interrompere.
Zan Lynx,

25

Per tutte le lingue:

Ridurre l'ambito delle variabili al minimo possibile. Evita le variabili che sono appena fornite per portarle nella dichiarazione successiva. Le variabili che non esistono sono variabili che non è necessario comprendere e di cui non si può essere ritenuti responsabili. Usa Lambdas quando possibile per lo stesso motivo.


5
Cosa significa esattamente la parte evitativa? A volte introduco variabili che vivono solo fino alla riga successiva. Servono come nome per un'espressione, il che rende il codice più leggibile.
zoul

4
Sì, anche io non sono d'accordo. Con espressioni molto complesse è spesso una buona idea scomporle in due o talvolta più brevi e più semplici usando variabili temporanee. È meno soggetto a errori nella manutenzione e il compilatore ottimizzerà le temp
Cruachan,

1
Sono d'accordo che ci sono casi eccezionali in cui dichiaro variabili per chiarezza (e talvolta per creare un obiettivo per il debug). La mia esperienza è che la pratica generale è quella di errare nella direzione opposta.
dkretz,

Sono d'accordo con entrambi i punti di vista; sebbene quando divido le espressioni in variabili temporanee, provo a farlo in un ambito separato. Le lambda sono ottime per questo, così come i metodi di aiuto.
Erik Forbes,

19

In caso di dubbio, bombardare l'applicazione!

Controlla ogni singolo parametro all'inizio di ogni singolo metodo (sia che tu lo codifichi esplicitamente da solo, o utilizzando la programmazione basata su contratto non contenga qui) e bombarda con l'eccezione corretta e / o un messaggio di errore significativo se qualsiasi condizione preliminare al codice è non soddisfatti.

Conosciamo tutti questi presupposti impliciti quando scriviamo il codice , ma se non vengono controllati esplicitamente, creiamo labirinti per noi stessi quando qualcosa va storto in seguito e pile di dozzine di chiamate di metodo separano l'occorrenza del sintomo e l'ubicazione effettiva dove non è soddisfatta una condizione preliminare (= dove si trova effettivamente il problema / bug).


E ovviamente: usa il codice generico (una piccola libreria, metodi di estensione in C #, qualunque cosa) per renderlo facile. Quindi puoi scrivere qualcosa di simile param.NotNull("param")invece diif ( param == null ) throw new ArgumentNullException("param");
peSHIr

2
Oppure usa la programmazione basata su contratto, come Spec #!
bzlm,

Dipende dall'applicazione che è. Spero che la gente che scrive codice per velivoli fly-by-wire e pacemaker non pensi come peSHIr.
MarkJ,

6
@MarkJ: Davvero non capisci, vero? Se bombarda presto (= durante lo sviluppo e il test) non dovrebbe mai bombardare quando è in produzione. Così Spero davvero che fanno programma come questo!
peSHIr,

Devo ammettere che non mi piace molto, specialmente per i metodi privati ​​e protetti. Motivi: a) ingombrare il codice con controlli che non assomigliano affatto ai requisiti aziendali b) quei controlli sono difficili da testare per metodi non pubblici c) è inutile in molti casi, ad es. Poiché un valore nullo farà fallire il metodo comunque due righe dopo
Erich Kitzmueller,

18

In Java, in particolare con le raccolte, utilizzare l'API, quindi se il metodo restituisce il tipo Elenco (ad esempio), provare quanto segue:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Non permettere a nulla di sfuggire alla tua classe che non è necessario!


+1 In C # ci sono raccolte di sola lettura per questo.
tobsen,

1
+1 per Java. Lo uso sempre
Fortyrunner,

+1 ... Lo faccio molto (anche se vorrei che più persone si ricordassero di farlo).
Ryan Delucchi,

Assicurati solo che nient'altro abbia un'istanza della variabile lista sottostante, o questo non ti farà molto bene ...
GreenieMeanie

5
Cordiali saluti, Collections.unmodifiableList restituisce una vista immutabile dell'elenco, non una copia immutabile . Quindi, se l'elenco originale viene modificato, lo sarà anche la vista!
Zarkonnen,

17

in Perl, lo fanno tutti

use warnings;

mi piace

use warnings FATAL => 'all';

Ciò causa la morte del codice per qualsiasi avviso di compilatore / runtime. Questo è utile soprattutto per catturare stringhe non inizializzate.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here

Vorrei che fosse un po 'più pubblicizzato ...
DJG

16

C #:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}

Lo stesso in Java, e probabilmente nella maggior parte delle lingue OO.
MiniQuark,

Questo è utile in Objective-C, dove è possibile inviare messaggi a zero ("metodi di chiamata di un oggetto null", con una certa licenza). La chiamata di [nil isEqualToString: @ "Moo"] restituisce false.
zoul

Non sono d'accordo con l'esempio C #. Una soluzione migliore è usare "if (myString ==" someValue ")". Inoltre, nessuna eccezione di riferimento null e sicuramente più leggibile.
Dan C.

13
questo esempio ti sta solo permettendo di nascondere una situazione potenzialmente pericolosa nel tuo programma. Se non ti aspettavi che fosse null, VUOI lanciare un'eccezione e se ti aspettavi che fosse null, dovresti gestirlo come tale. Questa è una cattiva pratica.
rmeador,

2
In C #, utilizzo sempre solo string.Equals (<stringa1>, <stringa2>). Non importa se nessuno dei due è nullo.
Darcy Casselman,

15

Nel controllo c # di string.IsNullOrEmpty prima di eseguire qualsiasi operazione sulla stringa come lunghezza, indexOf, metà ecc

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[Modifica]
Ora posso anche fare quanto segue in .NET 4.0, che controlla inoltre se il valore è solo uno spazio

string.IsNullOrWhiteSpace(myString)

7
Non è difensivo, sta ignorando il problema. Dovrebbe essere if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
enashnash,

14

In Java e C #, assegna a ogni thread un nome significativo. Ciò include i thread del pool di thread. Rende i dump dello stack molto più significativi. Ci vuole un po 'più di sforzo per dare un nome significativo anche ai thread del pool di thread, ma se un pool di thread ha un problema in un'applicazione a esecuzione prolungata, posso causare un dump dello stack (sai di SendSignal.exe , giusto? ), afferrare i log e senza dover interrompere un sistema in esecuzione posso dire quali thread sono ... qualunque cosa. Impasse, perdite, crescita, qualunque sia il problema.


E - su Windows - anche per C ++! (abilitato con il lancio dell'eccezione SEH speciale e successiva cattura).
Brian Haak,

12

Con VB.NET, l'opzione Opzione esplicita e Opzione rigorosa sono attivate per impostazione predefinita per l'intero Visual Studio.


Anche se sto lavorando con un codice più vecchio, quindi non ho l'opzione rigorosa attivata (causa troppi errori del compilatore da correggere), avere entrambe queste opzioni attivate può (e avrebbe nel mio caso) salvato molto cuore dolore.
Kibbee,

1
A proposito, per chiunque stia convertendo il vecchio codice che non ha usato Option Strict per usarlo, nota che nel caso di elementi che sono stati convertiti automaticamente in String, non stanno usando ToString (); stanno usando un cast per la stringa. Nei miei primi giorni di .NET li avrei cambiati per usare ToString (), e avrebbe rotto le cose, specialmente con enum.
Ryan Lundy,

10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

quindi sostituisci tutte le chiamate " delete pPtr " e " delete [] pPtr " con SAFE_DELETE (pPtr) e SAFE_DELETE_ARRAY (pPtr)

Ora per errore se si utilizza il puntatore 'pPtr' dopo averlo eliminato, si otterrà l'errore di 'violazione dell'accesso'. È molto più facile da correggere rispetto ai danni alla memoria casuali.


1
Meglio usare un modello. È mirato e sovraccarico.

Stavo per dirlo. Utilizzare un modello anziché una macro. In questo modo, se mai hai bisogno di scorrere il codice, puoi farlo.
Steve Rowe,

Ho imparato quale è stato più facile eseguire il debug nel modo più difficile tornare a scuola. È un errore che non faccio più da allora. :)
Greg D,

3
O usa i puntatori intelligenti ... O diamine, evita il nuovo / elimina il più possibile comunque.
Arafangion,

1
@Arafangion: evita delete. L'uso newva bene, purché il nuovo oggetto sia di proprietà di un puntatore intelligente.
Alexandre C.,

10

Con Java, può essere utile utilizzare la parola chiave assert, anche se si esegue il codice di produzione con le asserzioni disattivate:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

Anche con asserzioni disattivate, almeno il codice documenta il fatto che param dovrebbe essere impostato da qualche parte. Si noti che questa è una funzione di supporto privata e non un membro di un'API pubblica. Questo metodo può essere chiamato solo da te, quindi è OK fare alcune ipotesi su come verrà utilizzato. Per i metodi pubblici, probabilmente è meglio gettare una vera eccezione per input non validi.


In .NET, Debug.Assert fa la stessa cosa. Ogni volta che hai un posto in cui pensi "Questo riferimento non può essere nullo qui, giusto?", Puoi inserire un Debug.Assert lì, in modo che se può essere nullo, puoi correggere il bug o cambiare i tuoi presupposti.
Ryan Lundy,

1
+1, i metodi pubblici dovrebbero far
fallire i

9

Non ho trovato la readonlyparola chiave fino a quando non ho trovato ReSharper, ma ora la utilizzo istintivamente, soprattutto per le classi di servizio.

readonly var prodSVC = new ProductService();

Uso la parola chiave "finale" equivalente di Java su tutti i campi che posso. Ti risparmia davvero dal fare mosse di ossa come non impostare un campo / scrivere interruttori confusi che possono impostare campi più volte. Ho meno probabilità di contrassegnare le variabili / i parametri locali ma immagino che non possa far male.
Programmatore fuorilegge il

C # non ti consente di contrassegnare le variabili locali come di sola lettura, quindi non abbiamo nemmeno la scelta ...
Jason Punyon

Non sono sicuro di cosa significhi solo qui, ma il finale di Java non è abbastanza per me. Significa solo che il puntatore a un oggetto è invariato, ma non c'è modo di impedire il cambiamento sull'oggetto stesso.
Slartibartfast,

In C # una struttura di sola lettura impedirebbe che venga mai cambiata.
Samuel,

9

In Java, quando succede qualcosa e non so perché, a volte userò Log4J in questo modo:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

in questo modo ottengo una traccia dello stack che mi mostra come sono entrato in una situazione inaspettata, ad esempio un lucchetto che non è mai stato sbloccato, qualcosa di nullo che non può essere nullo e così via. Ovviamente, se viene generata una vera eccezione, non ho bisogno di farlo. Questo è quando ho bisogno di vedere cosa sta succedendo nel codice di produzione senza effettivamente disturbare nient'altro. Io non voglio gettare un'eccezione e non ho capito una. Voglio solo una traccia dello stack registrata con un messaggio appropriato per segnalarmi ciò che sta accadendo.


Hmm, questo è un po 'pulito ma temo che causerebbe un po' di mix di codice funzionale e di gestione degli errori. Mentre il meccanismo try-catch può essere un po 'goffo, costringe uno a fare eccezioni reindirizzare l'esecuzione da un blocco (try) a un altro (catch), che è dove si trova tutta la gestione degli errori
Ryan Delucchi,

1
Questo non è usato per la gestione degli errori, ma solo per la diagnostica . Ciò ti consente di vedere il percorso attraverso il quale il tuo codice è entrato in una situazione imprevista senza interrompere il flusso del codice. Lo uso quando il metodo stesso è in grado di gestire la situazione inaspettata, ma comunque non dovrebbe accadere.
Eddie,

2
puoi anche usare la nuova eccezione ("messaggio"). printStackTrace (); Non è necessario lanciare o catturare, ma nel registro viene comunque visualizzata una bella stack stack. Ovviamente, questo non dovrebbe essere nel codice di produzione, ma può essere molto utile per il debug.
Jorn

9

Se si utilizza Visual C ++, utilizzare la parola chiave override ogni volta che si supera il metodo di una classe base. In questo modo, se qualcuno dovesse mai cambiare la firma della classe base, genererà un errore del compilatore anziché il metodo errato che viene chiamato in silenzio. Questo mi avrebbe salvato alcune volte se fosse esistito prima.

Esempio:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}

per Java, questa è la classe Foo {void doSomething () {}} classe Bar {@Override void doSomething () {}} Fornirà un avviso se manca l'annotazione (quindi non puoi ignorare silenziosamente un metodo che non hai significa) e quando l'annotazione è presente ma non sostituisce nulla.
Jorn

Bello ... non sapevo di questo ...
peccato che

8

In Java ho imparato a non aspettare quasi mai all'infinito che un blocco si sblocchi, a meno che non mi aspetti davvero che potrebbe richiedere un tempo indefinitamente lungo. Se realisticamente, il blocco dovrebbe sbloccarsi in pochi secondi, quindi aspetterò solo per un certo periodo di tempo. Se il blocco non si sblocca, allora mi lamento e scarico stack nei registri e, a seconda di ciò che è meglio per la stabilità del sistema, o continuare come se il blocco fosse sbloccato o continuare come se il blocco non si fosse mai sbloccato.

Ciò ha contribuito a isolare alcune condizioni di gara e pseudo-deadlock che erano misteriose prima che iniziassi a farlo.


1
l'attesa con timeout usati in questo modo è un segno di altri, peggio, problemi.
dicroce,

2
In un sistema che deve avere un elevato tempo di attività, è meglio almeno ottenere informazioni diagnostiche in modo da poter trovare il problema. A volte preferisci uscire da un thread o restituire una risposta al chiamante quando il sistema è in uno stato noto, quindi attendi un semaforo. Ma non vuoi restare appeso per sempre
Eddie,

8

C #

  • Verificare i valori non nulli per i parametri del tipo di riferimento nel metodo pubblico.
  • Uso sealedmolto le lezioni per evitare di introdurre dipendenze dove non le volevo. Consentire l'eredità dovrebbe essere fatto esplicitamente e non per caso.

8

Quando si emette un messaggio di errore, almeno provare a fornire le stesse informazioni del programma quando ha preso la decisione di lanciare un errore.

"Autorizzazione negata" indica che si è verificato un problema di autorizzazione, ma non hai idea del perché o del luogo in cui si è verificato il problema. "Impossibile scrivere il registro delle transazioni / mio / file: filesystem di sola lettura" ti consente almeno di conoscere la base su cui è stata presa la decisione, anche se è sbagliata, soprattutto se è sbagliata: nome del file errato? aperto male? altro errore imprevisto? - e ti fa sapere dove ti trovavi quando hai avuto il problema.


1
Quando scrivi il codice per un messaggio di errore, leggi il messaggio e chiedi cosa vorresti sapere dopo , quindi aggiungilo. Ripeti fino a quando non è ragionevole. Ad esempio: "Fuori portata". Cosa è fuori portata? "Foo conta fuori portata." Qual era il valore? "Foo count (42) fuori portata." Qual è la gamma? "Conteggio Foo (42) fuori portata (da 549 a 666)."
HABO,

7

In C #, usa la asparola chiave per trasmettere.

string a = (string)obj

genererà un'eccezione se obj non è una stringa

string a = obj as string

lascerà un as null se obj non è una stringa

È comunque necessario prendere in considerazione null, ma in genere è più semplice rispetto alla ricerca di eccezioni di cast. A volte si desidera un comportamento di tipo "cast o blow up", nel qual caso (string)objsi preferisce la sintassi.

Nel mio codice, trovo che uso la assintassi circa il 75% delle volte e la (cast)sintassi circa il 25%.


Corretto, ma ora è necessario verificare la presenza di null prima di utilizzare il riferimento.
Brian Rasmussen,

7
Non l'ho capito. Mi sembra una decisione sbagliata, preferire il nulla. Avrai problemi da qualche parte durante il runtime senza alcun suggerimento per il motivo originale.
Kai Huppmann,

Sì. Ciò è utile solo se si desidera effettivamente null quando non è il tipo corretto. Utile in alcuni casi: in Silverlight dove la logica e il design del controllo sono spesso separati, la logica vuole usare il controllo "Up" solo se si tratta di un pulsante. In caso contrario, è come se non esistesse (= null).
Sander,

2
Sembra difensivo quanto le grandi finestre della sala da ballo di una fortezza. Ma è una vista incantevole!
Pontus Gagge,

1
Questo mi ricorda qualcuno che non ha usato le eccezioni perché "hanno rotto i programmi". Se vuoi un determinato comportamento quando un oggetto non è una classe che ti aspetti, penso che lo scriverei in un altro modo. is/instanceofmi viene in mente.
Kirschstein,

6

Preparati a qualsiasi input e qualsiasi input che ricevi sia inaspettato, scarica nei log. (Entro limiti ragionevoli. Se stai leggendo le password dell'utente, non scaricarle nei registri! E non registrare migliaia di questi tipi di messaggi nei registri al secondo. Motivo del contenuto, della probabilità e della frequenza prima di registrarlo .)

Non sto solo parlando della convalida dell'input dell'utente. Ad esempio, se stai leggendo richieste HTTP che prevedi di contenere XML, preparati per altri formati di dati. Sono stato sorpreso di vedere le risposte HTML dove mi aspettavo solo XML - fino a quando non ho visto e visto che la mia richiesta stava attraversando un proxy trasparente di cui non ero a conoscenza e che il cliente ha affermato di ignorare - e il proxy è scaduto nel tentativo di completare il richiesta. Quindi il proxy ha restituito una pagina di errore HTML al mio client, confondendo il diavolo dal client che si aspettava solo dati XML.

Pertanto, anche quando pensi di controllare entrambe le estremità del filo, puoi ottenere formati di dati imprevisti senza che sia coinvolto alcun malvagio. Preparati, codifica in modo difensivo e fornisci un output diagnostico in caso di input imprevisto.


6

Cerco di usare l'approccio Design by Contract. Può essere emulato il tempo di esecuzione in qualsiasi lingua. Ogni lingua supporta "assert", ma è facile e conveniente scrivere un'implementazione migliore che ti consenta di gestire l'errore in un modo più utile.

Nei 25 errori di programmazione più pericolosi, la "Convalida errata degli input" è l'errore più pericoloso nella sezione "Interazione insicura tra componenti".

L'aggiunta di asserzioni preliminari all'inizio dei metodi è un buon modo per assicurarsi che i parametri siano coerenti. Alla fine dei metodi scrivo postcondizioni , che controllano che l'output sia ciò che si intende essere.

Per implementare gli invarianti , scrivo un metodo in qualsiasi classe che controlla la "coerenza delle classi", che dovrebbe essere chiamato automaticamente dalla macro precondizione e postcondizione.

Sto valutando la libreria dei contratti di codice .


6

Giava

Java Java non ha alcun concetto di oggetti immutabili, il che è male! Final può aiutarti in quel caso. Contrassegna ogni classe immutabile con final e prepara la classe di conseguenza .

A volte è utile usare final sulle variabili locali per assicurarsi che non cambino mai il loro valore. L'ho trovato utile in costrutti loop brutti, ma necessari. È troppo facile riutilizzare accidentalmente una variabile anche se può essere una costante.

Usa la copia di difesa nei tuoi getter. A meno che non si restituisca un tipo primitivo o un oggetto immutabile, assicurarsi di copiare l'oggetto per non violare l'incapsulamento.

Non usare mai clone, usare un costruttore di copie .

Scopri il contratto tra equals e hashCode. Questo è violato così spesso. Il problema è che non influisce sul codice nel 99% dei casi. Le persone sovrascrivono sono uguali, ma non importa dell'hashCode. Esistono casi in cui il codice può rompersi o comportarsi in modo strano, ad esempio utilizzare oggetti mutabili come chiavi in ​​una mappa.


5

Ho dimenticato di scrivere echoin PHP troppe volte:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

Mi ci vorrebbe un'eternità per provare a capire perché -> baz () non stava restituendo nulla quando in realtà non l'ho fatto eco! : -S Così ho creato una EchoMeclasse che potrebbe essere racchiusa in qualsiasi valore che dovrebbe essere ripetuto:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

E poi per l'ambiente di sviluppo, ho usato un EchoMe per avvolgere le cose che dovrebbero essere stampate:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Usando quella tecnica, il primo esempio mancante echoora genererebbe un'eccezione ...


Il distruttore non dovrebbe controllare $ this-> stampato! == vero?
Karsten,

Dovresti prendere in considerazione l'uso di un sistema di template, l'incorporazione di php in html non è ottimale su quasi tutti i sistemi.
Cruachan,

Forse mi manca qualcosa? Ma mi sembra che questo stia cercando di compensare la codifica: AnObject.ToStringinvece di Writeln(AnObject.ToString)?
Disilluso il

Sì, ma l'errore è molto più facile da fare in PHP
troppo php il

4

C ++

Quando digito new, devo immediatamente digitare delete. Soprattutto per gli array.

C #

Controllare null prima di accedere alle proprietà, specialmente quando si utilizza il modello Mediatore. Gli oggetti vengono passati (e quindi devono essere lanciati usando as, come è già stato notato), quindi controllare con un valore null. Anche se pensi che non sarà nullo, controlla comunque. Sono stato sorpreso


Mi piace il tuo primo punto. Faccio cose simili quando, ad esempio, scrivo un metodo che restituisce una raccolta di qualcosa. Creo la raccolta sulla prima riga e scrivo immediatamente la dichiarazione di ritorno. Non resta che compilare il modo in cui viene popolata la raccolta.
Programmatore fuorilegge il

5
In C ++, quando si digita nuovo, è necessario assegnare immediatamente quel puntatore a un AutoPtr o a un contenitore conteggio dei riferimenti. C ++ ha distruttori e modelli; usali saggiamente per gestire automaticamente la cancellazione.
An̲̳̳drew

4

Utilizzare un sistema di registrazione che consenta regolazioni dinamiche del livello di registro del tempo di esecuzione. Spesso se si deve interrompere un programma per abilitare la registrazione, si perde lo stato raro in cui si è verificato il bug. È necessario essere in grado di attivare ulteriori informazioni di registrazione senza interrompere il processo.

Inoltre, 'strace -p [pid]' su Linux mostrerà che vuoi che vengano fatte delle chiamate di sistema (o thread di Linux). All'inizio può sembrare strano, ma una volta che ti sarai abituato a ciò che le chiamate di sistema vengono generalmente fatte da ciò che libc chiama, lo troverai prezioso nella diagnosi sul campo.

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.