Domanda

Disclaimer:Sono un laico che attualmente sta imparando a programmare.Non ho mai fatto parte di un progetto, né scritto nulla di più lungo di circa 500 righe.

La mia domanda è:la programmazione difensiva viola il principio Non ripeterti?Supponendo che la mia definizione di programmazione difensiva sia corretta (avere la funzione chiamante convalidare l'input anziché il contrario), non sarebbe dannoso per il tuo codice?

Ad esempio, è brutto questo:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    foo(input); //doesn't the extra logic
    foo(input); //and potentially extra calls
    foo(input); //work against you?
}   

rispetto a questo:

int main()
{
    if (input == /*condition*/)
    {
        foo(input);
        foo(input);
        foo(input);
    }
}

Ancora una volta, da laico, non so quanto semplici affermazioni logiche contino contro di te per quanto riguarda le prestazioni, ma sicuramente la programmazione difensiva non fa bene al programma o all'anima.

È stato utile?

Soluzione

Tutto si riduce alla contratto l'interfaccia fornisce. Ci sono due diversi scenari per questo: ingressi e uscite

.

Gli ingressi - e con questo fondamentalmente media dei parametri alle funzioni -. Deve essere controllato con l'attuazione, come regola generale

Uscite - che sono risultati di ritorno -. Dovrebbe essere sostanzialmente attendibile dal chiamante, almeno a mio parere

Tutto questo è temperato da questa domanda: cosa succede se una delle parti rompe il contratto? Ad esempio, consente di dire che hai avuto un'interfaccia:

class A {
  public:
    const char *get_stuff();
}

e che il contratto specifica che una stringa nulla sarà mai restituito (sarà una stringa vuota nel peggiore dei casi), allora è sicuro di fare questo:

A a = ...
char buf[1000];
strcpy(buf, a.get_stuff());

Perché? Beh, se ti sbagli e il chiamato un valore nullo allora il programma andrà in crash. Questo è in realtà OK . Se qualche oggetto viola il suo contratto quindi in generale il risultato dovrebbe essere catastrofico.

Il rischio che si faccia a essere eccessivamente difensiva è che si scrive un sacco di codice non necessario (che può introdurre più bug), o che si potrebbe in realtà mascherare un grave problema di deglutizione un'eccezione che davvero non dovrebbe.

di circostanze corso può cambiare questo.

Altri suggerimenti

La violazione del principio DRY sembra che:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    if (input == /*condition*/)
    {
       foo(input);
       foo(input);
       foo(input);
    }
}

come si può vedere, il problema è che abbiamo lo stesso controllo due volte nel programma, quindi se cambia la condizione, dobbiamo modificarlo in due punti, e le probabilità sono che ci dimentichiamo di loro, causando un comportamento strano. DRY non significa "non eseguire lo stesso codice per due volte", ma "non scrivere lo stesso codice due volte"

Vorrei innanzitutto affermare che seguire ciecamente un principio è idealistico e SBAGLIATO.Devi ottenere ciò che desideri (ad esempio, la sicurezza della tua applicazione), che di solito è molto più importante della violazione di DRY.Le violazioni intenzionali dei principi sono molto spesso necessarie nella BUONA programmazione.

Un esempio:Eseguo doppi controlli in fasi importanti (ad es.LoginService: convalida prima l'input una volta prima di chiamare LoginService.Login e poi di nuovo all'interno), ma a volte tendo a rimuovere nuovamente quello esterno in seguito dopo essermi assicurato che tutto funzioni al 100%, in genere utilizzando test unitari.Dipende.

Tuttavia non mi agiterei mai per i doppi controlli delle condizioni.DIMENTICARLI completamente, d'altro canto, di solito è molto peggio :)

Credo che la programmazione difensiva diventa un po 'un rap cattivo, dal momento che alcune cose che sono tipo di indesiderabili, che includono il codice prolisso, e più significativamente, tappezzare sopra gli errori.

La maggior parte delle persone sembrano concordare sul fatto che un programma dovesse fallire veloce quando si verifica un errore, ma che i sistemi mission critical dovrebbero preferibilmente non mancano mai, e invece fanno di tutto per andare avanti di fronte a stati di errore.

C'è un problema con questa affermazione, naturalmente, Come può un programma, anche mission critical, continuare quando è in uno stato incoerente. Naturalmente non può, in realtà.

Quello che vogliamo è che il programma di prendere ogni passo ragionevole per fare la cosa giusta, anche se c'è qualcosa di strano sta succedendo. Allo stesso tempo, il programma dovrebbe lamentarsi, ad alta voce , ogni volta che incontra un tale stato strano. E nel caso si verifica un errore che è irrecuperabile, che di solito dovrebbe evitare l'emissione di un'istruzione HLT, piuttosto dovrebbe fallire con grazia, chiudendo i suoi sistemi di sicurezza o l'attivazione di un qualche sistema di backup, se disponibile.

Nel tuo esempio semplificato, sì, il secondo formato è probabilmente preferibile.

Tuttavia, che non si applica in realtà a programmi più grandi, più complessi e più realistici.

Perché non si sa mai in anticipo dove e come "foo" verrà utilizzato, è necessario proteggere foo convalidando l'input. Se l'ingresso viene convalidato dal chiamante (ad es. "Principale" nel tuo esempio), allora "principale" ha bisogno di conoscere le regole di convalida, e applicarle.

Nella programmazione del mondo reale, le regole di convalida di ingresso possono essere abbastanza complessa. Non è il caso di fare il chiamante conoscere tutte le regole di validazione e applicarle correttamente. Alcuni chiamante, da qualche parte, sta andando a dimenticare le regole di convalida, o fare quelle sbagliate. Quindi, è meglio mettere la convalida dentro "foo", anche se verrà chiamato più volte. Questo sposta l'onere dal chiamante al chiamato, che libera il chiamante a pensare meno circa i dettagli di "foo", e usarlo più come un'interfaccia astratta affidabile.

Se avete veramente un modello in cui "foo" verrà chiamato più volte con lo stesso ingresso, mi permetto di suggerire una funzione wrapper che fa la convalida una volta, e una versione non protetta quel lato-passi la convalida:

void RepeatFoo(int bar, int repeatCount)
{
   /* Validate bar */
   if (bar != /*condition*/)
   {
       //code, assert, return, etc.
   }

   for(int i=0; i<repeatCount; ++i)
   {
       UnprotectedFoo(bar);
   }
}

void UnprotectedFoo(int bar)
{
    /* Note: no validation */

    /* do something with bar */
}

void Foo(int bar)
{
   /* Validate bar */
   /* either do the work, or call UnprotectedFoo */
}

Come Alex ha detto, dipende dalla situazione, per esempio, ho quasi sempre convalidare ingresso in ogni fase del processo di login.

In altri luoghi, non hai bisogno di tutto questo.

Tuttavia, nel esempio che ha dato, sto assumendo, nel secondo esempio, che si dispone di più di un ingresso, perche' altrimenti sarà superfluo chiamare la stessa funzione 3 volte per lo stesso ingresso che significa 'll deve scrivere la condizione per 3 volte. Ora che è ridondante.

Se l'ingresso SEMPRE deve essere controllato solo includerlo nella funzione.

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