Qual è il giusto equilibrio tra coerenza del codice e miglioramento del codice?


53

Di recente ho avuto una discussione con un collega sullo stile del codice. Stava sostenendo che il tuo utilizzo delle API e i modelli generali che stai usando dovrebbero essere il più simili possibile con il codice circostante, se non con la base di codice nel suo insieme, proprio come faresti con l'aspetto del codice (posizionamento parentesi, maiuscole, ecc.) . Ad esempio, se aggiungessi un metodo a una classe DAO in C #, proverei a utilizzare LINQ dove appropriato per aiutare a rendere il mio codice pulito e facile da mantenere, anche se nessuno degli altri metodi di quella classe lo utilizzava. Tuttavia, il mio collega sosterrebbe che non dovrei usarlo in quel caso perché sarebbe contrario allo stile esistente di quella classe e quindi più difficile da capire.

All'inizio ho trovato la sua posizione piuttosto estrema, ma dopo aver riflettuto per un po 'sto cominciando a capire il suo punto. Con l'ipotetico esempio LINQ, forse questa classe non lo contiene perché i miei colleghi non hanno familiarità con LINQ? In tal caso, il mio codice non sarebbe più gestibile per i miei colleghi sviluppatori se non lo usassi? D'altra parte, se credo davvero che l'uso di una tale tecnica comporterebbe un codice più pulito, non dovrei usarlo anche se differisce drasticamente dal codice circostante?

Penso che il punto cruciale dell'argomento del mio collega sia che se tutti noi implementiamo funzionalità simili in una base di codice in modi diversi, e ognuno di noi pensa che il nostro modo sia "migliore", alla fine il codice nel suo insieme diventa solo più difficile capire. Tuttavia, al momento penso ancora che se seguiamo troppo alla cieca il codice esistente, la qualità peggiorerà lentamente nel tempo.

Quindi, in che misura i pattern fanno parte dello stile del codice e dove dovremmo tracciare la linea tra rimanere coerenti e apportare miglioramenti?


14
Quindi come migliorerebbe mai la qualità del codice, se tutti si limitassero al modo "cattivo"?
Izkata,


Quando scrivi LINQ non intendi LINQ to SQL? Se sì, allora potrei essere d'accordo con il tuo amico. Se stai parlando di LINQ, allora non sono d'accordo con lui. Oggigiorno tutti devono conoscere LINQ. Non è una loro scelta. Sono pagati per essere familiari.
Piotr Perak,

@Peri Intendevo solo LINQ di base - immagino che il mio esempio avrebbe dovuto essere lavorare con IEnumerables piuttosto che con un DAO.
Robert Johnson,

2
ICYMI - Fumetti di Dilbert proprio su questo argomento: dilbert.com/strips/2013-09-21
Jim Bethancourt,

Risposte:


53

Per dare una risposta più generale:

In un caso come questo, hai due "best practice" di programmazione che si contrappongono: la coerenza del codice è importante, ma lo è anche scegliere il metodo migliore per eseguire il tuo compito. Non esiste una risposta corretta a questo dilemma; dipende da un paio di fattori:

  • Quanto è vantaggioso il modo "corretto"?

    • A volte la migliore e nuova best practice aumenterà notevolmente le prestazioni, eliminerà i bug, sarà molto più facile da programmare, ecc. In tal caso, mi spingerei pesantemente verso l'uso del nuovo metodo. D'altra parte , il "modo corretto" può essere poco più dello zucchero sintattico o un metodo idiomatico concordato di fare qualcosa che non è realmente superiore. In tal caso, la coerenza del codice è probabilmente più importante.
  • Quanto grande di un problema creerebbe l'incoerenza?

    • Quanto è interconnesso il nuovo codice con il codice legacy? Il tuo nuovo codice fa parte di una libreria? Crea un oggetto che viene passato a molte parti del programma? In casi come questi, la coerenza è molto importante. L'uso di una nuova API o di un nuovo modo di fare le cose in generale, potrebbe creare risultati leggermente diversi che infrangono ipotesi altrove nel programma. D'altra parte , se stai scrivendo un pezzo di codice abbastanza isolato, l'incoerenza ha meno probabilità di essere un problema.
    • Quanto è grande e matura la tua base di codice? Quanti sviluppatori devono capirlo e lavorarci sopra? Gli standard concordati e coerenti sono molto più importanti per i progetti più grandi.
    • Il codice deve essere eseguito in ambienti meno recenti che potrebbero non supportare le funzionalità più recenti?

In base al bilancio di questi problemi, devi fare la scelta giusta su quale percorso prendere. Personalmente vedo poco valore nella coerenza per motivi di coerenza e preferirei utilizzare i metodi migliori e più recenti a meno che non ci sia un costo significativo per farlo.

Naturalmente, c'è una terza opzione: riscrivere il codice esistente in modo che utilizzi i metodi migliori e sia coerente. Ci sono momenti in cui ciò è necessario, ma comporta un costo elevato.


7
Risposta molto buona ed equilibrata (+1). Nella mia esperienza, un team può essere più produttivo se tutti concordano su un insieme relativamente piccolo di idiomi ben compresi che se ogni membro del team è libero di usare idiomi nuovi e diversi che altri membri del team potrebbero trovare difficili da capire. Un piccolo punto riguardante l'affermazione "riscrivere il codice esistente in modo che utilizzi le pratiche più recenti e sia coerente": "latest" non implica automaticamente "migliore". Bisogna sempre decidere caso per caso.
Giorgio

1
@Giorgio, grazie per il feedback; Ho modificato la lingua del punto finale.

6
Buona risposta e buon commento di Giorgio. Forse vale la pena considerare che in questo esempio, a seconda della squadra / cultura, è possibile avere un'adozione coordinata della squadra piuttosto che iniziare da soli. Ciò riduce alcuni dei rischi legati alla manutenzione (poiché tutti sono a bordo). (es. poni più enfasi sulle capacità effettive della squadra al momento, piuttosto che sul codice che hanno scritto in passato.)
Matt

+1. D'accordo, una risposta completa ed equilibrata - buona considerazione di una varietà di scenari. Ancora una volta, buon commento di Giorgio.
Robert Johnson,

1
Per quanto riguarda l'adozione di nuovi modi di dire, le tecnologie: considererei l'inizio di un nuovo progetto come un buon punto per introdurre nuove cose. Successivamente, durante l'intera durata del progetto, lo stile del codice dovrebbe essere mantenuto il più coerente possibile. Di recente ho dovuto estrarre un codice di 5 anni ed ero molto contento di poter ancora trovare la strada attraverso quel codice perché l'intero team ha aderito a un'architettura e un insieme di idiomi comuni su cui avevamo concordato (non senza lotta! ) all'inizio del progetto.
Giorgio,

26

Rimanere coerente ha poco valore nella mia prospettiva; apportare continuamente miglioramenti è d'obbligo.

La posizione del tuo collega impedisce davvero l'innovazione. L'argomento di coerenza ti porta in una situazione in cui puoi utilizzare, ad esempio, LINQ solo se esegui la migrazione di tutto il codice per utilizzare LINQ. E bene, non abbiamo tempo per questo, vero?

Preferirei essere incoerente in cui un po 'di codice sta ancora eseguendo foreachsu ArrayLists e altre parti usano LINQ IEnumerable<T>, invece di attenersi al modo più vecchio di fare le cose fino alla fine dei tempi.

È responsabilità dei colleghi rimanere pertinenti e apprendere nuovi modi di fare le cose.


3
"È responsabilità dei colleghi rimanere pertinenti e apprendere nuovi modi di fare le cose.": Una possibilità è quella di usare nuovi modi di dire e librerie nel nuovo codice (nuovi moduli) e mantenere uno stile coerente nel codice legacy.
Giorgio

8

La coerenza delle API è molto importante, sia per le API pubbliche che per quelle interne.

La coerenza della formattazione del codice è importante e dovrebbe essere idealmente imposta dallo strumento di formattazione automatica con le stesse regole di formattazione per tutti. Semplifica la vita con codebase condivise controllate dalla versione.

Le convenzioni di denominazione dovrebbero essere coerenti, anche per cose come le variabili locali ecc.

Gli strumenti di analisi statica dovrebbero essere usati per applicare certe altre convenzioni e buone pratiche (ma non seguire ciecamente le impostazioni predefinite di tali strumenti, le impostazioni predefinite possono talvolta rasentare il pazzo), anche se se qualcosa lo richiede, non aver paura di disabilitarne alcune controlla (di solito con una direttiva all'interno di un commento) temporaneamente, se puoi giustificarlo.

Ciò che accade all'interno di funzioni / metodi , a parte quanto sopra elencato, non deve essere coerente in alcun modo, purché sia ​​un buon codice da solo . Un codice valido , comprensibile e ben commentato è molto importante, ma dopo ciò, se il compilatore e gli strumenti di analisi statica pensano che sia un codice coerente, è abbastanza coerente.


Per quanto riguarda le nuove funzionalità linguistiche come LINQ (beh, non è esattamente una novità), l'unica cosa che deve essere presa in considerazione è, una nuova versione sufficiente di lingua / librerie sarà in uso ovunque dove verrà utilizzato il codice? In caso di dubbi, attenersi alle funzioni che sono note per essere compatibili. Questo ovviamente non ti impedisce di inviare un aggiornamento di versione in tutto il sistema, quindi puoi iniziare a usare le nuove cose carine.

E ogni sviluppatore che lavora con il codice dovrebbe tenersi aggiornato, quindi qualsiasi sviluppatore .NET dovrebbe conoscere LINQ e, in caso contrario, dovrebbe essere costretto a imparare, per il proprio bene (non si sa mai quando si cercherà un nuovo lavoro in questo settore, per una ragione o per l'altra).


6

La risposta è semplice.

La coerenza è di fondamentale importanza.

ma viene fornito con un avvertimento ...

Tu e il tuo collega siete probabilmente ossessionati dal tipo sbagliato di coerenza

Le implementazioni sono usa e getta. Possono essere completamente revisionati con vari gradi di facilità a seconda della qualità e della completezza della suite di test. Preoccuparsi di cose come "Dovrebbe essere una proprietà?", "Il codice non dovrebbe usare LINQ invece di un costrutto di livello inferiore?" ha un valore dubbio. È molto difficile legare qualsiasi misura al valore della coerenza a livello di implementazione. Una domanda molto migliore da porre a questo livello è "Questo codice funziona come pubblicizzato?". TL; coerenza nell'implementazione della DR è dove le "piccole menti" si mettono in mostra.

Perché la coerenza non è così importante qui? Le implementazioni di solito hanno un numero esiguo di collaboratori. La maggior parte dei metodi sono scritti e mai più toccati. Del codice rimanente il numero di metodi che hanno due contributori quasi certamente una maggioranza. Questo modello continua all'infinito . In questo contesto la coerenza non è poi così importante. Se la durata di conservazione del codice è piuttosto ridotta ( alcuni anni ), i guadagni derivanti dalla coerenza aggressiva sono probabilmente un fattore irrilevante.

Questo non vuol dire che dovresti impazzire nelle tue implementazioni. Piuttosto è per dire che un design piacevole, pulito e semplice sarà ordini di grandezza più preziosi per il tuo ipotetico manutentore futuro rispetto alla stupida consistenza della piastra della caldaia metodo per metodo. Questo ci porta al punto reale ...

Le API non sono usa e getta.

Questo è tutto il livello di codice API, servizi web, SDK ecc. Questi devono, devono, DEVONO essere coerenti. I guadagni di produttività derivanti da questa varietà di coerenza sono enormi per una serie di motivi:

Test di integrazione:

Se mantieni coerenti le tue API, puoi creare suite di test di integrazione. Ciò consente agli sviluppatori di scambiare liberamente i dettagli dell'implementazione e ottenere una convalida immediata. Vuoi scambiare le tue cazzate con LINQ? I test di integrazione vengono eseguiti? Fornisce anche la convalida quando si prepara per andare alla produzione. Poiché i computer sono veloci, un singolo laptop può preformare il lavoro di mille tester che eseguono attività banali. È equivalente ad aumentare considerevolmente il personale dell'organizzazione.

Produttività

Quando le API sono coerenti, puoi fare ipotesi su come utilizzare un'API semplicemente seguendo ciò che hai appreso sull'uso di altre parti dell'API. Questo perché l'API offre un "look and feel" naturale e coerente. Significa che il tuo cliente impiega meno tempo a vagliare la documentazione. L'onboarding è più semplice ed economico. Vengono poste meno domande alle persone che hanno sviluppato l'API. La coerenza rende tutti vincitori

Perché la coerenza è importante in questo scenario? Perché le API hanno l'esatto problema opposto delle implementazioni. Il numero di persone che li utilizzano è in genere molto maggiore del numero di persone che contribuiscono alle loro implementazioni. I piccoli guadagni derivanti da una certa coerenza vengono moltiplicati e i costi di mantenimento di tale coerenza vengono ammortizzati.

Conclusione

La coerenza è costosa. A prima vista, riduce la produttività. Limita gli sviluppatori e rende le loro vite più difficili. Pone delle limitazioni sui modi in cui possono risolvere un problema a volte costringendoli a risolverlo in modo non ottimale. Questo è spesso per motivi che non comprendono, sono mal concepiti o non sono a conoscenza di (contratti, politiche organizzative o interorganizzative più grandi).

Raymond Hettinger ha fatto alcuni punti eccellenti nel suo discorso di Pycon 2015 sull'uso della guida di stile PEP8 per i team di programmatori Python. Ha dimostrato che l'ossessione per la coerenza stilistica su un pezzo di codice ha fatto sì che i revisori del codice manchino gravi problemi logici e di progettazione. La sua ipotesi può essere riassunta in quanto è facile trovare incoerenze stilistiche; determinare la qualità reale di un pezzo di codice è difficile

Il punto qui è critico. Individua dove è importante la coerenza e proteggila in modo aggressivo. Dove non è importante non perdere tempo. Se non sei in grado di fornire un modo oggettivo per misurare il valore della coerenza (nei casi di cui sopra "contabilità effettiva", costo in funzione della produttività) e non riesci a dimostrare che i rendimenti sono sostanziali, probabilmente stai facendo un disservizio a la tua organizzazione.


4

Non hai familiarità con LINQ? In tal caso, il mio codice non sarebbe più gestibile per i miei colleghi sviluppatori se non lo usassi?

Il linguaggio C # è ancora in evoluzione. Se le persone non imparassero le modifiche da C # 1, si perderebbero:

  • Generics
  • parziali
  • Metodi anonimi
  • iteratori
  • Tipi nullabili
  • Auto-proprietà
  • Tipi anonimi
  • Metodi di estensione
  • Ling
  • lambda
  • Metodi asincroni

Questa è solo una piccola selezione di funzioni comuni trovate nell'articolo di Wikipedia. Il punto che sto sottolineando è che se gli sviluppatori non imparano, la base di codice rimarrà nel vuoto. Si spera che i tuoi sviluppatori migliorino continuamente e che la base di codice evolva, supportata da una suite di test completa. Ti ricordi quanto è stato male con .NET 1 implementare manualmente le proprietà.

Linq semplifica la vita. Usalo dove puoi. Potresti motivare i membri del tuo team.

Quindi, in che misura i pattern fanno parte dello stile del codice e dove dovremmo tracciare la linea tra rimanere coerenti e apportare miglioramenti?

I miglioramenti sono graduali. Per me, non ha senso mantenere il vecchio codice di stile che è meno leggibile e potenzialmente meno gestibile. I membri del tuo team dovrebbero almeno essere in grado di capire cosa sta succedendo, anche se non riescono a scriverlo.


2
Sono pienamente d'accordo con quello che stai dicendo, ma la mia domanda è meno sulle nuove funzionalità linguistiche e la responsabilità degli sviluppatori di impararle, ma piuttosto la questione più generale di risolvere problemi simili in modi diversi all'interno di un'area relativamente piccola di una base di codice. Ho citato LINQ perché è un buon esempio di utilizzo di un modo diverso di risolvere un problema. Il mio collega è felice di usare nuove funzionalità linguistiche, ma solo se non vanno contro il granello del codice su cui sta lavorando.
Robert Johnson,

@RobertJohnson Vorrei sottolineare al collega che la manutenibilità supera la coerenza, quindi anche se qualcosa "va contro il grano" del codice esistente, se è più mantenibile, dovresti farlo. Dubito che l'uso del vecchio soggiorno DAO sarebbe più gestibile di Linq.
Andy,

3

Dovresti essere coerente, ma non necessariamente con il vecchio codice. Cioè, il tuo team dovrebbe essere d'accordo sul modo giusto di fare qualcosa e usarlo ogni volta che viene scritto un nuovo codice o vengono apportate modifiche sostanziali.

In questo modo, ottieni la maggior parte dei vantaggi della coerenza (in particolare, non importa chi scrive il codice), ma puoi comunque migliorare se il team vede un chiaro vantaggio facendo le cose in un altro modo.

In un mondo ideale, riscrivremmo tutto il vecchio codice per conformarci al nuovo modo giusto. Anche se questo non è economicamente valido, dovrebbe avere un senso tecnico (o altrimenti, il nuovo modo non è un modo migliore) e il tuo piano a lungo termine dovrebbe essere quello di aggiornarlo. Se non ne vale la pena, non preoccuparti del nuovo modo. Detto in altri termini, ci deve essere un chiaro vantaggio nel nuovo modo di considerare di dichiararlo giusto.


1

Penso che sia importante seguire uno stile di codice in un progetto, ad es. convenzioni di denominazione, rientri ecc.

Non sono d'accordo sul fatto che dovresti essere limitato dall'uso di nuovi costrutti linguistici o modelli di progettazione semplicemente perché i tuoi colleghi non li capiscono o non sono mai stati utilizzati nel progetto prima.

Naturalmente queste cose dovrebbero avere buoni motivi per usarle ad es. prestazione o brevità, se del caso.


A cosa serviva il meno?
ozz

0

Starei estremamente attento alla cieca seguendo gli attuali modelli di progettazione. Naturalmente, quando non vi è alcun aspetto negativo apprezzabile, si dovrebbe sempre tentare di seguire modelli simili attraverso la base di codice.

Tuttavia, è fin troppo facile entrare in circostanze in cui la maggior parte degli sviluppatori si sente come se fosse "la migliore pratica" seguire i modelli cattivi esistenti che portano a situazioni in cui i buoni sviluppatori scrivono codice cattivo, non mantenibile.

D'altra parte, la coerenza è importante. Quando si ha a che fare con basi di codice enormi, è estremamente utile provare a seguire schemi simili in modo che anche se gli sviluppatori non hanno letto ogni centimetro della base di codice, sanno cosa aspettarsi.

Pertanto, credo che sia meglio stabilire e aggiornare le linee guida su quali schemi gli sviluppatori dovrebbero seguire se in grado. In questo modo, qualsiasi nuovo codice è coerente con altri nuovi codici.


-1

Abbiamo incontri tecnici regolari, abbastanza informali, che ognuno di noi può organizzare. Se troviamo una nuova tecnica, la presentiamo all'incontro. Generalmente hai una buona sensazione di quanto l'idea sia accettata e compresa dai tuoi colleghi. Questo ti aiuta a decidere se è saggio includerlo nel tuo codice, aiuta gli altri a decifrarlo se lo fai e stabilisce anche la persona "vai a" se altri hanno bisogno di aiuto con quello stile. Se nessuno reinventasse la ruota, avremmo ancora trascinato roba sui registri.


A cosa serviva il downvote?
Formica

Non ero il downvoter, ma questo non sembra davvero mirato alla domanda. È un processo di gruppo che potrebbe essere applicato a qualsiasi cosa, piuttosto che una discussione su problemi di stile di codifica. Inoltre, non intendi "inventato la ruota" piuttosto che "reinventata la ruota"?

Suppongo sia semantico se un tronco è o meno un tipo di ruota, ma ruota, riduce l'attrito, ha una superficie che è in contatto successivo con il terreno ed è rotonda. Sono sicuro che ci sono esempi migliori di ciò che intendo: lo lascerò come esercizio per il lettore.
Formica
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.