In quali casi il codice non è migliore? [chiuso]

54

Recentemente ho rifatto il codice al lavoro e ho pensato di aver fatto un buon lavoro. Ho scaricato 980 righe di codice a 450 e dimezzato il numero di classi.

Quando mostravo questo ai miei colleghi, alcuni non erano d'accordo sul fatto che si trattava di un miglioramento.

Hanno detto - "meno linee di codice non sono necessariamente migliori"

Vedo che potrebbero esserci casi estremi in cui le persone scrivono davvero lunghe righe e / o mettono tutto in un unico metodo per salvare poche righe, ma non è quello che ho fatto. Il codice è a mio parere ben strutturato e più semplice da comprendere / mantenere a causa del fatto che è metà delle dimensioni.

Sto faticando a capire perché qualcuno vorrebbe lavorare con il doppio del codice necessario per ottenere un lavoro, e mi chiedo se qualcuno si sente uguale ai miei colleghi e può fare dei buoni casi per avere più codice su meno?

    
posta PiersyP 31.08.2017 - 01:51
fonte

10 risposte

123

Una persona magra non è necessariamente più sana di una persona sovrappeso.

Una storia per bambini di 980 linee è più facile da leggere di una tesi di fisica di 450 righe.

Ci sono molti attributi che determinano la qualità del tuo codice. Alcuni sono semplicemente calcolati, come Cyclomatic Complexity e Halstead Complexity . Altri sono definiti in modo più approssimativo, come coesione , leggibilità, comprensibilità, estendibilità, robustezza, correttezza, autodocumentazione , pulizia, testabilità e molti altri.

Potrebbe essere, ad esempio, che mentre riduci la lunghezza complessiva del codice, hai introdotto un'ulteriore complessità ingiustificata e reso il codice più criptico.

Dividere una lunga porzione di codice in piccoli metodi potrebbe essere dannoso quanto potrebbe essere utile .

Chiedi ai tuoi colleghi di fornirti un feedback specifico sul motivo per cui pensano che i tuoi sforzi di refactoring abbiano prodotto un risultato indesiderato.

    
risposta data 31.08.2017 - 02:32
fonte
35

È interessante notare che un collega ed io siamo attualmente nel mezzo di un refactoring che aumenterà il numero di classi e funzioni di poco meno del doppio, sebbene le linee di codice rimangano attorno allo stesso . Quindi mi capita di avere un buon esempio.

Nel nostro caso, avevamo uno strato di astrazione che in realtà avrebbe dovuto essere due. Tutto era stipato nel livello dell'interfaccia utente. Suddividendolo in due livelli, tutto diventa più coeso, e testare e mantenere i singoli pezzi diventa molto più semplice.

Non è la taglia del codice che disturba i tuoi colleghi, è un'altra cosa. Se non riescono ad articolarla, prova a guardare il codice come se non avessi mai visto la vecchia implementazione, e valutala per i suoi meriti piuttosto che in confronto. A volte, quando eseguo un lungo refactoring, perdo di vista l'obiettivo originale e porto le cose troppo lontano. Prendi un aspetto critico di "immagine grande" e rimettilo in carreggiata, magari con l'aiuto di un programmatore di coppia di cui valuti i consigli.

    
risposta data 31.08.2017 - 05:22
fonte
18

Mi viene in mente una citazione, spesso attribuita ad Albert Einstein:

Make everything as simple as possible, but not simpler.

Quando esagerate nel tagliare le cose, può rendere il codice più difficile da leggere. Poiché "facile / difficile da leggere" può essere un termine molto soggettivo, spiegherò esattamente cosa intendo con questo: una misura del grado di difficoltà che uno sviluppatore esperto avrà nel determinare "cosa fa questo codice?" semplicemente guardando la fonte, senza l'aiuto di strumenti specializzati.

Lingue come Java e Pascal sono infame per la loro verbosità. Le persone spesso puntano a determinati elementi sintattici e affermano con derisione che "sono lì solo per semplificare il lavoro del compilatore". Questo è più o meno vero, tranne che per la parte "giusta". Più informazioni esplicite sono presenti, più facile è leggere e comprendere il codice, non solo da un compilatore ma anche da un essere umano.

Se dico var x = 2 + 2; , è immediatamente ovvio che x dovrebbe essere un numero intero. Ma se dico var foo = value.Response; , è molto meno chiaro che cosa rappresenti foo o quali siano le sue proprietà e capacità. Anche se il compilatore può facilmente dedurlo, mette molto più sforzo cognitivo su una persona.

Ricorda che i programmi devono essere scritti affinché le persone possano leggerli e solo incidentalmente per le macchine da eseguire. (Ironia della sorte, questa citazione proviene da un libro di testo dedicato a una lingua infame per essere estremamente difficile da leggere!) È una buona idea rimuovere le cose che sono ridondanti, ma non togliere il codice che rende più facile per i tuoi simili capire cosa sta succedendo, anche se non è strettamente necessario per la scrittura del programma.

    
risposta data 31.08.2017 - 06:08
fonte
15

Il codice più lungo può essere più facile da leggere. Di solito è il contrario, ma ci sono molte eccezioni, alcune delle quali sono delineate in altre risposte.

Ma guardiamo da un'angolazione diversa. Presumiamo che il nuovo codice sarà considerato superiore dalla maggior parte dei programmatori esperti che vedono i 2 pezzi di codice senza avere una conoscenza aggiuntiva della cultura aziendale, della base di codice o della roadmap. Anche allora, ci sono molti motivi per obiettare sul nuovo codice. Per brevità chiamerò "Persone che criticano il nuovo codice" Pecritenc :

  • Stabilità. Se il vecchio codice era noto per essere stabile, la stabilità del nuovo codice è sconosciuta. Prima che il nuovo codice possa essere utilizzato, deve ancora essere testato. Se per qualche ragione non sono disponibili test adeguati, il cambiamento è un problema piuttosto grande. Anche se i test sono disponibili, Pecritenc potrebbe pensare che lo sforzo non valga il (minore) miglioramento del codice.
  • Spettacolo / ridimensionamento. Il vecchio codice potrebbe essere ridimensionato in modo migliore e Pecritenc presume che le prestazioni diventeranno un problema lungo la strada quando i client e le funzionalità presto si accumuleranno.
  • estensibilità. Il vecchio codice potrebbe aver consentito una facile introduzione di alcune funzionalità che Pecritenc presuppone vengano aggiunte presto *.
  • La familiarità. Il vecchio codice potrebbe avere modelli riutilizzati che vengono utilizzati in altri 5 punti della base di codici dell'azienda. Allo stesso tempo il nuovo codice utilizza uno schema di fantasia che solo la metà della società ha mai sentito parlare a questo punto.
  • Rossetto su un maiale. Pecritenc potrebbe pensare che sia il vecchio che il nuovo codice siano spazzatura o irrilevanti, rendendo così inutile il confronto tra di essi.
  • Pride. Pecritenc potrebbe essere stato l'autore originale del codice e non ama le persone che apportano enormi modifiche al suo codice. Potrebbe persino vedere miglioramenti come un insulto leggero, perché implicano che avrebbe dovuto fare meglio.
risposta data 31.08.2017 - 11:54
fonte
1

Quale tipo di codice è migliore può dipendere dall'esperienza dei programmatori e anche dagli strumenti che usano. Ad esempio, ecco perché quello che normalmente sarebbe considerato un codice scritto male potrebbe essere più efficace in alcune situazioni rispetto al codice orientato agli oggetti ben scritto che fa pieno uso dell'ereditarietà:

(1) Alcuni programmatori semplicemente non hanno una comprensione intuitiva della programmazione orientata agli oggetti. Se la tua metafora per un progetto software è un circuito elettrico, allora ti aspetteresti un sacco di duplicazione del codice. Ti piacerebbe vedere più o meno gli stessi metodi in molte classi. Ti faranno sentire a casa. E un progetto in cui devi cercare metodi nelle classi genitore o persino nelle classi di nonni per vedere cosa sta succedendo può sembrare ostile. Non vuoi capire come funziona la classe genitore e quindi capire come varia la classe corrente. Vuoi capire direttamente come funziona la classe corrente, e trovi il fatto che le informazioni sono distribuite su diversi file che confondono.

Inoltre, quando vuoi solo correggere un problema specifico in una classe specifica, potresti non dover pensare se correggere il problema direttamente nella classe base o sovrascrivere il metodo nella tua attuale classe di interesse. (Senza ereditarietà non dovresti prendere una decisione consapevole. L'impostazione predefinita è ignorare i problemi simili in classi simili finché non vengono segnalati come bug.) Quest'ultimo aspetto non è un argomento valido, anche se potrebbe spiegare alcuni dei opposizione.

(2) Alcuni programmatori usano molto il debugger. Anche se in generale sono io stesso saldamente dalla parte dell'ereditarietà del codice e impedisco la duplicazione, condivido alcune delle frustrazioni descritte in (1) quando eseguo il debug del codice orientato agli oggetti. Quando segui l'esecuzione del codice, a volte continua a saltare tra le classi (antenato) anche se rimane nello stesso oggetto. Inoltre, quando si imposta un punto di interruzione in un codice ben scritto è più probabile che si inneschi quando non è utile, quindi potrebbe essere necessario spendere sforzi per renderlo condizionale (laddove possibile), o anche per continuare manualmente molte volte prima del trigger pertinente.

    
risposta data 31.08.2017 - 11:41
fonte
1

Dipende totalmente. Ho lavorato a un progetto che non consente le variabili booleane come parametri di funzione, ma richiede invece un enum dedicato per ciascuna opzione.

enum OPTION1 { OPTION1_OFF, OPTION1_ON };
enum OPTION2 { OPTION2_OFF, OPTION2_ON };

void doSomething(OPTION1, OPTION2);

è molto più dettagliato di

void doSomething(bool, bool);

Tuttavia,

doSomething(OPTION1_ON, OPTION2_OFF);

è molto più leggibile di

doSomething(true, false);

Il compilatore dovrebbe generare lo stesso codice per entrambi, quindi non c'è nulla da guadagnare usando il modulo più breve.

    
risposta data 01.09.2017 - 15:15
fonte
0

Direi che la coesione potrebbe essere un problema.

Ad esempio in un'applicazione web, diciamo che hai una pagina di amministrazione in cui indicizzi tutti i prodotti, che è essenzialmente lo stesso codice (indice) che utilizzeresti in una situazione di homepage, per ... basta indicizzare prodotti.

Se decidi di parzializzare tutto in modo che tu possa rimanere ASCIUTTO ed elegante, dovresti aggiungere molte condizioni riguardo se l'utente che sta navigando è un amministratore o meno e ingombrare il codice con cose inutili che lo rendono altamente illeggibile diciamo un designer!

Quindi, in una situazione come questa anche se il codice è praticamente lo stesso, solo perché potrebbe ridimensionarsi a qualcos'altro e gli use case potrebbero cambiare leggermente, sarebbe male andare dopo ognuno di essi aggiungendo condizioni e if . Quindi una buona strategia sarebbe quella di abbandonare il concetto di ASCIUTTO e rompere il codice in parti manutenibili.

    
risposta data 31.08.2017 - 15:49
fonte
0
  • Quando meno codice non fa lo stesso lavoro di più codice. Il refactoring per semplicità è buono, ma è necessario fare attenzione a non semplificare eccessivamente lo spazio del problema che questa soluzione incontra. 980 linee di codice potrebbero gestire più casi d'angolo di 450.
  • Quando meno codice non fallisce con la stessa grazia di un numero maggiore di codice. Ho visto un paio di processi di "ref *** toring" fatti su codice per rimuovere "inutili" tentativi di catch-catch e altri casi di errore. Il risultato inevitabile era invece di mostrare una finestra di dialogo con un bel messaggio sull'errore e cosa l'utente poteva fare, l'app si è bloccata o YSODed.
  • Quando meno codice è meno gestibile / estendibile di un altro codice. Il refactoring per la concisione del codice rimuove spesso costrutti di codice "non necessari" nell'interesse di LoC. Il problema è che questi costrutti di codice, come le dichiarazioni dell'interfaccia parallela, i metodi / sottoclassi estratti ecc. Sono necessari se questo codice dovesse mai fare più di quello che fa attualmente, o farlo in modo diverso. Al limite, alcune soluzioni personalizzate su misura per il problema specifico potrebbero non funzionare affatto se la definizione del problema cambia leggermente.

    Un esempio; hai una lista di interi. Ciascuno di questi numeri interi ha un valore duplicato nell'elenco, tranne uno. Il tuo algoritmo deve trovare quel valore spaiato. La soluzione del caso generale è quella di confrontare ogni numero con ogni altro numero fino a trovare un numero che non ha duplicato nella lista, che è un'operazione N ^ 2. Si potrebbe anche costruire un istogramma usando un hashtable, ma questo è molto poco efficiente in termini di spazio. Tuttavia, è possibile renderlo lineare-tempo e spazio costante utilizzando un'operazione XOR bit a bit; XOR ogni intero contro un "totale" in esecuzione (che inizia con zero), e alla fine, la somma in esecuzione sarà il valore del numero intero non abbinato. Molto elegante. Fino a quando i requisiti non cambiano, e più di un numero nell'elenco può essere annullato, o gli interi includono zero. Ora il tuo programma restituisce spazzatura o risultati ambigui (se restituisce zero, significa che tutti gli elementi sono accoppiati o che l'elemento spaiato è zero?). Questo è il problema delle implementazioni "intelligenti" nella programmazione del mondo reale.

  • Quando meno codice è meno autodocumentato di più codice. Essere in grado di leggere il codice stesso e determinare cosa sta facendo è fondamentale per lo sviluppo del team. Dare un algoritmo brain-f *** che hai scritto che si adatta magnificamente a uno sviluppatore junior e chiedergli di modificarlo leggermente per modificare leggermente l'output non ti porterà molto lontano. Anche molti sviluppatori senior avrebbero avuto problemi con quella situazione. Essere in grado di capire in qualsiasi momento cosa sta facendo il codice, e cosa potrebbe andare storto, è la chiave per un ambiente di sviluppo del team di lavoro (e anche solo, ti garantisco il lampo di genio che hai avuto quando hai scritto un 5 il metodo per curare il cancro è ormai passato da molto tempo quando torni a quella funzione cercando di curare anche il morbo di Parkinson.
risposta data 01.09.2017 - 15:56
fonte
0

Il codice del computer deve fare una serie di cose. Un codice "minimalista" che non fa queste cose non è un buon codice.

Ad esempio, un programma per computer dovrebbe coprire tutto il possibile (o almeno, tutti i casi probabili). Se un pezzo di codice copre solo un "caso base" e ignora gli altri, non è un buon codice, anche se è breve.

Il codice del computer dovrebbe essere "scalabile". Un codice criptico può funzionare solo per un'applicazione specializzata, mentre un programma più lungo ma più aperto può facilitare l'aggiunta di nuove applicazioni.

Il codice del computer dovrebbe essere chiaro. Come è stato dimostrato da un altro rispondente, è possibile che un codificatore hard-core produca una funzione di tipo "algoritmico" a una riga che faccia il lavoro. Ma il raccoglitore doveva essere suddiviso in cinque "frasi" diverse prima che fosse chiaro al programmatore medio.

    
risposta data 01.09.2017 - 18:36
fonte
-2

Prestazioni computazionali. Quando si ottimizza il rivestimento delle tubature o si eseguono parti del codice in parallelo, potrebbe essere utile, ad esempio, non eseguire il ciclo da 1 a 400, ma da 1 a 50 e inserire 8 istanze di codice simile in ogni ciclo. Non presumo che questo sia il caso della tua situazione, ma è un esempio in cui più linee sono migliori (prestazioni-saggio).

    
risposta data 31.08.2017 - 14:15
fonte

Leggi altre domande sui tag