Buon stile di codice per introdurre controlli dei dati ovunque?


10

Ho un progetto di dimensioni sufficientemente grandi che non riesco più a mantenere ogni aspetto nella mia testa. Ho a che fare con un numero di classi e funzioni in esso e sto trasmettendo dati.

Con il passare del tempo ho notato che continuavo a ricevere errori, perché ho dimenticato quale forma precisa devono avere i dati quando li passo a funzioni diverse ( ad esempio, una funzione accetta e genera un array di stringhe, un'altra funzione, che ho scritto molto più tardi, accetta le stringhe che sono conservate in un dizionario, ecc., quindi devo trasformare le stringhe con cui sto lavorando dal disporne in un array per averle in un dizionario ).

Per evitare di dover sempre capire cosa ha funzionato, ho iniziato a considerare ogni funzione e classe come "entità isolata", nel senso che non può fare affidamento sul codice esterno che fornisce l'input corretto e deve eseguire i controlli di input stesso (o, in alcuni casi, rifondere i dati, se i dati sono forniti nella forma sbagliata).

Ciò ha notevolmente ridotto il tempo che passo a fare in modo che i dati che passo intorno si adattino a tutte le funzioni, perché le classi e le funzioni stesse ora mi avvisano quando alcuni input sono errati (e talvolta lo correggono) e non lo faccio devo andare con un debugger attraverso l'intero codice per capire dove qualcosa è andato in tilt.

D'altro canto, questo ha anche aumentato il codice generale.
La mia domanda è: se questo stile di codice è appropriato per risolvere questo problema?
Naturalmente, la soluzione migliore sarebbe quella di refactificare completamente il progetto e assicurarsi che i dati abbiano una struttura uniforme per tutte le funzioni - ma dal momento che questo progetto è in costante crescita, finirei per spendere di più e preoccuparmi del codice pulito piuttosto che aggiungere effettivamente nuove cose .

(FYI: Sono ancora un principiante, quindi per favore scusate se questa domanda era ingenua; il mio progetto è in Python.)



3
@gnat È simile, ma invece di rispondere alla mia domanda, lì fornisce consigli ("sii il più difensivo che puoi") per quella specifica istanza menzionata da OP, che è diversa dalla mia domanda più generale.
user7088941

2
"ma dal momento che questo progetto è in costante crescita, finirei per spendere di più e preoccuparmi del codice pulito piuttosto che aggiungere effettivamente nuove cose" - sembra proprio che tu debba iniziare a preoccuparti del codice pulito. Altrimenti, la tua produttività rallenterà e rallenterà poiché ogni nuovo bit di funzionalità è sempre più difficile da aggiungere a causa del codice esistente. Non tutte le esigenze di refactoring di essere "completo", se l'aggiunta di qualcosa di nuovo, è difficile a causa del codice esistente che tocca, refactoring solo che il codice toccare e prendere nota di ciò che si desidera rivedere in seguito
matt Freake

3
Questo è un problema che le persone affrontano spesso usando linguaggi debolmente tipizzati. Se non vuoi o puoi passare a un linguaggio più tipizzato, la risposta è semplicemente "sì, questo stile di codice è appropriato per risolvere questo problema" . Prossima domanda?
Doc Brown,

1
In un linguaggio rigorosamente tipizzato, con i tipi di dati corretti definiti, il compilatore lo avrebbe fatto per te.
SD

Risposte:


4

Una soluzione migliore è sfruttare maggiormente le funzionalità e gli strumenti del linguaggio Python.

Ad esempio, nella funzione 1, l'input previsto è un array di stringhe, in cui la prima stringa indica il titolo di qualcosa e la seconda un riferimento bibliografico. Nella funzione 2 l'input previsto è ancora un array di stringhe, ma ora i ruoli delle stringhe sono invertiti.

Questo problema è mitigato con a namedtuple. È leggero e offre un significato semantico facile ai membri dell'array.

Per ottenere il vantaggio di un controllo automatico del tipo senza cambiare lingua, puoi trarre vantaggio dal suggerimento sul tipo . Un buon IDE può usarlo per farti sapere quando fai qualcosa di stupido.

Sembri anche preoccupato che le funzioni diventino obsolete quando cambiano i requisiti. Questo può essere colto da test automatizzati .

Anche se non sto dicendo che il controllo manuale non sia mai appropriato, un migliore utilizzo delle funzionalità linguistiche disponibili può aiutarti a risolvere questo problema in modo più sostenibile.


+1 per avermi indicato namedtuplee tutte le altre cose carine. Non mi preoccupavo namedtuple- e mentre conoscevo i test automatizzati, non l'ho mai usato molto e non mi rendevo conto di quanto mi avrebbe aiutato in questo caso. Tutti questi sembrano davvero essere buoni come un'analisi statica. (I test automatizzati potrebbero anche essere migliori, in quanto riesco a cogliere tutte le cose sottili che non verrebbero catturate in un'analisi statica!) Se ne conosci altre, per favore fatemelo sapere. Terrò la domanda aperta ancora per un po ', ma se non arrivano altre risposte, accetterò la tua.
user7088941

9

OK, il problema reale è descritto in un commento sotto questa risposta:

Ad esempio, nella funzione 1, l'input previsto è un array di stringhe, in cui la prima stringa indica il titolo di qualcosa e la seconda un riferimento bibliografico. Nella funzione 2 l'input previsto è ancora un array di stringhe, ma ora i ruoli delle stringhe sono invertiti

Il problema qui è l'uso di un elenco di stringhe in cui l'ordine indica la semantica. Questo è un approccio veramente soggetto a errori. Dovresti invece creare una classe personalizzata con due campi denominati titlee bibliographical_reference. In questo modo non li mescolerai e eviterai questo problema in futuro. Naturalmente questo richiede un po 'di refactoring se usi già elenchi di stringhe in molti punti, ma credimi, sarà a lungo termine più economico.

L'approccio comune nei linguaggi di tipi dinamici è la "tipizzazione anatra", il che significa che non ti interessa davvero il "tipo" dell'oggetto passato, ti importa solo se supporta i metodi che lo chiami. Nel tuo caso, leggerai semplicemente il campo chiamato bibliographical_referencequando ne avrai bisogno. Se questo campo non esiste sull'oggetto passato, verrà visualizzato un errore e ciò indica che il tipo errato viene passato alla funzione. Questo è un controllo del tipo buono come un altro.


A volte il problema è ancora più sottile: sto passando il tipo corretto, ma la "struttura interna" del mio input rovina la funzione: ad esempio, nella funzione 1, l'input previsto è un array di stringhe, in cui la prima stringa denota il titolo di qualcosa e il secondo un riferimento bibliografico. Nella funzione 2 l'input previsto è ancora un array di stringhe, ma ora i ruoli delle stringhe sono invertiti: la prima stringa dovrebbe essere il riferimento bibliografico e la seconda dovrebbe essere il riferimento bibliografico. Immagino che questo controllo sia appropriato?
user7088941

1
@ user7088941: Il problema che descrivi potrebbe essere facilmente risolto con una classe con due campi: "titolo" e "riferimento bibliografico". Non lo confonderai. Affidarsi all'ordine in un elenco di stringhe sembra molto soggetto a errori. Forse questo è il problema di fondo?
JacquesB,

3
Questa è la risposta Python è un linguaggio orientato agli oggetti, non un elenco di dizionari-string-string-to-interi-oriented (o qualunque altra lingua). Quindi, usa gli oggetti. Gli oggetti sono responsabili della gestione del proprio stato e dell'applicazione dei propri invarianti, altri oggetti non possono corromperli, mai (se progettati correttamente). Se i dati non strutturati o semi-strutturati entrano nel sistema dall'esterno, si convalida e si analizza una volta al limite del sistema e si converte in oggetti ricchi il più presto possibile.
Jörg W Mittag,

3
"Eviterei davvero il costante refactoring" - questo blocco mentale è il tuo problema. Il buon codice deriva solo dal refactoring. Molti refactoring. Supportato da unit test. Soprattutto quando i componenti devono essere estesi o evoluti.
Doc Brown,

2
Ora ho capito. +1 per tutte le belle intuizioni e commenti. E grazie a tutti per i loro commenti incredibilmente utili! (Mentre stavo usando alcune classi / oggetti, li ho intervallati con gli elenchi citati, che, come vedo ora, non era una buona idea. Rimaneva la domanda su come implementarlo al meglio, dove ho usato i suggerimenti concreti dalla risposta di JETM , che ha fatto davvero una differenza radicale in termini di velocità nel raggiungere uno stato privo di bug.)
user7088941

3

Prima di tutto, quello che stai sperimentando in questo momento è l' odore del codice - cerca di ricordare cosa ti ha portato a diventare consapevole dell'odore e prova ad affinare il tuo naso "mentale", poiché prima noti un odore di codice, prima e più facile - sei in grado di risolvere il problema sottostante.

Per evitare di dover sempre capire cosa ha funzionato, ho iniziato a considerare ogni funzione e classe come "un'entità isolata", nel senso che non può fare affidamento sul codice esterno che fornisce l'input corretto e deve eseguire da sé i controlli di input.

La programmazione difensiva - come viene chiamata questa tecnica - è uno strumento valido e spesso utilizzato. Tuttavia, come per tutte le cose, è importante utilizzare la giusta quantità, troppi controlli e non si rilevano problemi, troppi e il codice sarà troppo gonfio.

(o, in alcuni casi, rifondere i dati, se i dati vengono forniti nella forma sbagliata).

Potrebbe essere un'idea meno buona. Se noti che una parte del tuo programma sta chiamando una funzione con dati formattati in modo errato, FISSA QUESTA PARTE , non modificare la funzione chiamata per poter comunque digerire i dati errati.

Ciò ha notevolmente ridotto il tempo che passo a fare in modo che i dati che passo intorno si adattino a tutte le funzioni, perché le classi e le funzioni stesse ora mi avvisano quando alcuni input sono errati (e talvolta lo correggono) e non lo faccio devo andare con un debugger attraverso l'intero codice per capire dove qualcosa è andato in tilt.

Migliorare la qualità e la manutenibilità del tuo codice è un risparmio di tempo a lungo termine (in tal senso devo nuovamente mettere in guardia contro la funzionalità di auto-correzione che hai incorporato in alcune delle tue funzioni: potrebbero essere una fonte insidiosa di bug. Solo perché il tuo programma non si arresta in modo anomalo e masterizza non significa che funzioni bene ...)

Per rispondere finalmente alla tua domanda: Sì, la programmazione difensiva (cioè la verifica della validità dei parametri forniti) è - in buona salute - una buona strategia. Detto questo , come hai detto tu stesso, il tuo codice è incoerente e raccomando vivamente di dedicare un po 'di tempo al refactoring delle parti che hanno un odore : hai detto che non vuoi preoccuparti del codice pulito tutto il tempo, dedicando più tempo a "pulizia" rispetto a nuove funzionalità ... Se non mantieni pulito il tuo codice potresti passare il doppio del tempo "risparmiando" a non mantenere un codice pulito per eliminare i bug E sarà difficile implementare nuove funzionalità - il debito tecnico può schiacciarti.


1

Va bene. Ero solito programmare in FoxPro, dove avevo blocchi TRY..CATCH quasi in ogni grande funzione. Ora, codice in JavaScript / LiveScript e raramente controllo i parametri nelle funzioni "interne" o "private".

"Quanto controllare" dipende dal progetto / dalla lingua scelti più che dalla tua abilità nel codice.


1
Immagino sia stato PROVARE ... CATTURARE ... IGNORARE. Hai fatto il contrario di ciò che l'OP chiede. IMHO il loro punto è quello di evitare incoerenze mentre il tuo stava assicurando che il programma non esplodesse quando si colpisce uno.
maaartinus,

1
@maaartinus è corretto. I linguaggi di programmazione in genere ci danno costrutti semplici da usare per impedire l'applicazione di esplosioni, ma i costrutti che i linguaggi di programmazione ci danno per prevenire incoerenze sembrano essere molto più difficili da usare: per quanto ne sappia, refactualizza costantemente tutto e usa le classi che meglio contengono flusso di informazioni nella tua applicazione. Questo è esattamente ciò di cui mi sto chiedendo: esiste un modo più semplice per risolvere questo problema.
user7088941

@ user7088941 Ecco perché evito le lingue debolmente digitate. Python è semplicemente fantastico, ma per qualcosa di più grande, non posso tenere traccia di ciò che ho fatto altrove. Pertanto, preferisco Java, che è piuttosto dettagliato (non tanto con le funzionalità di Lombok e Java 8), ha una tipizzazione rigorosa e strumenti per l'analisi statica. Ti suggerirei di provare alcuni tipi di linguaggio rigorosamente poiché non so come risolverlo altrimenti.
maaartinus,

Non si tratta di un parametro tipizzato rigoroso / sciolto. Si tratta di sapere che il parametro è corretto. Anche se si utilizza (numero intero 4 byte), potrebbe essere necessario verificare se è compreso in un intervallo compreso tra 0 e 10, ad esempio. Se sai che il parametro è sempre 0..10 non è necessario verificarlo. FoxPro non ha array associativi per esempio, è molto difficile operare con le sue variabili, il loro ambito e così via .. ecco perché devi controllare check check ..
Michael Quad

1
@ user7088941 Non è OO, ma c'è la regola "fail fast". Ogni metodo non privato deve controllare i suoi argomenti e lanciare quando qualcosa non va. Nessun tentativo di cattura, nessun tentativo di risolverlo, basta soffiarlo alle stelle. Certo, a un livello superiore, l'eccezione viene registrata e gestita. Poiché i test rilevano la maggior parte dei problemi in anticipo e nessun problema viene nascosto, il codice converge in una soluzione senza errori molto più velocemente rispetto a quando è tollerante agli errori.
maaartinus,
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.