Un metodo dovrebbe validare i suoi parametri? [duplicare]

35

Supponiamo che tu stia progettando un metodo sqrt con radice quadrata. Preferisci convalidare che il parametro passato non sia un numero negativo o lo lasci al chiamante per assicurarti che il parametro passato sia valido. Come varia la tua risposta se il metodo / API è per il consumo di terze parti o se verrà utilizzato solo per la particolare applicazione su cui stai lavorando

Sono stato dell'opinione che un metodo dovrebbe validare il suo parametro, tuttavia, Pragmatic Programmer nella sua sezione Design by Contract (capitolo 4) dice che è responsabilità del chiamante passare i buoni dati (pg 111 e 115) e suggerisce di usare Assertions nel metodo per verificare lo stesso. Voglio sapere cosa ne pensano gli altri di questo.

    
posta Amit Wadhwa 04.04.2011 - 07:01
fonte

10 risposte

29

In generale, disegno le mie API come segue:
 1. Documentare bene i metodi e incoraggiare i chiamanti a trasmettere dati validi / validi.
 2. Convalidare i parametri comunque! - lancio di eccezioni quando le condizioni preliminari non sono soddisfatte.

Direi che la convalida dei parametri è necessaria sulla maggior parte delle API pubbliche. La convalida dei parametri su metodi non pubblici non è così importante - spesso è auspicabile che la convalida avvenga solo una volta, al punto di ingresso pubblico - ma se riesci a convivere con il potenziale impatto sulle prestazioni, mi piace convalidare i parametri ovunque, come rende la manutenzione e il refactoring del codice un po 'più semplice.

    
risposta data 04.04.2011 - 07:25
fonte
23

Se convalidi sempre i parametri, stai facendo del lavoro extra che potrebbe non essere necessario.

Pensa alla situazione in cui l'input è già stato convalidato prima della chiamata e ora stai riconvalidando i dati nella chiamata. OK, un ulteriore controllo di validazione è OK, ma ora estendi tale logica a tutte le funzioni dell'applicazione. Ogni chiamata di funzione convalida i dati anche se è stata convalidata in precedenza (ora più volte).

I dati devono essere convalidati al punto UNO . È qui che i dati entrano nel programma (o nel sottosistema (questo fornisce un margine di manovra in quanto la definizione di sottosistema può essere flessibile)). Se non ci sono errori di programmazione, il tuo codice dovrebbe ora funzionare (le asserzioni per il controllo del codice errato sono diverse dai parametri di validazione).

Se vuoi davvero convalidare i parametri, allora hai due versioni della funzione. Uno da convalidare e uno che non convalida. Guardare a std :: vector (l'operatore [] non convalida mentre at () convalida).

Quindi, se dovessi progettare una funzione sqrt (), sarebbe non convalidare i suoi input perché nella maggior parte delle situazioni i dati sarebbero comunque buoni e la piccola minoranza di situazioni in cui è potenzialmente sbagliato l'utente può fare un rapido controllo (come l'input dell'utente potrebbe essere sbagliato e dovrebbe essere convalidato prima dell'uso). L'unica altra volta che è sbagliato è per errore del programmatore (e i tuoi test di unità dovrebbero prenderli).

    
risposta data 04.04.2011 - 07:21
fonte
15

Quanto ridondanza / robustezza dovrebbero essere complesse software implement? fa una domanda correlata.

La mia risposta è che le funzioni che interagiscono con il mondo esterno (API pubbliche, metodi dell'interfaccia utente, lettori di file, ecc.) dovrebbero convalidare l'input e informare l'utente degli errori nel modo più chiaro e chiaro possibile. Per lo meno il messaggio di errore / codice di ritorno dovrebbe indicare dove si è verificato l'input errato e quale tipo di vincolo ha violato. Più specifico è, meglio è.

D'altra parte, le funzioni private che trattano solo dati o dati generati internamente già elaborati da una delle funzioni rivolte all'esterno dovrebbero avere asserzioni su una delle condizioni necessarie per il corretto funzionamento. Se un programmatore scrive un codice che viola questi, il programma dovrebbe fallire e fallire duramente. Questo tipo di bug non dovrebbe mai superare le prime fasi di test.

La motivazione qui è di essere il più piacevole possibile per gli utenti, limitando allo stesso tempo le decisioni su come gestire gli input negativi al livello più alto possibile. Non vuoi capire il prossimo pezzo della tua strategia di gestione degli errori a livello di programma ogni volta che scrivi una funzione di basso livello.

Quindi: convalida l'input dell'utente, asserisci l'input del programmatore.

    
risposta data 04.04.2011 - 16:56
fonte
10

Se hai un sistema di tipi, usalo.

Le asserzioni aiuteranno a cogliere il male in anticipo.

i vincoli non nulli sono una cosa ragionevole.

sqrt (-1) non è un errore in alcuni linguaggi di programmazione. Smalltalk con supporto per numeri complessi restituirà solo i

    
risposta data 04.04.2011 - 08:08
fonte
7

Penso che dipenda dall'applicazione del metodo, piuttosto che dalla teoria dell'implementazione.

Ciò che intendo è: se stai costruendo una veloce libreria matematica, anche se potrebbe essere usata da chiunque, non vuoi avere i controlli di runtime, almeno non quando è integrata la modalità 'rilascio', perché la velocità è il criterio di giudizio. È possibile implementare i controlli in modalità 'debug', utilizzando le asserzioni perché si desidera che il comportamento sia coerente tra le modalità. Certo, vuoi documentare questo tipo di comportamento molto bene, quindi gli utenti della tua libreria (anche se sei tu fra tre mesi!) Sanno quali controlli loro dovrebbero fare.

Ora, se stai costruendo una libreria di comunicazione di rete, vuoi aggiungere più sicurezza che puoi, perché 1) sarà alimentata per lo più da input dell'utente, quindi pericolo 2) le prestazioni non elaborate saranno legate principalmente dalla rete I / O, non per l'operazione della CPU nella maggior parte dei casi, quindi aggiungere qualche convalida non verrà nemmeno notato.

    
risposta data 04.04.2011 - 10:35
fonte
2

Ogni funzione dovrebbe verificare il suo input , anche quelle funzioni interne che non fanno parte di alcuna API o interfaccia pubblica.

I programmatori sono umani e gli umani sono notoriamente inetti nel mantenere i vincoli correlati in sincrono con l'evolversi delle basi di codice di grandi dimensioni - alla fine, la parte di "controllo input" che accade prima della "funzione di chiamata" scomparirà o si sposterà altrove o diventano incompleti e la funzione sarà chiamata con input errato. Quando ciò accade, i tuoi due obiettivi principali saranno:

  • Rileva il problema il più rapidamente possibile (la fase di compilazione è l'opzione migliore)
  • Non rompere nulla finché il problema non viene risolto

Per molte cose, è possibile utilizzare le funzionalità della lingua o il sistema di tipi per trasportare informazioni in fase di compilazione su quali proprietà sono state verificate. Questo è estremamente veloce (nessuna penalità in fase di esecuzione) e rileva errori in fase di compilazione. La maggior parte delle mie verifiche sono in questa categoria, per esempio.

Se la tua lingua non supporta la verifica in fase di compilazione per quello che stai facendo (che, in lingue moderne, è piuttosto raro), aggiungi un'asserzione di runtime.

Solo se l'errore del tuo codice non può avere conseguenze negative oltre a un bug facilmente rilevato e innocuo, e ti aspetti che il codice venga chiamato estremamente spesso, e la verifica non è una parte naturale del codice funzione comunque , puoi lasciare le verifiche. sqrt sarebbe probabilmente qui.

    
risposta data 04.04.2011 - 11:02
fonte
2

Per C / C ++ e altri linguaggi che forniscono il preprocessore funzionale, puoi convalidare i parametri di input solo durante la compilazione per Debug / Test e produrre build di release non valida.

La libreria MFC di Visual C ++ è uno dei buoni esempi.

Di seguito è riportato il codice di esempio da campioni pubblici MFC:

void CServerNode::CalcBounding(CDC* pDC, CPoint& ptStart, CSize& sizeMax)
{
    ASSERT(sizeMax.cx >= 0 && sizeMax.cy >= 0);
    ASSERT(ptStart.x >= 0 && ptStart.y >= 0);

    CSize sizeNode;
    CalcNodeSize(pDC, sizeNode);

    ptStart.y += sizeNode.cy + CY_SEPARATOR;
    if (ptStart.y > sizeMax.cy)
        sizeMax.cy = ptStart.y;

    if (ptStart.x + sizeNode.cx > sizeMax.cx)
        sizeMax.cx = ptStart.x + sizeNode.cx;
    ptStart.x += CX_INDENT;
    // add in the kids
    if (!m_bHideChildren)
    {
        POSITION pos = m_listChild.GetHeadPosition();
        while (pos != NULL)
        {
            CServerNode* pNode = (CServerNode*)m_listChild.GetNext(pos);
            pNode->CalcBounding(pDC, ptStart, sizeMax);
        }
    }
    ptStart.x -= CX_INDENT;
}
    
risposta data 04.04.2011 - 16:02
fonte
1

Metto sempre la convalida vicino all'origine dei dati in entrata, sia che si tratti di dati da un database, dati da un POST HTTP o dati da un socket di rete.

I motivi sono:

  • riduzione del codice
  • eliminazione di operazioni non necessarie
  • codice più semplice (di solito)

Tuttavia, ci saranno sempre eccezioni alla maggior parte delle regole di programmazione o delle migliori pratiche. La chiave per riconoscere queste eccezioni è il pensiero attento e la considerazione di ogni situazione piuttosto che la cieca adesione a un insieme di regole.

    
risposta data 04.04.2011 - 07:32
fonte
0

Io uso sempre lo schema semplice: Interfaccia utente e codice semplice per chiamare i metodi (convalida dei parametri dell'interfaccia utente) - > Metodi (convalida non parametri UI) - > Funzioni aggiuntive (non convalidano nulla)

    
risposta data 04.04.2011 - 10:51
fonte
0

Penso che ogni funzione dovrebbe verificare la validità dei suoi parametri di input, ed è meglio se può essere verificata in fase di compilazione (con l'aiuto del sistema di tipi, se stai usando un linguaggio tipizzato staticamente).

L'idea di utilizzare le asserzioni per assicurarsi che i parametri di input siano validi, mi sembra un po 'strano - in sostanza, significa che devi scrivere gli stessi controlli due volte - una volta nella funzione chiamante, e la seconda volta nel metodo stesso, sotto forma di asserzioni. Questo significa anche che quando cambiano i requisiti sui parametri di input, dovrai modificare i controlli ovunque nelle funzioni di chiamata, non solo il chiamato.

Quindi perché non solo convalidare i parametri nel metodo stesso e sollevare un'eccezione (o fare qualsiasi cosa sia appropriata) quando viene rilevata un'incoerenza?

    
risposta data 04.04.2011 - 16:12
fonte