La verifica delle condizioni ridondanti è in contrasto con le best practice?

15

Ho sviluppato software negli ultimi tre anni, ma mi sono appena svegliato da quanto sono ignorante sulle buone pratiche. Questo mi ha portato a iniziare a leggere il libro Clean Code , che sta migliorando la mia vita in meglio, ma sto cercando di capire da vicino alcuni dei migliori approcci per scrivere i miei programmi.

Ho un programma Python in cui io ...

  1. usa argparse required=True per imporre due argomenti, che sono entrambi i nomi dei file. il primo è il nome del file di input, il secondo è il nome del file di output
  2. ha una funzione readFromInputFile che prima controlla che sia stato inserito un nome di file di input
  3. ha una funzione writeToOutputFile che prima controlla che sia stato inserito un nome di file di output

Il mio programma è abbastanza piccolo da indurmi a credere che il check-in n. 2 e n. 3 sia ridondante e debba essere rimosso, liberando entrambe le funzioni da una condizione if non necessaria. Tuttavia, sono stato anche portato a credere che "il doppio controllo sia ok" e potrebbe essere la soluzione giusta in un programma in cui le funzioni potrebbero essere chiamate da una posizione diversa in cui non si verifica l'analisi degli argomenti.

(Inoltre, se la lettura o la scrittura falliscono, ho un try except in ogni funzione per generare un messaggio di errore appropriato.)

La mia domanda è: è meglio evitare tutti i controlli delle condizioni ridondanti? La logica di un programma dovrebbe essere così solida che i controlli devono essere fatti una sola volta? Ci sono dei buoni esempi che illustrano questo o il contrario?

EDIT: Grazie a tutti per le risposte! Ho imparato qualcosa da ciascuno. Vedere così tante prospettive mi dà una migliore comprensione di come affrontare questo problema e determinare una soluzione in base alle mie esigenze. Grazie!

    
posta thesis 26.11.2016 - 06:59
fonte

9 risposte

15

Ciò che stai chiedendo è chiamato "robustezza", e non c'è una risposta giusta o sbagliata. Dipende dalle dimensioni e dalla complessità del programma, dal numero di persone che vi lavorano e dall'importanza di rilevare i guasti.

Nei piccoli programmi si scrive da soli e solo per se stessi, la robustezza è in genere una preoccupazione molto più piccola rispetto a quando si sta per scrivere un programma complesso che consiste di più componenti, magari scritti da una squadra. In tali sistemi, ci sono dei confini tra i componenti sotto forma di API pubbliche, e ad ogni limite, è spesso una buona idea convalidare i parametri di input, anche se "la logica del programma dovrebbe essere così solida che quei controlli siano ridondanti ". Questo rende il rilevamento dei bug molto più semplice e aiuta a ridurre i tempi di debug.

Nel tuo caso, devi decidere da solo, quale tipo di ciclo di vita ti aspetti per il tuo programma. È un programma che prevedi di utilizzare e mantenere negli anni? Quindi aggiungere un controllo ridondante è probabilmente meglio, dal momento che non sarà improbabile che il tuo codice verrà refactored in futuro e le tue funzioni read e write potrebbero essere utilizzate in un contesto diverso.

O è un piccolo programma solo per scopi di apprendimento o divertimento? Quindi quei doppi controlli non saranno necessari.

Nel contesto di "Clean Code", si potrebbe chiedere se una doppia verifica viola il principio di DRY. In realtà, a volte lo fa, almeno in misura minore: la convalida dell'input può essere interpretata come parte della logica di business di un programma, e avere questo in due punti può portare ai soliti problemi di manutenzione causati dalla violazione di DRY. La robustezza rispetto a DRY è spesso un compromesso: la robustezza richiede ridondanza nel codice, mentre DRY cerca di minimizzare la ridondanza. E con l'aumentare della complessità del programma, la robustezza diventa sempre più importante dell'essere DRY in fase di validazione.

Infine, vorrei fare un esempio di ciò che significa nel tuo caso. Supponiamo che le tue esigenze cambino in qualcosa di simile a

  • il programma deve funzionare anche con un argomento, il nome del file di input, se non è stato specificato il nome del file di output, viene automaticamente creato dal nome del file di input sostituendo il suffisso.

È probabile che sia necessario modificare la doppia convalida in due punti? Probabilmente no, un tale requisito porta a una modifica quando si chiama argparse , ma nessuna modifica in writeToOutputFile : quella funzione richiederà comunque un nome file. Quindi, nel tuo caso, voterei per fare la convalida dell'input due volte, il rischio di ottenere problemi di manutenzione a causa di due punti di cambiamento è IMHO molto inferiore al rischio di problemi di manutenzione a causa di errori mascherati causati da troppi controlli.

    
risposta data 26.11.2016 - 08:29
fonte
5

La ridondanza non è il peccato. La ridondanza è inutile.

  1. Se readFromInputFile() e writeToOutputFile() sono funzioni pubbliche (e in base alle convenzioni di denominazione di Python perché i loro nomi non iniziano con due trattini bassi) allora le funzioni potrebbero essere usate un giorno da qualcuno che evitava argparse del tutto . Ciò significa che quando lasciano gli argomenti non riescono a vedere il tuo messaggio di errore argparse personalizzato.

  2. Se readFromInputFile() e writeToOutputFile() controllano i parametri stessi, puoi visualizzare nuovamente un messaggio di errore personalizzato che spiega la necessità di nomi file.

  3. Se readFromInputFile() e writeToOutputFile() non controllano direttamente i parametri, non viene visualizzato alcun messaggio di errore personalizzato. L'utente dovrà capire da solo l'eccezione risultante.

Tutto si riduce a 3. Scrivi del codice che utilizza effettivamente queste funzioni evitando argparse e producendo il messaggio di errore. Immagina di non aver guardato all'interno di queste funzioni e si sono semplicemente fidati dei loro nomi per fornire una comprensione sufficiente da usare. Quando tutto ciò che sai è un modo per essere confusi dall'eccezione? È necessario un messaggio di errore personalizzato?

Spegnere la parte del tuo cervello che ricorda l'interno di quelle funzioni è difficile. Tanto che alcuni raccomandano di scrivere il codice usando prima del codice che viene usato. In questo modo arrivi al problema già sapendo quali sono le cose dall'esterno. Non devi fare TDD per farlo, ma se fai TDD entrerai già da fuori prima.

    
risposta data 26.11.2016 - 13:45
fonte
4

La misura in cui rendi i tuoi metodi autonomi e riutilizzabili è una buona cosa. Ciò significa che i metodi dovrebbero essere indulgenti in ciò che accettano e dovrebbero avere risultati ben definiti (precisi in ciò che restituiscono). Ciò significa anche che dovrebbero essere in grado di gestire con garbo tutto passato a loro e non fare alcuna ipotesi sulla natura dell'input, sulla qualità, sui tempi ecc.

Se un programmatore ha l'abitudine di scrivere metodi che fanno supposizioni su ciò che è passato, sulla base di idee come "se questo è rotto, abbiamo cose più grandi di cui preoccuparsi" o "il parametro X non può avere valore Y perché il resto del codice lo impedisce ", quindi all'improvviso non hai più componenti indipendenti disaccoppiati. I tuoi componenti dipendono essenzialmente dal sistema più ampio. Questo è un tipo di sottile accoppiamento stretto e porta ad aumentare esponenzialmente il costo totale di proprietà con l'aumentare della complessità del sistema.

Nota che questo potrebbe significare che stai convalidando le stesse informazioni più di una volta. Ma questo è OK. Ogni componente è responsabile della propria validazione a modo suo . Questa non è una violazione di DRY, perché le convalide avvengono tramite componenti indipendenti disaccoppiati e una modifica alla convalida in uno non deve necessariamente essere replicata esattamente nell'altro. Non c'è ridondanza qui. X ha la responsabilità di controllare gli input per i propri bisogni e passare alcuni a Y. Y ha la responsabilità di controllare i propri input per i suoi bisogni .

    
risposta data 27.11.2016 - 03:11
fonte
1

Supponi di avere una funzione (in C)

void readInputFile (const char* path);

E non è possibile trovare alcuna documentazione sul percorso. E poi guardi all'implementazione e dice

void readInputFile (const char* path)
{
    assert (path != NULL && strlen (path) > 0);

Questo test non solo l'input della funzione, ma dice anche all'utente della funzione che il percorso non può essere NULL o una stringa vuota.

    
risposta data 26.11.2016 - 17:19
fonte
0

In generale, il doppio controllo non è sempre buono o cattivo. Ci sono sempre molti aspetti della domanda nel tuo caso particolare da cui dipende la materia. Nel tuo caso:

  • Quanto è grande il programma? Più è piccolo, più è ovvio che chi chiama fa la cosa giusta. Quando il tuo programma diventa più grande, diventa più importante specificare esattamente quali sono le precondizioni e le postcondizioni di ogni routine.
  • gli argomenti sono già controllati dal modulo argparse . Spesso è una cattiva idea usare una libreria e poi fare il proprio lavoro da soli. Perché utilizzare la libreria, quindi?
  • Quanto è probabile che il tuo metodo venga riutilizzato in un contesto in cui il chiamante non controlla gli argomenti? Più è probabile che sia, più è importante convalidare gli argomenti.
  • Cosa succede se manca un argomento ? La ricerca di un file di input probabilmente non si interromperà completamente. Questa è probabilmente una ovvia modalità di errore che è facile da correggere. Gli errori insidiosi sono quelli in cui il programma continua a funzionare e produce risultati errati senza che tu lo noti .
risposta data 26.11.2016 - 08:30
fonte
0

I tuoi doppi controlli sembrano essere in luoghi dove vengono usati raramente. Quindi questi controlli stanno semplicemente rendendo il tuo programma più robusto:

Un controllo eccessivo non danneggerà, uno meno di quanto potrebbe.

Tuttavia, se stai controllando all'interno di un ciclo che viene ripetuto spesso, dovresti pensare a rimuovere la ridondanza, anche se il controllo stesso è nella maggior parte delle volte non costoso rispetto a quanto segue dopo il controllo.

    
risposta data 26.11.2016 - 09:37
fonte
0

Forse potresti cambiare il tuo punto di vista:

Se qualcosa va storto, qual è il risultato? Farà male alla tua applicazione / all'utente?

Naturalmente si potrebbe sempre discutere, se più o meno controlli sono migliori o peggiori, ma questa è una domanda piuttosto scolastica. E dal momento che hai a che fare con il software real world , ci sono conseguenze del mondo reale.

Dal contesto che stai dando:

  • un file di input A
  • un file di output B

Presumo che tu stia trasformando da A a B . Se A e B sono piccoli e la trasformazione è piccola, quali sono le conseguenze?

1) Hai dimenticato di specificare da dove leggere: Quindi il risultato è niente . E il tempo di esecuzione sarà più breve del previsto. Guardi il risultato - o meglio: cerca un risultato mancante, vedi che hai invocato il comando in modo sbagliato, ricomincia e tutto va bene di nuovo

2) Hai dimenticato di specificare il file di output. Ciò si traduce in diversi scenari:

a) L'input viene letto in una volta. La conversione inizia e il risultato dovrebbe essere scritto, ma invece si riceve un errore. A seconda del tempo, l'utente deve attendere (a seconda della massa di dati che potrebbero essere elaborati) questo potrebbe essere fastidioso.

b) L'input viene letto passo dopo passo. Quindi il processo di scrittura si chiude immediatamente come in (1) e l'utente ricomincia da capo.

Il controllo sloppy potrebbe essere visto come OK in alcune circostanze. Dipende interamente dal tuo caso d'uso e dalla tua intenzione.

Inoltre: Dovresti evitare la paranoia e non fare troppi controlli incrociati.

    
risposta data 26.11.2016 - 09:48
fonte
0

Direi che i test non sono ridondanti.

  • Hai due funzioni pubbliche che richiedono un nome file come parametro di input. È opportuno convalidare i loro parametri. Le funzioni potrebbero essere potenzialmente utilizzate in qualsiasi programma che richiede la loro funzionalità.
  • Hai un programma che richiede due argomenti che devono essere nomi di file. Capita di usare le funzioni. È opportuno che il programma verifichi i suoi parametri.

Mentre i nomi dei file vengono controllati due volte, vengono controllati per scopi diversi. In un piccolo programma in cui è possibile verificare i parametri per le funzioni sono stati verificati, i controlli nelle funzioni potrebbero essere considerati ridondanti.

Una soluzione più robusta avrebbe uno o due validatori di nomi di file.

  • Per un file di input, è possibile verificare che il parametro abbia specificato un file leggibile.
  • Per un file di output, ti consigliamo di verificare che il parametro sia un file scrivibile o un nome file valido che può essere creato e scritto.

Uso due regole per quando eseguire azioni:

  • Falli il prima possibile. Funziona bene per cose che saranno sempre richieste. Dal punto di vista di questo programma, questo è il controllo sui valori di argv e le successive convalide nella logica dei programmi sarebbero ridondanti. Se le funzioni vengono spostate in una libreria, non sono più ridondanti, poiché la libreria non può fidarsi che tutti i chiamanti abbiano convalidato i parametri.
  • Falli il più tardi possibile. Questo funziona molto bene per cose che raramente saranno richieste. Dal punto di vista di questo programma, questo è il controllo dei parametri della funzione.
risposta data 27.11.2016 - 00:43
fonte
0

Il controllo è ridondante. Risolvendo questo problema, è necessario rimuovere readFromInputFile e writeToOutputFile e sostituirli con readFromStream e writeToStream.

Nel punto in cui il codice riceve il flusso di file, sai di avere un flusso valido connesso a un file valido o qualsiasi altra cosa a cui un flusso può essere collegato. Questo evita controlli ridondanti.

Potresti quindi chiedere, beh, hai ancora bisogno di aprire lo stream da qualche parte. Sì, ma ciò avviene internamente nel metodo di analisi dell'argomento. Sono presenti due controlli, uno per verificare che sia richiesto un nome file, l'altro è un controllo che il file indicato dal nome file sia valido nel contesto specificato (ad esempio, il file di input esiste, la directory di output è scrivibile). Si tratta di diversi tipi di controlli, quindi non sono ridondanti e avvengono all'interno del metodo di analisi dell'argomento (perimetro dell'applicazione) anziché all'interno dell'applicazione principale.

    
risposta data 17.02.2017 - 14:07
fonte

Leggi altre domande sui tag