Programmazione pulita durante la scrittura di codice scientifico


169

Non scrivo davvero grandi progetti. Non sto gestendo un enorme database o gestendo milioni di righe di codice.

Il mio codice è principalmente roba di tipo "scripting" - cose per testare funzioni matematiche o per simulare qualcosa - "programmazione scientifica". I programmi più lunghi su cui ho lavorato fino a questo punto sono circa duecento righe di codice e la maggior parte dei programmi su cui lavoro sono circa 150.

Anche il mio codice è una schifezza. Me ne sono reso conto l'altro giorno mentre cercavo di trovare un file che ho scritto qualche tempo fa, ma che probabilmente ho sovrascritto e che non uso il controllo della versione, il che probabilmente sta facendo rabbrividire un gran numero di voi per la mia stupidità.

Lo stile del mio codice è contorto ed è pieno di commenti obsoleti che notano modi alternativi per fare qualcosa o con righe di codice copiate. Mentre i nomi delle variabili iniziano sempre molto bene e descrittivi, quando aggiungo o cambio cose come per esempio, qualcosa di nuovo che qualcuno vuole testato, il codice viene sovrapposto e sovrascritto e perché sento che questa cosa dovrebbe essere testata rapidamente ora che ho un framework, comincio a usare nomi di variabili scadenti e il file va al piatto.

Nel progetto a cui sto lavorando ora, sono nella fase in cui tutto questo sta tornando per mordermi in grande stile. Ma il problema è (a parte l'utilizzo del controllo versione e la creazione di un nuovo file per ogni nuova iterazione e la registrazione di tutto in un file di testo da qualche parte, il che probabilmente aiuterà drammaticamente la situazione) Non so davvero come procedere con il miglioramento il mio attuale stile di programmazione.

Il test unitario è necessario per scrivere piccoli pezzi di codice? Che ne dici di OOP? Quali tipi di approcci sono utili per scrivere rapidamente codice pulito e buono quando si fa "programmazione scientifica" anziché lavorare su progetti più grandi?

Faccio queste domande perché spesso la programmazione stessa non è super complessa. È più sulla matematica o sulla scienza che sto testando o facendo ricerche con la programmazione. Ad esempio, è necessaria una classe quando due variabili e una funzione potrebbero probabilmente occuparsene? (Considera che queste sono anche situazioni in cui si preferisce che la velocità del programma sia più rapida - quando stai eseguendo più di 25.000.000 di passi temporali di una simulazione, vorrai che lo sia.)

Forse questo è troppo ampio e, in tal caso, mi scuso, ma guardando i libri di programmazione, sembrano spesso essere indirizzati a progetti più grandi. Il mio codice non ha bisogno di OOP, ed è già piuttosto breve quindi non è come "oh, ma il file verrà ridotto di mille righe se lo facciamo!" Voglio sapere come "ricominciare" e programmare in modo pulito questi progetti più piccoli e veloci.

Sarei felice di fornire dettagli più specifici, ma più il consiglio è generale, più utile credo. Sto programmando in Python 3.


Qualcuno ha suggerito un duplicato. Consentitemi di chiarire che non sto parlando di ignorare apertamente gli standard di programmazione standard. Chiaramente, c'è una ragione per cui esistono questi standard. D'altra parte, ha davvero senso scrivere un codice che dice OOP quando alcune cose standard potrebbero essere state fatte, sarebbe stato molto più veloce da scrivere e sarebbe stato un livello simile di leggibilità a causa della brevità del programma?

Ci sono eccezioni. Inoltre, ci sono probabilmente standard per la programmazione scientifica oltre a semplici standard. Sto chiedendo anche di quelli. Non si tratta di ignorare i normali standard di codifica quando si scrive codice scientifico, si tratta di scrivere codice scientifico pulito!


Aggiornare

Ho pensato di aggiungere un tipo di aggiornamento "non abbastanza una settimana dopo". Tutti i tuoi consigli sono stati estremamente utili. Ora sto usando il controllo versione - git, con git kraken per un'interfaccia grafica. È molto facile da usare e ha ripulito drasticamente i miei file - non è più necessario che i vecchi file rimangano in giro o che le vecchie versioni di codice abbiano commentato "per ogni evenienza".

Ho anche installato Pylint ed eseguito su tutto il mio codice. Inizialmente un file ha ottenuto un punteggio negativo; Non sono nemmeno sicuro di come sia stato possibile. Il mio file principale è iniziato con un punteggio di ~ 1.83 / 10 e ora è a ~ 9.1 / 10. Tutto il codice ora si conforma abbastanza bene agli standard. L'ho anche investito con i miei occhi aggiornando i nomi delle variabili che erano diventati ... uhm ... errati, e cercavo sezioni da refactoring.

In particolare, ho posto una domanda recente su questo sito sul refactoring di una delle mie funzioni principali, e ora è molto più pulita e molto più breve: invece di una funzione lunga, gonfia, se / riempita, ora è meno della metà la dimensione e molto più facile capire cosa sta succedendo.

Il mio prossimo passo è implementare una sorta di "unit test". Con ciò intendo un file che posso eseguire sul mio file principale che esamina tutte le funzioni in esso contenute con istruzioni assert e try / excepts, che probabilmente non è il modo migliore per farlo e genera un sacco di codice duplicato, ma continuerò a leggere e cercherò di capire come farlo meglio.

Ho anche aggiornato in modo significativo la documentazione che avevo già scritto e ho aggiunto file aggiuntivi come un foglio di calcolo Excel, la documentazione e un documento associato al repository github. Sembra un vero progetto di programmazione ora.

Quindi ... immagino sia tutto da dire: grazie .




8
Se desideri attivamente migliorare il tuo codice, potresti prendere in considerazione l'idea di pubblicarne alcuni su Code Review . La community sarà lieta di aiutarti.
hoffmale,

7
Quando dici "a parte l'utilizzo del controllo versione e la creazione di un nuovo file per ogni nuova iterazione e la registrazione di tutto in un file di testo da qualche parte" per "e" intendi "o" perché se stai usando il controllo versione non dovresti sarà copia incolla versioni. Il punto è che il controllo della versione mantiene tutta la vecchia versione per te
Richard Tingle il

2
@mathreadler Non credo che tu abbia capito bene. Sì, solo probabilmente probabilmente leggerò e incasinerò il codice (anche se non lo sai mai, e ho qualcuno con cui sto lavorando che può programmare, anche se in una lingua diversa) ... ma il codice è ancora una schifezza. Dovrò leggerlo più tardi e capire di nuovo cosa diavolo sto facendo. Si tratta di un problema, e posso testimoniare perché sto sperimentando gli effetti ora, e le cose sono diventate più facile come ho implementato il controllo di versione e altre tecniche suggerite qui.
heather,

Risposte:


163

Questo è un problema abbastanza comune per gli scienziati. L'ho visto molto, e deriva sempre dal fatto che la programmazione è qualcosa che scegli di lato come strumento per fare il tuo lavoro.

Quindi i tuoi script sono un casino. Ho intenzione di andare contro il buon senso e dire che, supponendo che tu stia programmando da solo, questo non è poi così male! Non toccherai mai più la maggior parte di ciò che scrivi mai più, quindi passare troppo tempo a scrivere un bel codice invece di produrre "valore" (quindi il risultato del tuo script) non ti farà molto.

Tuttavia, ci sarà un momento in cui dovrai tornare a qualcosa che hai fatto e vedere esattamente come funzionava qualcosa. Inoltre, se altri scienziati dovranno rivedere il tuo codice, è davvero importante che sia il più chiaro e conciso possibile, in modo che tutti possano capirlo.

Il tuo problema principale sarà la leggibilità, quindi ecco alcuni suggerimenti per migliorare:

Nomi delle variabili:

Gli scienziati adorano usare notazioni concise. Tutte le equazioni matematiche di solito usano singole lettere come variabili e non sarei sorpreso di vedere molte variabili molto brevi nel tuo codice. Ciò danneggia molto la leggibilità. Quando tornerai al tuo codice non ricorderai cosa rappresentano quelle y, io e x2 e passerai molto tempo a cercare di capirlo. Prova invece a dare un nome esplicito alle tue variabili, usando nomi che rappresentano esattamente quello che sono.

Dividi il tuo codice in funzioni:

Ora che hai rinominato tutte le tue variabili, le tue equazioni sembrano terribili e sono lunghe più righe.

Invece di lasciarlo nel tuo programma principale, sposta quell'equazione su un'altra funzione e chiamala di conseguenza. Ora invece di avere una riga di codice enorme e incasinata, avrai una breve istruzione che ti dirà esattamente cosa sta succedendo e quale equazione hai usato. Ciò migliora sia il tuo programma principale, poiché non devi nemmeno guardare l'equazione reale per sapere cosa hai fatto, sia il codice dell'equazione stesso, poiché in una funzione separata puoi nominare le variabili come preferisci e tornare a le lettere singole più familiari.

Su questa linea di pensiero, prova a scoprire tutti i pezzi di codice che rappresentano qualcosa, specialmente se quel qualcosa è qualcosa che devi fare più volte nel tuo codice e suddividili in funzioni. Scoprirai che il tuo codice diventerà rapidamente più facile da leggere e che sarai in grado di utilizzare le stesse funzioni senza scrivere altro codice.

La ciliegina sulla torta, se quelle funzioni sono necessarie in più dei tuoi programmi, puoi semplicemente creare una libreria per loro e le avrai sempre disponibili.

Variabili globali:

Quando ero un principiante, pensavo fosse un ottimo modo per trasferire i dati di cui avevo bisogno in molti punti del mio programma. Si scopre che ci sono molti altri modi per aggirare le cose, e l'unica cosa che fanno le variabili globali è dare mal di testa alle persone, poiché se vai in un punto casuale del tuo programma non saprai mai quando quel valore è stato usato o modificato l'ultima volta, e rintracciarlo sarà un dolore. Cerca di evitarli quando possibile.

Se le funzioni devono restituire o modificare più valori, creare una classe con tali valori e passarli come parametro oppure fare in modo che la funzione restituisca più valori (con tuple denominate) e assegnare tali valori nel codice chiamante.

Controllo versione

Questo non migliora direttamente la leggibilità, ma ti aiuta a fare tutto quanto sopra. Ogni volta che si apportano alcune modifiche, impegnarsi per il controllo della versione (un repository Git locale andrà abbastanza bene), e se qualcosa non funziona, guarda cosa hai cambiato o torna indietro! Ciò renderà il refactoring del codice molto più semplice e sarà una rete di sicurezza in caso di rottura accidentale delle cose.

Tenere presente tutto ciò ti permetterà di scrivere un codice chiaro e più efficace e ti aiuterà anche a trovare possibili errori più velocemente, poiché non dovrai guadare funzioni gigantesche e variabili disordinate.


56
Ottimo consiglio Tuttavia non posso votare per il commento "che non è poi così male". È così male Gli script scientifici di bassa qualità sono un grosso problema per la riproducibilità nell'analisi dei dati e una frequente fonte di errore nell'analisi. Scrivere un buon codice è prezioso non solo per poterlo capire in seguito, ma anche per evitare errori in primo luogo.
Jack Aidley,

22
Se il codice è un'implementazione di una formula ben nota, allora i nomi delle variabili a lettera singola e tale potrebbero essere la cosa giusta da fare. Dipende dal pubblico ... e ancora di più dal punto, se ci si aspetta che il lettore sappia già cosa significano i nomi.
cHao,

11
@cHao il problema non è quando quelle variabili sono inserite nella formula (da cui il consiglio "rinominale all'interno della funzione"), ma quando vengono lette e manipolate al di fuori di essa, e quando iniziano a entrare in conflitto con altre variabili lungo la linea (ad esempio, ho visto le persone che hanno bisogno di tre variabili "x" nominarle x1, x2, x3)
BgrWorker,

4
"Vado contro il buon senso ..." No, non lo sei. Stai andando contro il dogma prevalente, che è esso stesso contro il buon senso. ;) Questo è tutto un consiglio perfettamente valido.
jpmc26,

3
Come un (ex) programmatore scientifico, generalmente adotto una regola di tre. Se finisco per scrivere tre volte un codice simile, la funzionalità viene perfezionata e scritta in un modulo separato con documentazione (spesso solo commenti, ma è sufficiente). Ciò limita il caos della programmazione ad hoc e mi consente di creare una libreria su cui potrò espandermi in futuro.
rcollyer,

141

Fisico qui. Stato lì.

Direi che il tuo problema non riguarda la scelta di strumenti o paradigmi di programmazione (unit test, OOP, qualunque cosa). Riguarda l' atteggiamento , la mentalità. Il fatto che i nomi delle tue variabili siano ben scelti all'inizio e finiscano per essere una schifezza è abbastanza rivelatore. Se pensi al tuo codice come "esegui una volta, poi butta via", allora sarà inevitabilmente un casino. Se lo consideri come il prodotto dell'artigianato e dell'amore, sarà bellissimo.

Credo che ci sia una sola ricetta per scrivere codice pulito: scriverlo per l'essere umano che lo leggerà, non per l'interprete che lo eseguirà. L'interprete non si preoccupa se il codice è un pasticcio, ma il lettore umano fa la cura.

Sei uno scienziato. Probabilmente puoi passare molto tempo a lucidare un articolo scientifico. Se la tua prima bozza sembra contorta, la rifatterai finché la logica non scorrerà nel modo più naturale. Volete che i vostri colleghi lo leggano e trovino gli argomenti chiarissimi. Vuoi che i tuoi studenti siano in grado di imparare da esso.

Scrivere codice pulito è esattamente lo stesso. Pensa al tuo codice come una spiegazione dettagliata di un algoritmo che solo per inciso risulta leggibile da una macchina. Immagina di pubblicarlo come un articolo che la gente leggerà. Lo mostrerai anche a una conferenza e accompagnerai il pubblico riga per riga. Ora prova la tua presentazione . Sì, riga per riga ! Imbarazzante, vero? Quindi ripulisci le diapositive (err ... voglio dire, il tuo codice) e prova di nuovo. Ripeti finché non sei soddisfatto del risultato.

Sarebbe ancora meglio se, dopo le prove, puoi mostrare il tuo codice a persone reali piuttosto che solo a persone immaginarie e al tuo sé futuro. Percorrerlo riga per riga si chiama "passeggiata del codice", e non è una pratica sciocca.

Naturalmente, tutto ciò ha un costo. La scrittura di codice pulito richiede molto più tempo rispetto alla scrittura di codice usa e getta. Solo tu puoi valutare se i benefici superano i costi per il tuo particolare caso d'uso.

Per quanto riguarda gli strumenti, ho detto prima che non fossero così importanti. Tuttavia, se dovessi sceglierne uno, direi che il controllo della versione è il più utile.


32
«Scrivere codice pulito è esattamente lo stesso [come scrivere un articolo chiaro].» Lo approvo pienamente, per dirla bene!
juandesant,

43
Questo è qualcosa che la maggior parte dei programmatori professionisti dimentica di dire perché è così ovvio. Il tuo codice è finito quando è leggibile e modificabile da un altro programmatore. Non quando viene eseguito e produce l'output corretto. L'OP deve dedicare un'ora in più per il refactoring dello script e commentare il suo codice per renderlo leggibile.
UEFI,

31
Sebbene la scrittura di codice pulito richieda molto più tempo rispetto alla scrittura di codice usa e getta, molto più importante è che la lettura di codice usa e getta richiede molto più tempo rispetto alla lettura di codice pulito.
user949300,

3
@UEFI No, è qualcosa che la maggior parte dei programmatori professionisti non capisce nemmeno. O non importa.
jpmc26,

2
Accetto il 100%. Lo statistico è diventato programmatore, quindi lavoro in modo abbastanza programmato "scientifico". Il codice chiaro, con commenti significativi, è un vero toccasana quando devi tornare a quel codice 1, 4 o 12 mesi dopo. La lettura del codice indica cosa sta facendo il codice. La lettura dei commenti ti dice cosa dovrebbe fare il codice.
railsdog

82

Il controllo della versione ti darà probabilmente il massimo per il tuo dollaro. Non è solo per l'archiviazione a lungo termine, è ottimo per tenere traccia della tua sperimentazione a breve termine e tornare all'ultima versione che ha funzionato, mantenendo le note lungo la strada.

I più utili sono i test unitari. La cosa su unit test è che anche le basi di codice con milioni di righe di codice sono unit test per una funzione alla volta. Il test unitario viene eseguito nel piccolo, al livello più basso di astrazione. Ciò significa che non esiste sostanzialmente alcuna differenza tra i test unitari scritti per basi di codice di piccole dimensioni e quelli utilizzati per grandi. Ce ne sono solo altri.

I test unitari sono il modo migliore per evitare di rompere qualcosa che già funzionava quando correggi qualcos'altro, o almeno per dirti rapidamente quando lo fai. In realtà sono più utili quando non sei un programmatore esperto o non sai come o non vuoi scrivere codice più dettagliato strutturato per rendere gli errori meno probabili o più ovvi.

Tra il controllo della versione e la scrittura di unit test man mano che procedi, il tuo codice diventerà naturalmente molto più pulito. Altre tecniche per una codifica più pulita possono essere apprese quando si colpisce un plateau.


78
Relativamente a test unitari della maggior parte del mio codice, ho trovato test sperimentali, codice scientifico meno che inutile . Fondamentalmente la metodologia non sembra funzionare qui. Non conosco nessuno scienziato computazionale nel mio campo che testa unitamente il loro codice di analisi. Non sono sicuro di quale sia la ragione di questa discrepanza, ma uno dei motivi è sicuramente che, ad eccezione di unità banali, è difficile o impossibile stabilire buoni casi di test.
Konrad Rudolph,

22
@KonradRudolph il trucco in quei casi è probabilmente una netta separazione delle preoccupazioni tra parti del codice che hanno un comportamento chiaramente definibile (leggi questo input, calcola questo valore) dalle parti del tuo codice che sono autenticamente esplorative o che si stanno adattando ad es. alcuni output o visualizzazioni leggibili dall'uomo. Il problema qui è probabilmente che la scarsa separazione delle preoccupazioni porta a confondere quelle linee, il che porta a una percezione dell'impossibilità di testare le unità in questo contesto, il che ti riporta all'inizio in un ciclo ripetuto.
Ant P,

18
In una nota a margine, il controllo della versione funziona anche abbastanza bene per i documenti LaTeX, poiché il formato è suscettibile di differenze di testo. In questo modo, puoi avere un repository sia per i tuoi documenti che per il codice che li supporta. Suggerisco di esaminare il controllo della versione distribuita, come Git. C'è un po 'di una curva di apprendimento, ma una volta che lo capisci, hai un bel modo pulito di iterare sul tuo sviluppo e hai alcune opzioni interessanti per usare una piattaforma come Github, che offre account di team gratuiti per accademici .
Dan Bryant,

12
@AntP È possibile che non ci sia molto codice che può essere significativamente riformulato in unità testabili ben definite. Un sacco di codice scientifico sta essenzialmente registrando un sacco di librerie insieme. Queste librerie saranno già ben testate e strutturate in modo pulito, il che significa che l'autore deve solo scrivere "colla" e, nella mia esperienza, è quasi impossibile scrivere test unitari per colla che non siano tautologici.
James_pic,

7
"Tra il controllo della versione e la scrittura di unit test man mano che procedi, il tuo codice diventerà naturalmente molto più pulito." Questo non è vero Posso attestarlo personalmente. Nessuno di questi strumenti ti impedisce di scrivere codice schifoso, e in particolare, scrivere test schifosi sopra il codice schifoso rende ancora più difficile la pulizia. I test non sono un magico proiettile d'argento e parlare come sono è una cosa terribile da fare per qualsiasi sviluppatore che sta ancora imparando (che è tutti). Tuttavia, il controllo della versione non causa mai danni al codice stesso come fanno i test errati.
jpmc26,

29

(a parte l'utilizzo del controllo versione e la creazione di un nuovo file per ogni nuova iterazione e la registrazione di tutto in un file di testo da qualche parte, il che probabilmente aiuterà la situazione in modo drammatico)

Probabilmente l'avresti capito tu stesso, ma se devi " registrarlo tutto in un file di testo da qualche parte " non stai usando il sistema di controllo della versione al suo pieno potenziale. Usa qualcosa come Subversion, git o Mercurial e scrivi un buon messaggio di commit con ogni commit e avrai un registro che serve allo scopo del file di testo ma non può essere separato dal repository.

A parte questo, l'uso del controllo versione è la cosa più importante che puoi fare per un motivo che nessuna delle risposte esistenti menziona: riproducibilità dei risultati . Se puoi utilizzare i tuoi messaggi di registro o aggiungere una nota ai risultati con il numero di revisione, puoi essere sicuro di essere in grado di rigenerare i risultati e sarai nella posizione migliore per pubblicare il codice con la carta.

Il test unitario è necessario per scrivere piccoli pezzi di codice? Che ne dici di OOP? Quali tipi di approcci sono utili per scrivere rapidamente codice pulito e buono quando si fa "programmazione scientifica" anziché lavorare su progetti più grandi?

Il test unitario non è mai necessario, ma è utile se (a) il codice è abbastanza modulare da poter testare le unità piuttosto che il tutto; (b) è possibile creare test. Idealmente, si sarebbe in grado di scrivere manualmente l'output previsto anziché generarlo con il codice, anche se generarlo con il codice può almeno fornire test di regressione che indicano se qualcosa ha cambiato il suo comportamento. Considera solo se è più probabile che i test siano corretti rispetto al codice che stanno testando.

OOP è uno strumento. Usalo se aiuta, ma non è l'unico paradigma. Suppongo che tu conosca davvero solo la programmazione procedurale: se è così, nel contesto descritto penso che trarrai maggiori benefici dallo studio della programmazione funzionale rispetto a OOP, e in particolare la disciplina di evitare effetti collaterali ove possibile. Python può essere scritto in uno stile molto funzionale.


4
+1 per i messaggi di commit; sono come commenti che non possono essere superati perché legati alla versione del codice quando erano effettivamente applicabili. Per comprendere il tuo vecchio codice, è più semplice esaminare la cronologia del progetto (se le modifiche vengono eseguite con una granularità ragionevole) piuttosto che leggere commenti obsoleti.
Silly Freak,

Subversion, git e Mercurial non sono fungibili. Raccomanderei fortemente di usare Git (o Mercurial) con un repository locale su Subversion. Con un programmatore solista, i difetti di Subversion sono meno un problema, ma non è un ottimo strumento per lo sviluppo collaborativo e ciò potrebbe potenzialmente accadere nella ricerca
mcottle

2
@mcottle, personalmente preferisco git ma non pensavo che questo fosse il posto giusto per entrare nei dettagli sulle differenze, soprattutto perché la scelta è una delle guerre di religione attive. Meglio incoraggiare gli OP a usare qualcosa piuttosto che spaventarli lontano dall'area, e la decisione non è permanente in ogni caso.
Peter Taylor,

21

Alla scuola di specializzazione, ho scritto un codice pesante per algoritmi. È un po 'difficile da rompere. Per dirla in modo grossolano, molte convenzioni di programmazione si basano sull'idea di mettere le informazioni in un database, recuperarle al momento giusto e quindi massaggiare quei dati per presentarli a un utente, in genere usando una libreria per qualsiasi matematica o parti pesanti di questo processo. Per questi programmi, tutto ciò che hai sentito su OOP, suddividere il codice in brevi funzioni e rendere tutto facilmente comprensibile a colpo d'occhio, ove possibile, è un consiglio eccellente. Ma non funziona del tutto con il codice pesante dell'algoritmo, o codice che implementa calcoli matematici complessi e poco altro.

Se stai scrivendo script per eseguire calcoli scientifici, probabilmente hai dei documenti con le equazioni o gli algoritmi che usi scritti in essi. Se stai usando nuove idee che hai scoperto da solo, spero che le pubblichi in articoli per conto tuo. In questo caso, la regola è: vuoi che il tuo codice legga il più possibile le equazioni pubblicate. Ecco una risposta su Software Engineering.SE con oltre 200 voti a favore di questo approccio e che spiega come appare: esiste una scusa per i nomi di variabili brevi?

Come altro esempio, ci sono alcuni grandi frammenti di codice in Simbody , uno strumento di simulazione fisica utilizzato per la ricerca e l'ingegneria della fisica. Questi frammenti hanno un commento che mostra un'equazione utilizzata per un calcolo, seguita da un codice che legge il più vicino possibile alle equazioni implementate.

ContactGeometry.cpp:

// t = (-b +/- sqrt(b^2-4ac)) / 2a
// Discriminant must be nonnegative for real surfaces
// but could be slightly negative due to numerical noise.
Real sqrtd = std::sqrt(std::max(B*B - 4*A*C, Real(0)));
Vec2 t = Vec2(sqrtd - B, -sqrtd - B) / (2*A);

ContactGeometry_Sphere.cpp:

// Solve the scalar Jacobi equation
//
//        j''(s) + K(s)*j(s) = 0 ,                                     (1)
//
// where K is the Gaussian curvature and (.)' := d(.)/ds denotes differentiation
// with respect to the arc length s. Then, j is the directional sensitivity and
// we obtain the corresponding variational vector field by multiplying b*j. For
// a sphere, K = R^(-2) and the solution of equation (1) becomes
//
//        j  = R * sin(1/R * s)                                        (2)
//          j' =     cos(1/R * s) ,                                      (3)
//
// where equation (2) is the standard solution of a non-damped oscillator. Its
// period is 2*pi*R and its amplitude is R.

// Forward directional sensitivity from P to Q
Vec2 jPQ(R*sin(k * s), cos(k * s));
geod.addDirectionalSensitivityPtoQ(jPQ);

// Backwards directional sensitivity from Q to P
Vec2 jQP(R*sin(k * (L-s)), cos(k * (L-s)));
geod.addDirectionalSensitivityQtoP(jQP);

9
Più uno per creare "il codice per leggere il più possibile le equazioni pubblicate". Siamo spiacenti, sostenitori di nomi di variabili lunghi e significativi. I nomi più significativi nel codice scientifico sono spesso cattivi, brevi e brutali proprio perché è esattamente la convenzione usata in un giornale scientifico che il codice sta tentando di attuare. Per un grosso pezzo di codice che implementa equazioni trovate in un giornale, è spesso meglio stare il più vicino possibile alla nomenclatura nel documento, e se questo va contro il grano di buoni standard di codifica, difficile.
David Hammen,

@DavidHammen: Come studente laureato, lo rispetto. Come programmatore, insisto quindi sul fatto che nella parte superiore di ogni funzione sia presente un gigantesco blocco di commenti che descriva in inglese (o lingua di propria scelta) ciò che ogni variabile rappresentava, anche se solo un segnaposto temporaneo. In questo modo ho almeno un riferimento a cui guardare indietro.
tonysdg,

1
@DavidHammen Inoltre, il supporto Python per UTF-8 nei file sorgente e semplici regole per i nomi delle variabili rende facile dichiarare λo φinvece dei brutti lambda_o phy...
Mathias Ettinger

1
@tonysdg Hai già un riferimento; si chiama "Hammen, et al. (2018)" (o qualunque altra cosa). Spiegherà i significati delle variabili in modo molto più dettagliato di qualsiasi altro blocco di commenti possa mai. Il motivo per mantenere i nomi delle variabili vicini alla notazione nel documento è proprio per rendere più semplice collegare ciò che è nel documento a ciò che è nel codice.
Nessuno il

17

Quindi, il mio lavoro quotidiano è nella pubblicazione e conservazione dei dati di ricerca per il sistema dell'Università della California. Un paio di persone hanno menzionato la riproducibilità e penso che questo sia davvero il problema principale qui: documentare il tuo codice nel modo in cui documenteresti qualcos'altro che qualcuno ha bisogno per riprodurre il tuo esperimento e, idealmente, scrivere codice che lo rende semplice per qualcun altro entrambi per riprodurre l'esperimento e controllare i risultati per le fonti di errore.

Ma qualcosa che non ho visto menzionato, che penso sia importante, è che le agenzie di finanziamento guardano sempre più alla pubblicazione di software come parte della pubblicazione di dati e a rendere la pubblicazione di software un requisito per la scienza aperta.

A tal fine, se si desidera qualcosa di specifico, rivolto ai ricercatori piuttosto che agli sviluppatori di software generali, non posso raccomandare abbastanza l'organizzazione del Software Carpentry . Se puoi partecipare a uno dei loro seminari , fantastico; se tutto ciò che hai tempo / accesso a fare è leggere alcuni dei loro articoli sulle migliori pratiche di calcolo scientifico , anche questo è buono. Da quest'ultimo:

Gli scienziati in genere sviluppano il proprio software per questi scopi perché ciò richiede una conoscenza sostanziale specifica del dominio. Di conseguenza, studi recenti hanno scoperto che gli scienziati in genere impiegano il 30% o più del loro tempo nello sviluppo di software. Tuttavia, il 90% o più di questi sono principalmente autodidatta e pertanto non sono esposti alle pratiche di sviluppo del software di base come la scrittura di codice gestibile, l'utilizzo del controllo della versione e il rilevamento dei problemi, la revisione del codice, il test dell'unità e l'automazione delle attività.

Crediamo che il software sia solo un altro tipo di apparato sperimentale e debba essere costruito, controllato e usato con la stessa cura di qualsiasi apparato fisico. Tuttavia, sebbene la maggior parte degli scienziati stia attenta a convalidare il proprio laboratorio e le apparecchiature sul campo, la maggior parte non sa quanto sia affidabile il proprio software. Ciò può portare a gravi errori che incidono sulle conclusioni centrali della ricerca pubblicata. ...

Inoltre, poiché il software viene spesso utilizzato per più di un singolo progetto ed è spesso riutilizzato da altri scienziati, gli errori informatici possono avere impatti sproporzionati sul processo scientifico. Questo tipo di impatto a cascata ha causato diverse retrazioni rilevanti quando non è stato scoperto un errore dal codice di un altro gruppo fino a dopo la pubblicazione.

Una descrizione generale delle pratiche che raccomandano:

  1. Scrivi programmi per persone, non per computer
  2. Lascia che il computer faccia il lavoro
  3. Apporta modifiche incrementali
  4. Non ripetere te stesso (o gli altri)
  5. Pianifica errori
  6. Ottimizza il software solo dopo che funziona correttamente
  7. Progettazione e scopo del documento, non meccanica
  8. Collaborare

Il documento approfondisce in modo considerevole ciascuno di questi punti.


16

Ha davvero senso scrivere codice che dice OOP quando alcune cose standard potrebbero essere state fatte, sarebbe stato molto più veloce da scrivere e sarebbe stato un livello simile di leggibilità a causa della brevità del programma?

Risposta personale:
faccio molto scripting anche per scopi scientifici. Per gli script più piccoli, cerco semplicemente di seguire le buone pratiche generali di programmazione (ad es. Usando il controllo di versione, esercitando l'autocontrollo con nomi di variabili). Se sto scrivendo qualcosa per aprire o visualizzare rapidamente un set di dati, non mi preoccupo di OOP.

Risposta generale:
"Dipende". Ma se stai lottando per capire quando usare un concetto di programmazione o paradigmi, ecco un paio di cose a cui pensare:

  • Scalabilità: lo script sarà autonomo o verrà eventualmente utilizzato in un programma più ampio? In tal caso, la programmazione più ampia utilizza OOP? Il codice nel tuo script può essere facilmente integrato nel programma più grande?
  • Modularità: in generale, il codice dovrebbe essere modulare. Tuttavia, OOP suddivide il codice in blocchi in un modo molto speciale. Quel tipo di modularità (ovvero la suddivisione della sceneggiatura in classi) ha senso per quello che stai facendo?

Voglio sapere come "ricominciare" e programmare in modo pulito questi progetti più piccoli e veloci.

# 1: Acquisisci familiarità con ciò che è là fuori:
anche se stai "solo" scripting (e ti interessa davvero solo la componente scientifica), dovresti prenderti del tempo per conoscere diversi concetti e paradigmi di programmazione. In questo modo, puoi avere un'idea migliore di cosa dovresti / non dovresti usare e quando. Potrebbe sembrare un po 'scoraggiante. E potresti ancora avere la domanda "Da dove comincio / cosa inizio a guardare?" Cerco di spiegare un buon punto di partenza nei prossimi due punti elenco.

# 2: Inizia a correggere ciò che sai che è sbagliato:
Personalmente, vorrei iniziare con le cose che so essere sbagliate. Ottieni un po 'di controllo della versione e inizia a disciplinarti per migliorare con quei nomi di variabili (è una lotta seria). Risolvere ciò che sai è sbagliato può sembrare ovvio. Tuttavia, nella mia esperienza, ho scoperto che riparare una cosa mi porta a qualcos'altro, e così via. Prima che io lo sappia, ho svelato 10 cose diverse che stavo facendo di sbagliato e ho capito come risolverli o come implementarli in modo pulito.

# 3: Ottieni un partner di programmazione:
se "ricominciare" per te non implica prendere lezioni formali, considera di collaborare con uno sviluppatore e chiedere loro di rivedere il tuo codice. Anche se non comprendono la parte scientifica di ciò che stai facendo, potrebbero essere in grado di dirti cosa avresti potuto fare per rendere il tuo codice più elegante.

# 4: Cerca consorzi:
non so in quale area scientifica ti trovi. Ma a seconda di cosa fai nel mondo scientifico, prova a cercare consorzi, gruppi di lavoro o partecipanti alla conferenza. Quindi vedi se ci sono degli standard su cui stanno lavorando. Ciò potrebbe portare ad alcuni standard di codifica. Ad esempio, faccio molto lavoro geospaziale. Guardare i documenti della conferenza e i gruppi di lavoro mi ha portato all'Open Geospatial Consortium . Una delle cose che fanno è lavorare sugli standard per lo sviluppo geospaziale.

Spero che aiuti!


Nota a margine: so che hai appena usato OOP come esempio. Non volevo che pensassi di essermi bloccato su come gestire la scrittura del codice usando OOP. Era solo più facile scrivere una risposta continuando con quell'esempio.


Penso che il n. 3 sia il problema più importante: un programmatore esperto può dire all'OP i concetti di cui hanno bisogno (n. 1), come organizzare gli script in modo migliore e come utilizzare il controllo della versione (n. 2).
Doc Brown,

16

Consiglierei di attenermi al principio Unix: Keep It Simple, Stupid! (BACIO)

Oppure, in altre parole: fai una cosa alla volta e fallo bene.

Cosa significa? Bene, prima di tutto, significa che le tue funzioni dovrebbero essere brevi. Qualsiasi funzione che non può essere compresa appieno nello scopo, nell'uso e nell'implementazione in pochi secondi è decisamente troppo lunga. È probabile che faccia diverse cose contemporaneamente, ognuna delle quali dovrebbe essere una funzione propria. Quindi dividerlo.

In termini di linee di codice, la mia euristica è che 10 linee sono una buona funzione, e qualsiasi cosa oltre 20 è molto probabilmente merda. Altre persone hanno altre euristiche. La parte importante è mantenere la lunghezza fino a qualcosa che puoi effettivamente cogliere in un istante.

Come si divide una funzione lunga? Bene, prima cerchi schemi ripetuti di codice. Quindi estrai questi schemi di codice, dai loro un nome descrittivo e guardi il tuo codice ridursi . In realtà, il refactoring migliore è il refactoring che riduce la dimensione del codice.

Ciò è particolarmente vero quando la funzione in questione è stata programmata con copia-incolla. Ogni volta che vedi un modello così ripetuto, sai immediatamente che questo dovrebbe probabilmente essere trasformato in una sua funzione. Questo è il principio di Don't Repeat Yourself (DRY) . Ogni volta che colpisci copia-incolla, stai facendo qualcosa di sbagliato! Crea invece una funzione.

Aneddoto Una
volta ho trascorso diversi mesi a modificare il codice che aveva funzioni di circa 500 linee ciascuna. Dopo che avevo finito, il codice totale era più corto di circa mille righe; Avevo prodotto un output negativo in termini di righe di codice. Ho dovuto la società ( http://www.geekherocomic.com/2008/10/09/programmers-salary-policy/index.html ). Tuttavia, sono fermamente convinto che questa sia stata una delle mie opere più preziose che abbia mai fatto ...

Alcune funzioni potrebbero essere lunghe perché stanno facendo diverse cose distinte una dopo l'altra. Queste non sono violazioni SECCHI, ma possono anche essere divise. Il risultato è spesso una funzione di alto livello che chiama una manciata di funzioni che implementano i singoli passaggi delle funzioni originali. Questo generalmente aumenta la dimensione del codice, ma i nomi delle funzioni aggiunte fanno miracoli nel rendere il codice più leggibile. Perché ora hai una funzione di livello superiore con tutti i suoi passaggi esplicitamente denominati. Inoltre, dopo questa suddivisione è chiaro quale passaggio opera su quali dati. (Argomenti delle funzioni. Non usi variabili globali, vero?)

Una buona euristica per questo tipo di suddivisione delle funzioni in sezione è ogni volta che sei tentato di scrivere un commento di sezione o quando trovi un commento di sezione nel tuo codice. Questo è molto probabilmente uno dei punti in cui la funzione dovrebbe essere divisa. Il commento della sezione può anche servire a ispirare un nome per la nuova funzione.

I principi KISS e DRY possono fare molto. Non è necessario iniziare subito con OOP ecc., Spesso è possibile ottenere grandi semplificazioni semplicemente applicando questi due. Tuttavia, nel lungo periodo è utile conoscere OOP e altri paradigmi perché offrono strumenti aggiuntivi che è possibile utilizzare per rendere più chiaro il codice del programma.

Infine, registra ogni azione con un commit. Fattori qualcosa in una nuova funzione, è un impegno . Unisci due funzioni in una, perché fanno davvero la stessa cosa, è un impegno . Se rinomini una variabile, è un commit . Impegnati frequentemente. Se passa un giorno e non ti sei impegnato, probabilmente hai fatto qualcosa di sbagliato.


2
Ottimi punti sulla divisione di metodi lunghi. Un altro buon euristico riguardo al primo paragrafo dopo l'aneddoto: se il tuo metodo può essere logicamente diviso in sezioni e sei tentato di scrivere un commento che spieghi cosa fa ogni sezione, allora dovrebbe essere suddiviso nei commenti. Buone notizie, questi commenti probabilmente ti danno una buona idea di come chiamare i nuovi metodi.
Jaquez,

@Jaquez Ah, totalmente dimenticato. Grazie per avermi ricordato. Ho aggiornato la mia risposta per includere questo :-)
cmaster

1
Grandi punti, vorrei semplificare questo per dire che "DRY" è il singolo fattore più importante. Identificare "ripetizioni" e rimuoverle è la pietra angolare di quasi tutti gli altri costrutti di programmazione. Per dirla in altro modo, tutti i costrutti di programmazione sono lì, almeno in parte, per aiutarti a creare il codice DRY. Inizia dicendo "Nessuna duplicazione mai" e poi esercitati a identificarlo ed eliminarlo. Sii molto aperto su ciò che potrebbe essere un duplicato - anche se non è un codice simile potrebbe duplicare la funzionalità ...
Bill K

11

Concordo con gli altri sul fatto che il controllo della versione risolverà immediatamente molti dei tuoi problemi. In particolare:

  • Non c'è bisogno di mantenere un elenco di quali modifiche sono state apportate o di avere molte copie di un file, ecc. Poiché è a questo che serve il controllo della versione.
  • Niente più file persi a causa di sovrascritture, ecc. (Purché si attenga alle basi, ad es. Evitare "riscrittura della cronologia")
  • Non c'è bisogno che commenti obsoleti, codici morti, ecc. Vengano mantenuti "per ogni evenienza"; una volta che si sono impegnati nel controllo della versione, sentiti libero di screditarli. Questo può sembrare molto liberatorio!

Direi di non pensarci troppo: basta usare git. Attenersi a semplici comandi (ad esempio solo un singolo masterramo), forse utilizzare una GUI e dovresti andare bene. Come bonus puoi usare gitlab, github, ecc. Per pubblicazione e backup gratuiti;)

Il motivo per cui ho scritto questa risposta è stato quello di affrontare due cose che potresti provare che non ho mai visto prima. Il primo è utilizzare le asserzioni come alternativa leggera ai test unitari. I test unitari tendono a rimanere "fuori" dalla funzione / modulo / qualunque cosa venga testata: in genere inviano alcuni dati in una funzione, ricevono un risultato indietro, quindi controllano alcune proprietà di quel risultato. Questa è generalmente una buona idea, ma potrebbe essere scomoda (specialmente per il codice "buttare via") per alcuni motivi:

  • I test unitari devono decidere quali dati daranno alla funzione. I dati devono essere realistici (altrimenti non ha senso testarli), devono avere il formato corretto, ecc.
  • I test unitari devono avere "accesso" alle cose che vogliono affermare. In particolare, i test unitari non possono controllare nessuno dei dati intermedi all'interno di una funzione; dovremmo dividere questa funzione in pezzi più piccoli, testare quei pezzi e collegarli insieme da qualche altra parte.
  • Si presume inoltre che i test unitari siano rilevanti per il programma. Ad esempio, le suite di test possono diventare "obsolete" se ci sono state grandi modifiche dall'ultima esecuzione e potrebbero esserci anche un sacco di test per il codice che non viene nemmeno più utilizzato.

Le asserzioni non presentano questi inconvenienti, poiché vengono verificate durante la normale esecuzione di un programma. In particolare:

  • Dato che sono eseguiti come parte della normale esecuzione del programma, abbiamo dati reali con cui giocare. Ciò non richiede una cura separata e (per definizione) è realistico e ha il formato corretto.
  • Le asserzioni possono essere scritte in qualsiasi parte del codice, in modo da poterle inserire ovunque abbiamo accesso ai dati che vogliamo controllare. Se vogliamo testare un valore intermedio in una funzione, possiamo semplicemente mettere alcune asserzioni nel mezzo di quella funzione!
  • Dato che sono scritti in linea, le asserzioni non possono essere "fuori sincrono" con la struttura del codice. Se ci assicuriamo che le asserzioni siano verificate per impostazione predefinita, non dobbiamo nemmeno preoccuparci che diventino "stantie" poiché vedremo immediatamente se passeranno o meno la prossima volta che avremo eseguito il programma!

Si menziona la velocità come fattore, nel qual caso il controllo delle asserzioni potrebbe non essere auspicabile in quel ciclo (ma comunque utile per controllare l'installazione e la successiva elaborazione). Tuttavia, quasi tutte le implementazioni delle asserzioni forniscono un modo per disattivarle; ad esempio in Python possono apparentemente essere disabilitati eseguendo con l' -Oopzione (non lo sapevo, dal momento che non avevo mai sentito la necessità di disabilitare nessuna delle mie asserzioni prima). Vorrei raccomandare che li si lascia sudi default; se il tuo ciclo di codifica / debug / test rallenta, potresti fare meglio a testare con un sottoinsieme più piccolo dei tuoi dati o eseguire meno iterazioni di alcune simulazioni durante i test, o altro. Se finisci per disabilitare le asserzioni in esecuzioni non di prova per motivi di prestazioni, la prima cosa che ti consiglio di fare è misurare se sono effettivamente la fonte del rallentamento! (È molto facile illudersi quando si tratta di colli di bottiglia delle prestazioni)

Il mio ultimo consiglio sarebbe di usare un sistema di build che gestisca le tue dipendenze. Personalmente uso Nix per questo, ma ho sentito anche cose positive su Guix . Ci sono anche alternative come Docker, che sono molto meno utili dal punto di vista scientifico ma forse un po 'più familiari.

Sistemi come Nix sono diventati solo di recente (un po ') popolari, e alcuni potrebbero considerarli eccessivi per il codice "usa e getta" come descrivi, ma il loro vantaggio per la riproducibilità del calcolo scientifico è enorme. Prendi in considerazione uno script di shell per l'esecuzione di un esperimento, come questo (ad esempio run.sh):

#!/usr/bin/env bash
set -e
make all
./analyse < ./dataset > output.csv

Possiamo invece riscriverlo in una "derivazione" Nix, in questo modo (es. run.nix):

with import <nixpkgs> {};
runCommand "output.csv" {} ''
  cp -a ${./.} src
  cd src
  make all
  ./analyse < ./dataset > $out
''

La roba tra ''...''è il codice bash, lo stesso che avevamo prima, tranne che ${...}può essere usato per "giuntare" nel contenuto di altre stringhe (in questo caso ./., che si espanderà nel percorso della directory che contiene run.nix). La with import ...linea importa la libreria standard di Nix , che prevede l' runCommandesecuzione di codice bash. Possiamo eseguire il nostro esperimento utilizzando nix-build run.nix, che fornirà un percorso simile /nix/store/1wv437qdjg6j171gjanj5fvg5kxc828p-output.csv.

Quindi cosa ci compra questo? Nix imposterà automaticamente un ambiente "pulito", che ha accesso solo alle cose che abbiamo esplicitamente richiesto. In particolare non ha accesso a variabili simili $HOMEo a nessuno dei software di sistema che abbiamo installato. Questo rende il risultato indipendente dai dettagli della nostra macchina attuale, come i contenuti ~/.configo le versioni dei programmi che abbiamo installato; AKA il materiale che impedisce ad altre persone di replicare i nostri risultati! Questo è il motivo per cui l'ho aggiuntocpcomando, poiché il progetto non sarà accessibile per impostazione predefinita. Potrebbe sembrare fastidioso che il software del sistema non sia disponibile per uno script Nix, ma va anche dall'altra parte: non abbiamo bisogno di nulla installato sul nostro sistema (diverso da Nix) per utilizzarlo in uno script; lo chiediamo e Nix si spegne e recupera / compila / tutto ciò che è necessario (la maggior parte delle cose verrà scaricata come binaria; anche la libreria standard è enorme!). Ad esempio, se vogliamo un mucchio di pacchetti Python e Haskell particolari, per alcune versioni particolari di quelle lingue, oltre ad altre spazzature (perché perché no?):

with import <nixpkgs> {};
runCommand "output.csv"
  {
    buildInputs = [
      gcc49 libjson zlib
      haskell.packages.ghc802.pandoc
      (python34.withPackages (pyPkgs: [
        pyPkgs.beautifulsoup4 pyPkgs.numpy pyPkgs.scipy
        pyPkgs.tensorflowWithoutCuda
      ]))
    ];
  }
  ''
    cp -a ${./.} src
    cd src
    make all
    ./analyse < ./dataset > $out
  ''

Lo stesso nix-build run.nixeseguirà questo, recuperando tutto ciò che abbiamo chiesto per primo (e memorizzandolo nella cache nel caso lo desiderassimo in seguito). L'output (qualsiasi file / directory chiamato $out) verrà archiviato da Nix, che è il percorso che sputa. È identificato dall'hash crittografico di tutti gli input che abbiamo richiesto (contenuto dello script, altri pacchetti, nomi, flag del compilatore, ecc.); quegli altri pacchetti sono identificati da hash dei loro input, e così via che abbiamo una catena di provinence completa per tutto, tornando alla versione di GCC che ha compilato la versione di GCC che ha compilato bash, e così via!

Spero di aver dimostrato che questo ci compra molto per il codice scientifico ed è ragionevolmente facile iniziare. Sta anche iniziando a essere preso molto sul serio dagli scienziati, ad esempio (hit di Google) https://dl.acm.org/citation.cfm?id=2830172, quindi potrebbe essere un'abilità preziosa da coltivare (proprio come la programmazione)


2
Risposta utile molto dettagliata - Mi piacciono molto le altre risposte, ma le asserzioni sembrano un primo passo molto utile.
heather,

9

Senza passare alla versione completa controllo + packaging + unit test tipo di mentalità (che sono buone pratiche di programmazione che dovresti cercare di raggiungere ad un certo punto), una soluzione intermedia che penso si adatterebbe è usare Jupiter Notebook . Questo sembra integrarsi meglio con il calcolo scientifico.

Ha il vantaggio di poter mescolare i tuoi pensieri con il codice; spiegare perché un approccio è migliore di un altro e lasciare il vecchio codice così com'è in una sezione ad hoc. Inoltre, l'uso corretto delle celle ti porterà naturalmente a frammentare il tuo codice e organizzarlo in funzioni che possono aiutare la sua comprensione.


1
Inoltre, questo aiuta molto con la riproducibilità: puoi eseguire esattamente lo stesso codice per generare una figura di pubblicazione, ad esempio, o tornare a qualcosa che hai messo mesi fa forse per incorporare i commenti dei revisori.
afaulconbridge,

Per qualcuno che vuole leggere di più, questo è anche noto come programmazione alfabetica.
martedì

6

Le risposte migliori sono già buone, ma volevo rispondere direttamente ad alcune delle tue domande.

Il test unitario è necessario per scrivere piccoli pezzi di codice?

La dimensione del codice non è direttamente correlata alla necessità di test unitari. È collegato indirettamente: i test unitari sono più preziosi in basi di codice complesse e le basi di codici piccole non sono generalmente così complesse come quelle più grandi.

I test unitari brillano per il codice in cui è facile commettere errori o quando si hanno molte implementazioni di questo codice. I test unitari fanno poco per aiutarti con lo sviluppo attuale , ma fanno molto per impedirti di fare errori in futuro che causano un comportamento anomalo del codice esistente (anche se non hai toccato quella cosa).

Supponiamo che tu abbia un'applicazione in cui la libreria A esegue la quadratura dei numeri e la libreria B applica il teorema di Pitagora. Ovviamente, B dipende da A. Devi correggere qualcosa nella libreria A, e supponiamo che tu introduca un bug che cubi i numeri invece di quadrarli.

La libreria B inizierà improvvisamente a comportarsi male, probabilmente generando eccezioni o semplicemente dando un output errato. E quando guardi la cronologia delle versioni della libreria B, vedi che è intatta. Il risultato finale problematico è che non hai indicazioni di cosa potrebbe andare storto e dovrai eseguire il debug del comportamento di B prima di rendersi conto che il problema è in A. Questo è uno sforzo sprecato.

Inserisci i test unitari. Questi test confermano che la libreria A funziona come previsto. Se si introduce un bug nella libreria A che lo fa restituire risultati errati, i test unitari lo cattureranno. Pertanto, non rimarrete bloccati nel tentativo di eseguire il debug della libreria B.
Questo va oltre il vostro scopo, ma in uno sviluppo di integrazione continua, i test unitari vengono eseguiti ogni volta che qualcuno commette del codice, il che significa che saprete di aver rotto qualcosa al più presto.

Soprattutto per complicate operazioni matematiche, i test unitari possono essere una benedizione. Fai alcuni calcoli di esempio e poi scrivi unit test che confrontano l'output calcolato e l'output effettivo (sulla base degli stessi parametri di input).

Tuttavia, tieni presente che i test unitari non ti aiuteranno a creare un buon codice, ma piuttosto a mantenerlo . Se di solito scrivi il codice una volta e non lo rivedi mai, i test unitari saranno meno vantaggiosi.

Che ne dici di OOP?

OOP è un modo di pensare a entità distinte, ad esempio:

Quando un Customervuole acquistare un Product, parla con il Vendorper ricevere un Order. La Accountantvolontà poi pagare il Vendor.

Confronta questo con il modo in cui un programmatore funzionale pensa alle cose:

Quando un cliente lo desidera purchaseProduct(), talktoVendor()lo farà sendOrder()anche lui. Lo farà il consulente payVendor().

Mele e arance Nessuno dei due è oggettivamente migliore dell'altro. Una cosa interessante da notare è che per OOP Vendorè menzionato due volte ma si riferisce alla stessa cosa. Tuttavia, per la programmazione funzionale, talktoVendor()e payVendor()sono due cose separate.
Questo mostra la differenza tra gli approcci. Se tra queste due azioni è presente molta logica condivisa specifica del fornitore, OOP aiuta a ridurre la duplicazione del codice. Tuttavia, se non esiste una logica condivisa tra i due, fonderli in un singolo Vendorè un lavoro inutile (e quindi la programmazione funzionale è più efficiente).

Più spesso, i calcoli matematici e scientifici sono operazioni distinte che non si basano su logiche / formule condivise implicite. Per questo motivo, la programmazione funzionale viene utilizzata più spesso di OOP.

Quali tipi di approcci sono utili per scrivere rapidamente codice pulito e buono quando si fa "programmazione scientifica" anziché lavorare su progetti più grandi?

La tua domanda implica che la definizione di "codice buono e pulito" cambia indipendentemente dal fatto che tu stia facendo una programmazione scientifica o lavori su progetti più grandi (presumo che tu intenda impresa).

La definizione di buon codice non cambia. La necessità di evitare la complessità (che può essere fatta scrivendo un codice pulito), tuttavia, cambia.

Lo stesso argomento torna qui.

  • Se non riesci mai a rivisitare il vecchio codice e a comprendere appieno la logica senza la necessità di compartimentarla, allora non spendere eccessivamente per rendere le cose mantenibili.
  • Se rivisiti il ​​vecchio codice o la logica richiesta è troppo complessa per essere affrontata tutta in una volta (richiedendo quindi di compartimentare le soluzioni), quindi concentrati sulla scrittura pulita, riutilizzabile vicino.

Faccio queste domande perché spesso la programmazione stessa non è super complessa. È più sulla matematica o sulla scienza che sto testando o facendo ricerche con la programmazione.

Ho la distinzione che stai facendo qui, ma quando guardi indietro al codice esistente, stai guardando sia la matematica che la programmazione. Se uno dei due è inventato o complesso, allora avrai difficoltà a leggerlo.

Ad esempio, è necessaria una classe quando due variabili e una funzione potrebbero probabilmente occuparsene?

A parte i principi OOP, il motivo principale per cui scrivo le classi per ospitare alcuni valori di dati è perché semplifica la dichiarazione dei parametri del metodo e dei valori di ritorno. Ad esempio, se ho molti metodi che usano una posizione (coppia lat / lon), allora mi stancherò rapidamente di dover scrivere float latitude, float longitudee preferirò di gran lunga scrivere Location loc.

Questo è ulteriormente aggravato se si considera che i metodi generalmente restituiscono un valore (a meno che non esistano funzionalità specifiche della lingua per restituire più valori) e cose come una posizione vorrebbero che si restituissero due valori (lat + lon). Questo ti incentiva a creare una Locationclasse per semplificare il tuo codice.

Ad esempio, è necessaria una classe quando due variabili e una funzione potrebbero probabilmente occuparsene?

Un'altra cosa interessante da notare è che puoi usare OOP senza mescolare valori e metodi di dati. Non tutti gli sviluppatori sono d'accordo qui (alcuni lo chiamano antipattern), ma puoi avere modelli di dati anemici in cui hai classi di dati separate (campi di valori di negozi) e classi di logica (metodi di negozi).
Questo è, ovviamente, su uno spettro. Non devi essere perfettamente anemico, puoi usarlo quando lo ritieni appropriato.

Ad esempio, un metodo che concatena semplicemente il nome e il cognome di una persona può ancora essere ospitato nella Personclasse stessa, perché non è in realtà una "logica" ma piuttosto un valore calcolato.

(Considera che queste sono anche situazioni in cui si preferisce che la velocità del programma sia più rapida - quando stai eseguendo più di 25.000.000 di passi temporali di una simulazione, vuoi che lo sia.)

Una classe è sempre grande quanto la somma dei suoi campi. Prendendo l'esempio di Locationnuovo, che consiste di due floatvalori, è importante notare qui che un singolo Locationoggetto occuperà tutta la memoria di due floatvalori separati .

In questo senso, non importa se stai usando OOP o no. L'impronta di memoria è la stessa.

Anche le prestazioni in sé non sono un grosso ostacolo da attraversare. La differenza tra ad esempio l'uso di un metodo globale o un metodo di classe non ha nulla a che fare con le prestazioni di runtime, ma ha tutto a che fare con la generazione di bytecode in fase di compilazione.

Pensala in questo modo: se scrivo la mia ricetta per la torta in inglese o spagnolo non cambia il fatto che la torta impiegherà 30 minuti per cuocere (= prestazioni di runtime). L'unica cosa che cambia la lingua della ricetta è il modo in cui il cuoco mescola gli ingredienti (= compilando il bytecode).

Per Python in particolare, non è necessario precompilare esplicitamente il codice prima di chiamarlo. Tuttavia, quando non si esegue la pre-compilazione, la compilazione verrà eseguita quando si tenta di eseguire il codice. Quando dico "runtime", intendo l'esecuzione stessa, non la compilazione che potrebbe precedere l'esecuzione.


6

Vantaggi di un codice scientifico pulito

  • ... guardando i libri di programmazione, spesso sembrano indirizzati a progetti più grandi.

  • ... ha davvero senso scrivere codice che dice OOP quando alcune cose standard potrebbero essere state fatte, sarebbe stato molto più veloce da scrivere e sarebbe stato un livello simile di leggibilità a causa della brevità del programma?

Potrebbe essere utile considerare il tuo codice dalla prospettiva di un futuro programmatore.

  • Perché hanno aperto questo file?
  • Che cosa stanno cercando

Dalla mia esperienza,

Il codice pulito dovrebbe facilitare la verifica dei risultati

  • Consenti agli utenti di sapere esattamente cosa devono fare per eseguire il tuo programma.
  • Potresti voler dividere il tuo programma in modo che i singoli algoritmi possano essere confrontati separatamente.

  • Evitare di scrivere funzioni con effetti collaterali non intuitivi in ​​cui un'operazione non correlata provoca un comportamento diverso di un'altra operazione. Se non puoi evitarlo, documenta ciò di cui il tuo codice ha bisogno e come impostarlo.

Il codice pulito può servire da codice di esempio per i programmatori futuri

Commenti chiari (compresi quelli che mostrano come chiamare le funzioni) e funzioni ben separate possono fare una grande differenza nel tempo impiegato da qualcuno che sta appena iniziando (o in futuro tu) per ottenere qualcosa di utile dal tuo lavoro.

Oltre a ciò, creare una vera "API" per il tuo algoritmo può prepararti meglio se decidi di trasformare i tuoi script in una vera libreria per essere utilizzata da qualcun altro.

raccomandazioni

"Cita" formule matematiche usando i commenti.

  • Aggiungi commenti a "citare" formule matematiche, soprattutto se hai utilizzato ottimizzazioni (identità trig, serie di Taylor, ecc.).
  • Se hai preso la formula dal libro, aggiungi un commento dicendo che John Smith Method from Some Book 1st Ed. Section 1.2.3 Pg 180, se hai trovato la formula su un sito Web o in un documento, cita anche quello.
  • Consiglio di evitare i commenti "solo link", assicurati di fare riferimento al metodo per nome da qualche parte per consentire alle persone di cercarlo su Google, ho incontrato alcuni commenti "solo link" che reindirizzano a vecchie pagine interne e possono essere molto frustranti .
  • Puoi provare a digitare la formula nel tuo commento se è ancora facile da leggere in Unicode / ASCII, ma questo può diventare molto imbarazzante (i commenti in codice non sono LaTeX).

Usa i commenti con saggezza

Se puoi migliorare la leggibilità del tuo codice usando buoni nomi di variabili / nomi di funzioni, fallo prima. Ricorda che i commenti rimarranno per sempre fino a quando non li rimuovi, quindi prova a fare commenti che non saranno obsoleti.

Usa nomi di variabili descrittivi

  • Le variabili a lettera singola possono essere l'opzione migliore se fanno parte di una formula.
  • Potrebbe essere cruciale per i futuri lettori poter guardare il codice che hai scritto e confrontarlo con l'equazione che stai implementando.
  • Se del caso, considera l'aggiunta di un suffisso per descriverne il significato reale, ad es. xBar_AverageVelocity
  • Come accennato in precedenza, ti consiglio di indicare chiaramente la formula / metodo che stai usando per nome in un commento da qualche parte.

Scrivi il codice per eseguire il tuo programma con dati noti positivi e non validi.

Il test unitario è necessario per scrivere piccoli pezzi di codice?

Penso che i test unitari possano essere utili, penso che la migliore forma di test unitari per il codice scientifico sia una serie di test eseguiti su dati validi e negativi noti.

Scrivi un po 'di codice per eseguire l'algoritmo e controlla per vedere quanto il risultato si discosta da quello che ti aspetti. Questo ti aiuterà a trovare (potenzialmente molto male e difficile da trovare) problemi in cui accidentalmente codifichi qualcosa che causa un risultato falso positivo o commetti un errore che fa sì che la funzione restituisca sempre lo stesso valore.

Si noti che ciò può essere fatto a qualsiasi livello di astrazione. Ad esempio, è possibile testare un intero algoritmo di corrispondenza dei modelli oppure è possibile verificare una funzione che calcola solo la distanza tra due risultati nel processo di ottimizzazione. Inizia dalle aree che sono le più cruciali per i tuoi risultati prima e / o le parti del codice di cui sei più preoccupato.

Semplifica l'aggiunta di nuovi casi di test, considera l'aggiunta di funzioni di "supporto" e struttura i dati di input in modo efficace. Ciò può significare possibilmente salvare i dati di input su file in modo da poter rieseguire facilmente i test, anche se fai molta attenzione a evitare falsi positivi o casi di test distorti / banalmente risolti.

Prendi in considerazione l'idea di utilizzare la convalida incrociata , vedi questo post su convalida incrociata per ulteriori informazioni.

Usa il controllo versione

Vorrei raccomandare l'uso del controllo versione e l'hosting del repository su un sito esterno. Esistono siti che ospiteranno repository gratuitamente.

vantaggi:

  1. Fornisce un backup in caso di guasto del disco rigido
  2. Fornisce una cronologia, che ti impedisce di preoccuparti se un problema recente che è emerso è stato causato dalla modifica accidentale di un file, tra gli altri vantaggi.
  3. Ti consente di utilizzare la ramificazione, che è un buon modo di lavorare su codice a lungo termine / sperimentale senza influire sul lavoro non correlato.

Fare attenzione quando si copia / incolla il codice

Lo stile del mio codice è contorto ed è pieno di commenti obsoleti che notano modi alternativi per fare qualcosa o con righe di codice copiate.

  • Copia / incolla codice può farti risparmiare tempo, ma è una delle cose più pericolose che puoi fare, soprattutto se è un codice che non ti sei scritto (ad esempio, se è un codice di un collega).

  • Non appena il codice funziona e testato, ti consiglio di esaminarlo attentamente per rinominare qualsiasi variabile o commentare tutto ciò che non capisci.



6

Gli strumenti del mestiere sono di solito inventati per risolvere un bisogno. Se hai la necessità di utilizzare lo strumento, in caso contrario, molto probabilmente non è necessario.

In particolare, i programmi scientifici non sono l'obiettivo finale, sono i mezzi. Scrivi il programma per risolvere un problema che hai ora - non ti aspetti che il programma venga utilizzato da altri (e debba essere mantenuto) tra dieci anni. Che da solo significa che non si deve pensare in uno qualsiasi degli strumenti che consentono allo sviluppatore corrente per registrare la storia per gli altri, come il controllo di versione, o funzionalità di acquisizione in codice come unit test.

Cosa ti gioverebbe allora?

  • il controllo della versione è utile perché ti consente di eseguire facilmente il backup del tuo lavoro. A partire dal 2018 github è un posto molto popolare per farlo (e puoi sempre spostarlo in seguito, se necessario, git è molto flessibile). Un sostituto economico e semplice per i backup sono le procedure di backup automatico nel tuo sistema operativo (Time Machine per Mac, rsync per Linux ecc.). Il tuo codice deve essere in più punti!
  • I test unitari sono utili perché se li scrivi prima sei costretto a pensare a come controllare cosa fa effettivamente il codice, il che ti aiuta a progettare un'API più utile per il tuo codice. Questo è utile se ti capita di scrivere codice da riutilizzare in un secondo momento e ti aiuta a modificare un algoritmo perché sai che funziona in questi casi.
  • Documentazione. Impara a scrivere la documentazione corretta nel linguaggio di programmazione che usi (ad esempio javadoc per Java). Scrivi per il futuro tu. In questo processo, scoprirai che i nomi di variabili validi facilitano la documentazione. Iterate. Dai la stessa quantità di cura alla tua documentazione come fa un poeta per le poesie.
  • Usa buoni strumenti. Trova un IDE che ti aiuti e imparalo bene. Rifattorizzare come rinominare le variabili con un nome migliore è molto più semplice in questo modo.
  • Se hai colleghi, puoi utilizzare la revisione tra pari. Guardare e comprendere il tuo codice da un estraneo è la versione qui-e-ora del futuro per cui scrivi. Se il tuo peer non capisce il tuo codice, probabilmente non lo farai neanche più tardi.

In che modo questa risposta non ha ricevuto un voto? Ha adesso. Il nostro gruppo ha riscontrato che la revisione tra pari è uno degli strumenti più efficaci di tutti, molto più importante dei test unitari quando si tratta di codice scientifico. È facile commettere un errore quando si traduce in codice un insieme complesso di equazioni in un articolo di rivista scientifica. Gli scienziati e gli ingegneri spesso rendono programmatori estremamente poveri; la peer review può catturare le bruttezze architettoniche che rendono il codice difficile da mantenere / comprendere / usare.
David Hammen,

5

Oltre ai buoni consigli già qui, potresti prendere in considerazione lo scopo della tua programmazione e quindi ciò che è importante per te.

"È più sulla matematica o sulla scienza che sto testando o facendo ricerche con la programmazione."

Se lo scopo è quello di sperimentare e testare qualcosa per la tua comprensione e sai quali dovrebbero essere i risultati, allora il tuo codice è sostanzialmente un semplice lancio e il tuo approccio attuale può essere sufficiente, anche se potrebbe essere migliorato. Se i risultati non sono quelli previsti, è possibile tornare indietro e rivedere.

Tuttavia, se i risultati della tua codifica informano la direzione della tua ricerca e non sai quali dovrebbero essere i risultati , allora la correttezza diventa particolarmente importante. Un errore nel tuo codice potrebbe portarti a trarre conclusioni errate dal tuo esperimento con una serie di cattive implicazioni per la tua ricerca complessiva.

In tal caso, suddividere il codice in funzioni facilmente comprensibili e verificabili con i test unitari ti fornirà mattoni più solidi che ti daranno maggiore fiducia nei risultati e potrebbero salvarti da molte frustrazioni in seguito.


5

Per quanto il controllo della versione e il test unitario siano utili per mantenere organizzato e funzionale il tuo codice generale, nessuno dei due ti aiuta a scrivere codice più pulito.

  • Il controllo della versione ti permetterà di vedere come e quando il codice è diventato così disordinato.
  • I test unitari assicureranno che, nonostante il codice sia un casino completo, funziona ancora.

Se vuoi impedirti di scrivere codice disordinato, hai bisogno di uno strumento che funzioni dove si verificano i problemi: quando scrivi il codice. Un tipo popolare di strumento che fa si chiama linter. Non sono uno sviluppatore Python, ma sembra che Pylint potrebbe essere una buona opzione.

Una linter esamina il codice che hai scritto e lo confronta con una serie configurabile di best practice. Se la linter ha una regola che devono essere le variabili camelCasee ne scrivi una snake_case, la contrassegnerà come errore. I buoni linter hanno regole che vanno da "le variabili dichiarate devono essere utilizzate" a "La complessità ciclomatica delle funzioni deve essere inferiore a 3".

La maggior parte degli editor di codice può essere configurata per eseguire una linter ogni volta che si salva, o semplicemente durante la digitazione, e indica i problemi in linea. Se digiti qualcosa del genere x = 7, xverrà evidenziato, con un'istruzione per usare un nome più lungo e migliore (se è quello che hai configurato). Funziona come il controllo ortografico nella maggior parte dei programmi di elaborazione testi, rendendo difficile ignorarlo e aiutando a costruire abitudini migliori.


Questo dovrebbe avere molti più voti. +1
heather,

2
Ma, per l'amor del cielo, assicurati di sapere come configurare la linter su uno stile che ti piace, altrimenti ti farà impazzire con il suo agitarsi.
DrMcCleod,

4

Tutto ciò che hai elencato è uno strumento nella casella degli strumenti metaforico. Come qualsiasi cosa nella vita, strumenti diversi sono appropriati per compiti diversi.

Rispetto ad altri campi dell'ingegneria, il software funziona con un mucchio di singoli pezzi che da soli sono piuttosto semplici. Un'istruzione di assegnazione non viene valutata in modo diverso a seconda delle fluttuazioni di temperatura della stanza. Una ifdichiarazione non si corrode in posizione e continua a restituire la stessa cosa dopo un po '. Ma poiché i singoli elementi sono così semplici e il software creato dagli umani, questi elementi vengono combinati in pezzi sempre più grandi fino a quando il risultato diventa così grande e complesso da raggiungere i limiti di ciò che le persone possono gestire mentalmente.

Man mano che i progetti software sono cresciuti, le persone li hanno studiati e hanno creato strumenti per provare a gestire quella complessità. OOP è un esempio. Sempre più linguaggi di programmazione astratti sono un altro mezzo. Poiché gran parte del denaro nel software sta facendo di più , gli strumenti per ottenere ciò sono ciò che vedrai e leggerai. Ma sembra che quelle situazioni non si applichino a te.

Quindi, non si sentono come te bisogno di fare nulla di tutto ciò. Alla fine della giornata, il codice è solo un mezzo per raggiungere un fine. Sfortunatamente, ciò che ti darà la giusta prospettiva su ciò che è e ciò che non è appropriato è lavorare su alcuni progetti più grandi, poiché è molto più difficile sapere cosa manca quando la cassetta degli attrezzi è la tua mente.

In ogni caso, non mi preoccuperei di non usare OOP o altre tecniche purché i tuoi script siano piccoli. Molti dei problemi che hai descritto sono solo capacità organizzative professionali generali, ovvero non perdere un vecchio file è qualcosa che tutti i campi devono affrontare.


4

Oltre a tutti i buoni suggerimenti forniti finora, una pratica che ho imparato nel tempo e che trovo essenziale è quella di aggiungere in modo molto libero commenti dettagliati al tuo codice. È la cosa più importante per me quando torno a qualcosa dopo un lungo periodo di tempo. Spiega a te stesso cosa stai pensando. Ci vuole un po 'di tempo per farlo ma è relativamente facile e per lo più indolore.

A volte ho due o tre volte più righe di commenti rispetto al codice, specialmente quando i concetti o le tecniche sono nuovi per me e sopportano di spiegarmi.

Controlla la versione, migliora le tue pratiche, ecc .... tutto quanto sopra. Ma spiega le cose a te stesso mentre vai. Funziona davvero bene.


4

Quali qualità sono importanti per questo tipo di programma?

Probabilmente non importa se è facile mantenerlo o evolverlo, perché è probabile che non accadrà.

Probabilmente non importa quanto sia efficiente.

Probabilmente non importa se ha una grande interfaccia utente o se è sicuro contro gli aggressori dannosi.

Può importare che sia leggibile: che qualcuno che legge il tuo codice può facilmente convincersi che fa quello che sostiene di fare.

È certamente importante che sia corretto. Se il programma fornisce risultati errati, queste sono le tue conclusioni scientifiche fuori dalla finestra. Ma deve solo elaborare correttamente l'input che in realtà gli stai chiedendo di elaborare; non importa molto se cade se vengono dati valori di input negativi, se tutti i valori dei dati sono positivi.

È inoltre importante mantenere un certo livello di controllo delle modifiche. I tuoi risultati scientifici devono essere riproducibili e ciò significa che devi sapere quale versione del programma ha prodotto i risultati che intendi pubblicare. Poiché esiste un solo sviluppatore, il controllo delle modifiche non deve essere molto elaborato, ma è necessario assicurarsi di poter tornare indietro nel tempo e riprodurre i risultati.

Quindi non preoccuparti di programmare paradigmi, orientamento agli oggetti, eleganza algoritmica. Preoccupati della chiarezza e della leggibilità e della tracciabilità delle tue modifiche nel tempo. Non preoccuparti dell'interfaccia utente. Non preoccuparti di testare ogni possibile combinazione di parametri di input, ma fai abbastanza test per essere fiducioso (e per rendere gli altri fiduciosi) che i tuoi risultati e conclusioni siano validi.


4

Ho lavorato in un ambiente simile con accademici che scrivono molto codice (matematica / scienza) ma i loro progressi sono lenti a causa degli stessi motivi che descrivi. Tuttavia ho notato una cosa particolare che è andata bene e che penso possa anche aiutarti: creare e mantenere una raccolta di librerie specializzate che possono essere utilizzate in più progetti. Queste librerie dovrebbero fornire funzioni di utilità e aiuteranno pertanto a mantenere il progetto corrente specifico per il dominio problematico.

Ad esempio, potresti dover affrontare molte trasformazioni coordinate nel tuo campo (ECEF, NED, lat / lon, WGS84 ecc.), Quindi una funzione come convert_ecef_to_ned()dovrebbe andare in un nuovo progetto chiamato CoordinateTransformations. Metti il ​​progetto sotto controllo della versione e ospitalo sui server del tuo dipartimento in modo che altre persone possano usarlo (e si spera lo migliorino). Quindi dopo alcuni anni dovresti avere una solida raccolta di librerie con i tuoi progetti contenenti solo codice specifico per un particolare problema / dominio di ricerca.

Alcuni consigli più generali:

  • Punta sempre a modellare il tuo problema particolare nel modo più accurato possibile, qualunque esso sia. In questo modo le domande di progettazione del software come cosa / dove / come inserire una variabile dovrebbero diventare molto più ovvie a cui rispondere.
  • Non mi preoccuperei dello sviluppo guidato dai test poiché il codice scientifico descrive idee e concetti ed è più creativo e fluido; non ci sono API da definire, servizi da mantenere, rischi per il codice di altre persone quando si cambia funzionalità ecc.

Non lasciare che altre persone lo migliorino. È probabile che non capiscano lo scopo del codice e rovineranno semplicemente le cose.
matematico

@mathreadler Beh, se sono librerie di utilità generali, allora sarà un po 'difficile per gli altri sbagliare, questa è l'idea.
jigglypuff,

Perché è difficile rovinare le librerie di uso generale? Non è così difficile se non hai idea di cosa stai facendo, o se ci provi davvero tanto, sia per quello.
matematico

@mathreadler Perché in genere esiste un solo modo per coordinare trasformazioni o conversioni di unità, ad esempio.
jigglypuff,

Di solito ci sono molti modi a seconda di come i tuoi numeri sono memorizzati, quale rappresentazione usano e molte altre cose, per quale CPU intendi compilare la libreria. Un programmatore può presumere che tutti useranno sempre i doppi IEEE per esempio, ma un altro usa quasi sempre una precisione singola o un terzo formato più strano. Un programmatore utilizzerà quindi il polimorfismo modello, ma un altro potrebbe essere allergico ad esso, un terzo ancora più strano sarà quello di codificare tutto in basso livello o assemblaggio.
matematico

3

Quelle che seguono sono le mie opinioni e molto influenzate dal mio percorso particolare.

La codifica spesso genera prospettive dogmatiche su come dovresti fare le cose. Invece di tecniche e strumenti, penso che sia necessario esaminare i valori e i costi cumulativi per decidere una strategia appropriata.

Scrivere codice valido, leggibile, debuggable e solido richiede molto tempo e fatica. In molti casi, dato un orizzonte di pianificazione limitato, non vale la pena farlo (analisi paralisi).

Un collega aveva una regola empirica; se stai facendo essenzialmente lo stesso genere di cose per la terza volta, investi gli sforzi, altrimenti è appropriato un lavoro veloce e sporco.

Test di qualche tipo è essenziale, ma per i progetti una tantum, semplicemente il bulbo oculare può essere sufficiente. Per qualsiasi cosa sostanziale, test e infrastrutture di test sono essenziali. Il valore è che ti libera durante la codifica, il costo è che se il test si concentra su una particolare implementazione, anche i test hanno bisogno di manutenzione. I test ti ricordano anche come dovrebbero funzionare le cose.

Per i miei script una tantum (spesso per cose come la convalida di una stima di probabilità o simili), ho trovato due piccole cose molto utili: 1. Includere un commento che mostra come viene utilizzato il codice. 2. Includi una breve descrizione del motivo per cui hai scritto il codice. Queste cose sono tremendamente ovvie quando scrivi il codice, ma l'ovvietà si perde nel tempo :-).

OOP riguarda il riutilizzo del codice, l'astrazione, l'incapsulamento, il factoring, ecc. Molto utile, ma facile da perdere se la produzione di codice e design di qualità non è il tuo obiettivo finale. Ci vuole tempo e fatica per produrre materiale di qualità.


3

Mentre penso che i test unitari abbiano i loro meriti, hanno un valore dubbio per lo sviluppo scientifico - spesso sono troppo piccoli per offrire un sacco di valore.

Ma mi piacciono molto i test di integrazione per il codice scientifico:

Isolare una piccola parte del codice che potrebbe funzionare autonomamente, ad esempio la pipeline ETL. Quindi scrivere un test che fornisce i dati, eseguire la pipeline etl (o solo un passaggio) e quindi verificare che il risultato corrisponda alle proprie aspettative. Mentre il blocco testato può contenere molto codice, il test fornisce ancora valore:

  1. È disponibile un hook conveniente per rieseguire il codice, il che aiuta a eseguirlo spesso.
  2. Puoi testare alcuni presupposti nel tuo test
  3. Se qualcosa si rompe, è facile aggiungere un test non riuscito ed effettuare la correzione
  4. Si codificano gli input / output previsti, evitando il solito mal di testa che deriva dal tentativo di indovinare il formato dei dati di input.
  5. Sebbene non siano snelli come i test unitari, i test IT aiutano ancora a separare il codice e ti costringono ad aggiungere alcuni limiti al codice.

Sto usando questa tecnica spesso e finisco spesso con una funzione principale relativamente leggibile, ma le sotto-funzioni sono spesso piuttosto lunghe e brutte, ma possono essere modificate e riorganizzate rapidamente a causa di solidi confini I / O.


2

Normalmente lavoro su una base di origine molto ampia. Utilizziamo tutti gli strumenti che menzioni. Di recente, ho iniziato a lavorare su alcuni script Python per un progetto secondario. Sono al massimo da poche decine a poche centinaia di righe. Per abitudine, ho affidato i miei script al controllo del codice sorgente. Questo è stato utile perché posso creare rami per provare esperimenti che potrebbero non funzionare. Posso fork se devo duplicare il codice e modificarlo per un altro scopo. Ciò lascia intatto l'originale nel caso in cui dovessi farlo uscire di nuovo.

Per "unit test" ho solo alcuni file di input che hanno lo scopo di produrre alcuni output noti che controllo a mano. Probabilmente potrei automatizzarlo, ma sembra che ci vorrebbe più tempo per farlo che risparmierò facendolo. Probabilmente dipende dalla frequenza con cui devo modificare ed eseguire gli script. Ad ogni modo, se funziona, fallo. Se è più un problema che un problema, non perdere tempo.


2

Con la scrittura del codice - come con la scrittura in generale - la domanda principale è:

Quale lettore hai in mente? o Chi consuma il tuo codice?

Cose come le linee guida formali di programmazione non hanno senso quando sei il tuo unico pubblico.

Detto questo, d'altra parte, sarebbe utile scrivere il codice in un modo, il tuo futuro è in grado di capirlo subito.

Quindi un "buon stile" sarebbe uno, che ti aiuta di più. Quello che dovrebbe assomigliare a quello stile è una risposta che non posso dare.

Penso che non hai bisogno di OOP o unit test per file di 150 LOC. Un VCS dedicato sarebbe interessante quando si dispone di un codice in evoluzione. Altrimenti a .bakfa il trucco. Questi strumenti sono una cura per un malessere, che potresti anche non avere.

Forse dovresti scrivere il tuo codice in modo tale che, anche se lo leggi mentre sei ubriaco, sei in grado di leggerlo, comprenderlo e modificarlo.

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.