Come dovrei gestire l'input dell'utente non valido?

12

Ho pensato a questo problema per un po 'e sarei curioso di avere opinioni da altri sviluppatori.

Tendo ad avere uno stile di programmazione molto difensivo. Il mio tipico blocco o metodo ha questo aspetto:

T foo(par1, par2, par3, ...)
{
    // Check that all parameters are correct, return undefined (null)
    // or throw exception if this is not the case.

    // Compute and (possibly) return result.
}

Inoltre, durante il calcolo, controllo tutti i puntatori prima di dereferenziarli. La mia idea è che, se c'è qualche bug e qualche puntatore NULL dovrebbe apparire da qualche parte, il mio programma dovrebbe gestirlo bene e semplicemente rifiutarsi di continuare il calcolo. Ovviamente può notificare il problema con un messaggio di errore nel registro o qualche altro meccanismo.

Per dirla in un modo più astratto, il mio approccio è

if all input is OK --> compute result
else               --> do not compute result, notify problem

Altri sviluppatori, tra cui alcuni miei colleghi, usano un'altra strategia. Ad esempio, non controllano i puntatori. Assumono che un pezzo di codice debba ricevere un input corretto e non dovrebbe essere responsabile di ciò che accade se l'input è sbagliato. Inoltre, se un'eccezione del puntatore NULL si arresta in modo anomalo nel programma, un bug verrà trovato più facilmente durante il test e avrà più possibilità di essere corretto.

La mia risposta a questo è normalmente: ma cosa succede se il bug non viene rilevato durante il test e appare quando il prodotto è già utilizzato dal cliente? Qual è il modo preferito per manifestare il bug? Dovrebbe essere un programma che non esegue una determinata azione, ma può ancora continuare a funzionare, o un programma che si blocca e deve essere riavviato?

Riassumendo

Quale dei due approcci per gestire input errati consiglierebbe?

Inconsistent input --> no action + notification

o

Inconsistent input --> undefined behaviour or crash

Modifica

Grazie per le risposte e i suggerimenti. Sono un fan del design per contratto. Ma anche se mi fido della persona che ha scritto il codice chiamando i miei metodi (forse è me stesso), possono ancora esserci dei bug, che portano a input sbagliati. Quindi il mio approccio è di non assumere mai che un metodo sia passato all'input corretto.

Inoltre, vorrei utilizzare un meccanismo per rilevare il problema e notificarlo. Su un sistema di sviluppo, sarebbe ad es. aprire una finestra di dialogo per informare l'utente. In un sistema di produzione scriverebbe solo alcune informazioni nel registro. Non penso che i controlli extra possano portare a problemi di prestazioni. Non sono sicuro che le asserzioni siano sufficienti, se vengono disattivate in un sistema di produzione: potrebbe verificarsi qualche situazione nella produzione che non si è verificata durante il test.

Ad ogni modo, sono stato davvero sorpreso dal fatto che molte persone seguano l'approccio opposto: lasciano che l'applicazione si arresti "a scopo" perché ritengono che questo renderà più facile trovare bug durante i test.

    
posta Giorgio 25.08.2011 - 00:08
fonte

6 risposte

8

Hai capito bene. Sii paranoico. Non fidarti di altro codice, anche se è il tuo codice. Dimentichi le cose, apporti delle modifiche, il codice si evolve. Non fidarti del codice esterno.

È stato fatto un buon punto sopra: cosa succede se gli input non sono validi ma il programma non si blocca? Quindi si ottiene la spazzatura nel database e gli errori lungo la linea.

Quando viene richiesto un numero (ad esempio, il prezzo in dollari o il numero di unità) mi piace inserire "1e9" e vedere cosa fa il codice. Può succedere.

Quattro decenni fa, ottenendo il mio B.S. in informatica da U.C.Berkeley, ci è stato detto che un buon programma è il 50% di gestione degli errori. Sii paranoico.

    
risposta data 25.08.2011 - 09:55
fonte
7

Hai già l'idea giusta

Quale dei due approcci per gestire input errati consiglierebbe?

Inconsistent input --> no action + notification

o meglio

Inconsistent input --> appropriately handled action

Non si può davvero prendere un approccio alla programmazione (si potrebbe) con il cookie cutter, ma si finirà con un design formulato che fa le cose per abitudine piuttosto che per scelta consapevole.

Temperare il dogmatismo con pragmatismo.

Steve McConnell lo ha detto meglio

Steve McConnell ha praticamente scritto il libro ( Codice completo ) sulla programmazione difensiva e questo era uno dei metodi che ha consigliato di convalidare sempre i tuoi input.

Non ricordo se Steve abbia menzionato questo, tuttavia dovresti prendere in considerazione l'idea di farlo per i metodi e le funzioni non-privati e solo altri dove ritenuto necessario.

    
risposta data 25.08.2011 - 00:59
fonte
3

Non c'è una risposta "corretta" qui, in particolare senza specificare la lingua, il tipo di codice e il tipo di prodotto in cui potrebbe essere inserito il codice. Prendere in considerazione:

  • La lingua conta. In Objective-C, è spesso bene inviare messaggi a zero; non succede nulla, ma neanche il programma si arresta. Java non ha puntatori espliciti, quindi nil puntatori non sono una grande preoccupazione lì. In C, devi essere un po 'più attento.

  • Essere paranoici significa sospetto irragionevole, sospetto o diffidenza ingiustificata. Probabilmente non è migliore per il software di quanto lo sia per le persone.

  • Il livello di preoccupazione dovrebbe essere commisurato al livello di rischio nel codice e alla probabile difficoltà di identificare eventuali problemi che si presentano. Cosa succede nel caso peggiore? L'utente riavvia il programma e continua da dove si erano interrotti? La società perde milioni di dollari?

  • Non è sempre possibile identificare input errati. Puoi confrontare religiosamente i tuoi indicatori con zero, ma questo cattura solo uno di 2 ^ 32 possibili valori, quasi tutti negativi.

  • Esistono molti meccanismi diversi per gestire gli errori. Di nuovo, dipende in una certa misura dalla lingua. È possibile utilizzare macro assert, istruzioni condizionali, test unitari, gestione delle eccezioni, progettazione attenta e altre tecniche. Nessuno di questi è infallibile e nessuno è appropriato per ogni situazione.

Quindi, per lo più si riduce a dove si vuole mettere la responsabilità. Se stai scrivendo una libreria per l'uso da parte di altri, probabilmente vorresti essere cauto quanto puoi ragionevolmente sugli input che ottieni e fare del tuo meglio per emettere errori utili quando possibile. Nelle tue funzioni e metodi privati, potresti usare gli asserti per cogliere errori sciocchi, ma altrimenti mettere responsabilità nel chiamante (che sei tu) a non passare la spazzatura.

    
risposta data 25.08.2011 - 09:02
fonte
1

Ci dovrebbe essere sicuramente una notifica, come un'eccezione generata. Serve come riferimento per altri codificatori che potrebbero utilizzare in modo improprio il codice che hai scritto (cercando di usarlo per qualcosa che non era destinato a fare) che il loro input non è valido o che genera errori. Questo è molto utile per rintracciare gli errori, mentre se si restituisce semplicemente null, il loro codice continuerà finché non tenteranno di usare il risultato e ottenere un'eccezione da un codice diverso.

Se il tuo codice incontra un errore durante una chiamata ad un altro codice (forse un aggiornamento fallito del database) che va oltre lo scopo di quel particolare pezzo di codice, non hai alcun controllo su di esso e la tua unica risorsa è lanciare un eccezione che spiega cosa sai (solo ciò che ti viene detto dal codice che hai chiamato). Se sai che determinati input porteranno inevitabilmente a un risultato del genere, non puoi limitarti a eseguire il codice e lanciare un'eccezione che indica quale input non è valido e perché.

Su una nota più simile all'utente finale, è meglio restituire qualcosa di descrittivo ma semplice in modo che chiunque possa capirlo. Se il tuo cliente chiama e dice "il programma si è bloccato, risolvilo", hai un sacco di lavoro sulle tue mani, rintracciando cosa è andato storto e perché, e sperando di poter riprodurre il problema. L'utilizzo corretto delle eccezioni non solo previene un arresto anomalo, ma fornisce informazioni preziose. Una chiamata da un cliente che dice "Il programma mi sta dando un errore. Dice che" XYZ non è un input valido per il metodo M, perché Z è troppo grande ", o qualcosa del genere, anche se non hanno idea di cosa significhi, tu sapere esattamente dove cercare. Inoltre, a seconda delle pratiche commerciali della tua azienda o della tua azienda, potrebbe non essere nemmeno la soluzione a questi problemi, quindi è meglio lasciare loro una buona mappa.

Quindi la versione breve della mia risposta è che la tua prima opzione è la migliore.

Inconsistent input -> no action + notify caller
    
risposta data 25.08.2011 - 01:18
fonte
1

Ho faticato con questo stesso problema durante una lezione universitaria in programmazione. Mi sono spinto verso il lato paranoico e ho la tendenza a controllare tutto, ma mi è stato detto che si trattava di un comportamento errato.

Ci è stato insegnato "Design per contratto". L'accento è che le condizioni preliminari, invarianti e post-condizioni siano specificate nei commenti e nei documenti di progettazione. Come la persona che implementa la mia parte del codice, dovrei avere fiducia nell'architetto del software e potenziarli seguendo le specifiche che includeranno le precondizioni (quali input i miei metodi devono essere in grado di gestire e quali input non verranno inviati) . Un controllo eccessivo in ogni chiamata di metodo risulta gonfio.

Le asserzioni dovrebbero essere usate durante le iterazioni di costruzione per verificare la correttezza del programma (convalida di precondizioni, invarianti, condizioni di posta). Le asserzioni verrebbero quindi trasformate nella compilazione di produzione.

    
risposta data 25.08.2011 - 08:15
fonte
0

L'uso delle "asserzioni" è la strada da percorrere per avvisare gli altri sviluppatori che stanno facendo di sbagliato, solo nei metodi "privati" . Abilitarli / disabilitarli è solo un flag da aggiungere / rimuovere in fase di compilazione e in quanto tale è facile rimuovere asserzioni dal codice di produzione. C'è anche un ottimo strumento per sapere se tu stai sbagliando in qualche modo nei tuoi stessi metodi.

Per quanto riguarda la verifica dei parametri di input con metodi pubblici / protetti, preferisco lavorare in modo difensivo e controllare i parametri e lanciare InvalidArgumentException o qualcosa di simile. Ecco perché ci sono qui per. Dipende anche dal fatto che tu stia scrivendo un'API o meno. Se si tratta di un'API e ancor di più se è closed source, convalidare meglio tutto in modo che gli sviluppatori sappiano esattamente cosa è andato storto. Altrimenti, se la fonte è disponibile per altri sviluppatori, non è in bianco e nero. Basta essere coerenti con le tue scelte.

Modifica: tanto per aggiungere che, se si guarda ad esempio a Oracle JDK, si vedrà che non controllano mai "null" e lasciano il codice in crash. Dato che sta per lanciare comunque una NullPointerException, perché preoccuparsi di verificare la presenza di null e lanciare un'eccezione esplicita. Immagino che abbia un senso.

    
risposta data 25.08.2011 - 07:43
fonte

Leggi altre domande sui tag