Codice leggibile pulito vs codice veloce da leggere. Quando attraversare la linea?


67

Quando scrivo codice cerco sempre di rendere il mio codice il più pulito e leggibile possibile.

Ogni tanto arriva un momento in cui è necessario attraversare la linea e passare da un bel codice pulito a un codice leggermente più brutto per renderlo più veloce.

Quando è corretto attraversare quella linea?


69
Hai risposto alla tua domanda, attraversi la linea quando devi attraversarla
gnibbler

6
Inoltre, il tuo "codice sporco" potrebbe funzionare altrettanto velocemente del "codice pulito" sull'hardware tra 6 mesi. Non esagerare come ha fatto Windows, però. :)
Mateen Ulhaq,

21
Esiste una differenza significativa tra un algoritmo di difficile comprensione e un codice di difficile comprensione. A volte l'algoritmo che devi implementare è complicato e il codice sarà necessariamente confuso, semplicemente perché esprime un'idea complessa. Ma se il codice stesso è il punto difficile, allora il codice dovrebbe essere risolto.
Tylerl,

8
In molti casi un compilatore / interprete intelligente può ottimizzare il codice pulito e leggibile in modo da avere le stesse prestazioni del codice "brutto". Quindi ci sono poche scuse, a meno che la profilazione non dica diversamente.
Dan Diplo,

1
Al giorno d'oggi, quando si tratta di compilatori, il tuo brutto codice sarà probabilmente lo stesso del tuo codice pulito (supponendo che tu non faccia cose davvero strane). Soprattutto in .NET, non è come nei giorni C ++ / MFC in cui il modo in cui si definiscono le variabili avrà un impatto sulle prestazioni. Scrivi codice gestibile. un po 'di codice finirà per essere complesso ma ciò non significa che sia brutto.
DustinDavis,

Risposte:


118

Quando attraversi la linea

  • Hai misurato che il tuo codice è troppo lento per l'uso previsto .
  • Hai provato miglioramenti alternativi che non richiedono l'archiviazione del codice.

Ecco un esempio reale: un sistema sperimentale che sto eseguendo stava producendo dati troppo lentamente, impiegando oltre 9 ore per corsa e usando solo il 40% della CPU. Invece di rovinare troppo il codice, ho spostato tutti i file temporanei in un filesystem in memoria. Aggiunte 8 nuove righe di codice non brutto e ora l'utilizzo della CPU è superiore al 98%. Problema risolto; nessuna bruttezza richiesta.


2
Ti assicuri inoltre di mantenere il codice originale, più lento e più pulito, sia come implementazione di riferimento sia per ripiegare quando l'hardware cambia e il tuo codice hacker più veloce non funziona più.
Paul R,

4
@PaulR Come conservi quel codice? In forma di commenti? Questo è sbagliato, imo - i commenti diventano obsoleti, nessuno li legge e personalmente se vedo il codice commentato di solito lo rimuovo - questo è il motivo per cui il controllo del codice sorgente. Un commento su un metodo che spiega cosa fa è meglio, imo.
Evgeni,

5
@Eugene: di solito mantengo la versione originale di una routine denominata fooe la rinomino foo_ref- in genere vive immediatamente sopra foonel file di origine. Nel mio cablaggio di prova chiamo fooe foo_refper la convalida e la misurazione delle prestazioni relative.
Paolo R,

5
@Paul, se lo stai facendo, potrebbe essere una buona idea fallire il test se la versione ottimizzata è sempre più lenta della funzione ref. questo può accadere se le ipotesi che hai fatto per farlo andare più veloce non sono più vere.
user1852503

58

È una falsa dicotomia. Puoi rendere il codice veloce e facile da mantenere.

Il modo in cui lo fai è scriverlo pulito, soprattutto con una struttura di dati il ​​più semplice possibile.

Quindi scopri dove sono gli scarichi di tempo (eseguendolo, dopo averlo scritto, non prima) e risolverli uno per uno. (Ecco un esempio.)

Aggiunto: sentiamo sempre dei compromessi, giusto, come un compromesso tra tempo e memoria o un compromesso tra velocità e manutenibilità? Sebbene tali curve possano benissimo esistere, non si deve presumere che un determinato programma si trovi sulla curva , o anche in nessun posto vicino ad essa.

Qualsiasi programma che si trova sulla curva può facilmente (dandolo a un certo tipo di programmatore) essere reso sia molto più lento, sia molto meno gestibile, e quindi non si troverà da nessuna parte vicino alla curva. Tale programma ha quindi molto spazio per essere reso più veloce e più mantenibile.

Nella mia esperienza, è lì che iniziano molti programmi.


Sono d'accordo. In effetti il ​​codice veloce che non è pulito alla fine diventerà più lento poiché non è possibile modificarlo correttamente.
edA-qa mort-ora-y,

22
Non sono d'accordo che si tratti di una falsa dicotomia; IMO ci sono scenari soprattutto nel codice della libreria (non tanto nel codice dell'applicazione ) in cui la divisione è molto reale. Vedi la mia risposta per di più.
Marc Gravell

1
Marc, puoi linkare le risposte nei commenti con l'URL "link". programmers.stackexchange.com/questions/89620/…

Abbiamo trovato tutti i tempi in cui dobbiamo provare a rendere il codice più veloce. Ma dopo la sperimentazione con il profiler per trovare la soluzione migliore (se il codice diventa brutto) questo non significa che il codice debba rimanere brutto. Si tratta di trovare la soluzione migliore che all'inizio non può sembrare ovvia, ma una volta trovata può di solito essere codificata in modo pulito. Quindi credo che sia una falsa dicotomia e solo una scusa per non riordinare la tua stanza dopo esserti divertito con i tuoi giocattoli. Dico succhiarlo e riordinare la tua stanza.
Martin York,

Non sono d'accordo sul fatto che si tratti di una falsa dicotomia. Dato che lavoro molto con la grafica, l'esempio più ovvio per me è rappresentato da cicli grafici ristretti: non so quanto spesso sia ancora fatto, ma era comune per i motori di gioco scritti in C per usare Assembly per il rendering di base loop per spremere fino all'ultima goccia di velocità. Questo mi fa pensare anche a situazioni in cui programmi in Python ma usi moduli scritti in C ++. "Difficile da leggere" è sempre relativo; ogni volta che passi a una lingua di livello inferiore per la velocità, quel codice è più difficile da leggere rispetto al resto.
deridere il

31

Nella mia esistenza OSS, svolgo molte attività di biblioteca mirate alla performance, che sono profondamente legate alla struttura dei dati del chiamante (cioè esterna alla biblioteca), senza (in base alla progettazione) nessun mandato sui tipi in arrivo. Qui, il modo migliore per rendere questo performer è la meta-programmazione, che (dato che sono in .NET-land) significa IL-emit. Questo è un codice brutto, brutto, ma molto veloce.

In questo modo, accetto felicemente che il codice della libreria possa essere "più brutto" del codice dell'applicazione , semplicemente perché ha un controllo minore (o forse no) sugli input , quindi è necessario eseguire alcune attività attraverso meccanismi diversi. O come l'ho espresso l'altro giorno:

"codifica sulla scogliera della follia, quindi non devi "

Ora il codice dell'applicazione è leggermente diverso, in quanto è qui che gli sviluppatori "normali" (in genere) investono in gran parte del loro tempo collaborativo / professionale; gli obiettivi e le aspettative di ciascuno sono (IMO) leggermente diversi.

IMO, le risposte sopra che suggeriscono che può essere veloce e facile da mantenere si riferiscono al codice dell'applicazione in cui lo sviluppatore ha un maggiore controllo sulle strutture di dati e non utilizza strumenti come la meta-programmazione. Detto questo, ci sono diversi modi di fare meta-programmazione, con diversi livelli di follia e diversi livelli di sovraccarico. Anche in quell'arena devi scegliere il livello appropriato di astrazione. Ma quando si desidera attivamente, positivamente, sinceramente che gestisca i dati imprevisti nel modo più rapido assoluto; potrebbe diventare brutto. Affrontalo; p


4
Solo perché il codice è brutto, non significa che non debba essere mantenuto. Commenti e rientri sono gratuiti e il brutto codice può di solito essere incapsulato in un'entità gestibile (classe, modulo, pacchetto, funzione, a seconda della lingua). Il codice può essere ugualmente brutto, ma almeno le persone saranno in grado di giudicare l'impatto dei cambiamenti che stanno per apportare ad esso.
tdammers,

6
@tdammers in effetti, e provo a farlo il più lontano possibile; ma è un po 'come mettere il rossetto su un maiale.
Marc Gravell

1
Bene, forse si dovrebbe fare una distinzione tra brutta sintassi e brutti algoritmi - a volte sono necessari brutti algoritmi, ma la brutta sintassi è generalmente IMO ingiustificabile.
martedì

4
La sintassi brutta di @IMO è piuttosto inevitabile se ciò che stai facendo è per sua natura diversi livelli di astrazione sotto il solito livello linguistico.
Marc Gravell

1
@marc ... è interessante. La mia prima reazione all'essere brutto del meta / astratto è stata il sospetto che il linguaggio / la forma della piastra specifici non favorissero la meta-codifica piuttosto che una qualche legge sottostante che collega i due. La cosa che mi ha fatto credere che fosse l'esempio dei metalevel progressivi in ​​matematica che terminavano nella teoria degli insiemi la cui espressione non è affatto brutta dell'algebra o persino del concreto aritmatico. Ma allora la notazione set è probabilmente una lingua completamente diversa e ogni livello di astrazione sottostante ha una sua lingua ....
explorest

26

Quando hai profilato il codice e verificato che sta effettivamente causando un significativo rallentamento.


3
E cos'è "significativo"?
Rook,

2
@ hotpaw2: è la risposta intelligente - presume che gli sviluppatori siano almeno in qualche modo concorrenti. Altrimenti sì, usare qualcosa di più veloce dell'ordinamento a bolle è (solitamente) una buona idea. Ma troppo spesso qualcuno (per continuare con l'ordinamento) cambierà quicksort con heapsort per una differenza dell'1%, solo per vedere qualcun altro sostituirlo sei mesi dopo per lo stesso motivo.

1
Non c'è mai motivo di creare un codice non pulito. Se non riesci a rendere il tuo codice efficiente pulito e di facile manutenzione, stai facendo qualcosa di sbagliato.
edA-qa mort-ora-y

2
@SF. - il cliente lo troverà sempre troppo lento, se può essere più veloce. Non gli importa di "cleanless" del codice.
Arriva il

1
@Rook: il cliente potrebbe trovare il codice (banale) dell'interfaccia troppo lento. Alcuni trucchi psicologici piuttosto semplici migliorano l'esperienza dell'utente senza accelerare effettivamente il codice - rinvia gli eventi dei pulsanti a una routine di fondo invece di eseguire le azioni al volo, mostrare una barra di avanzamento o qualcosa del genere, porre alcune domande insignificanti mentre l'attività viene eseguita in background ... è quando questi non bastano, puoi considerare l'ottimizzazione effettiva.
SF.

13

Il codice pulito non è necessariamente esclusivo con il codice ad esecuzione rapida. Il codice normalmente difficile da leggere è stato scritto perché era più veloce da scrivere, non perché viene eseguito più velocemente.

Scrivere codice "sporco" nel tentativo di renderlo più veloce è probabilmente poco saggio, dal momento che non si sa con certezza che le modifiche apportate migliorano effettivamente qualsiasi cosa. Knuth l'ha detto meglio:

"Dovremmo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali . Tuttavia non dovremmo cedere le nostre opportunità in quel 3% critico. Un buon programmatore non sarà cullato dall'accompagnamento da ragionando, sarà saggio esaminare attentamente il codice critico, ma solo dopo che questo codice sarà stato identificato ".

In altre parole, scrivi prima il codice. Quindi, profilare il programma risultante e vedere se quel segmento è, in effetti, un collo di bottiglia delle prestazioni. In tal caso, ottimizza la sezione se necessario e assicurati di includere molti commenti sulla documentazione (possibilmente includendo il codice originale) per spiegare le ottimizzazioni. Quindi profilare il risultato per verificare che sia stato effettivamente apportato un miglioramento.


10

Poiché la domanda dice " codice veloce da leggere ", la risposta semplice non è mai. Non c'è mai una scusa per scrivere codice difficile da leggere. Perché? Due ragioni.

  1. Cosa succede se sei investito da un autobus sulla strada di casa stasera? O (più ottimisticamente e più tipicamente) decollato da questo progetto e riassegnato a qualcos'altro? Il piccolo vantaggio che immagini di aver fatto con il tuo groviglio di codice è totalmente compensato dal fatto che nessun altro può capirlo . Il rischio che ciò comporta per i progetti software è difficile da sopravvalutare. Ho lavorato una volta con un importante PBXproduttore (se lavori in un ufficio probabilmente hai uno dei suoi telefoni sulla scrivania). Il loro project manager mi ha detto un giorno che il loro prodotto principale - il software proprietario che ha trasformato una normale scatola Linux in un centralino telefonico completo - era noto all'interno dell'azienda come "il blob". Nessuno lo capiva più. Ogni volta che hanno implementato una nuova funzionalità. colpivano la compilazione, poi stavano indietro, chiudevano gli occhi, contavano fino a venti, poi sbirciavano tra le dita per vedere se funzionava. Nessuna azienda ha bisogno di un prodotto principale che non controlla più, ma è uno scenario spaventosamente comune.
  2. Ma devo ottimizzare! OK, quindi hai seguito tutti i consigli eccellenti in altre risposte a questa domanda: il tuo codice non sta superando i suoi casi di test delle prestazioni, lo hai profilato con attenzione, identificato i colli di bottiglia, trovato una soluzione ... e sta per coinvolgere qualche bit-twiddling . Bene: ora vai avanti e ottimizza. Ma ecco il segreto (e potresti voler sederti per questo): l' ottimizzazione e la riduzione delle dimensioni del codice sorgente non sono la stessa cosa. Commenti, spazi bianchi, parentesi e nomi di variabili significativi sono tutti enormi aiuti per la leggibilità che non ti costano assolutamente nulla perché il compilatore li getterà via. (O se stai scrivendo un linguaggio non compilato come JavaScript - e sì, ci sono ragioni molto valide per ottimizzare JavaScript - possono essere gestiti da un compressore .) Lunghe righe di codice ristretto e minimalista (come quello che ha muntoo pubblicato qui ) non hanno nulla a che fare con l'ottimizzazione: è un programmatore che cerca di mostrare quanto sono intelligenti impacchettando il maggior numero di codice nel minor numero possibile di caratteri. Non è intelligente, è stupido. Un programmatore veramente intelligente è colui che può comunicare chiaramente le proprie idee agli altri.

2
Non posso essere d'accordo sul fatto che la risposta è "mai". Alcuni algoritmi sono intrinsecamente molto difficili da comprendere e / o implementare in modo efficiente. Leggere il codice, indipendentemente dal numero di commenti, può essere un processo molto difficile.
Rex Kerr,

4

Quando è un codice usa e getta. Voglio dire letteralmente: quando scrivi uno script per eseguire un calcolo o un'attività una tantum, e sai con tale certezza che non dovrai mai più fare quell'azione che puoi 'rm source-file' senza esitazione, quindi puoi scegliere la brutta via.

Altrimenti è una falsa dicotomia - se pensi di aver bisogno di renderlo brutto per farlo più velocemente, stai sbagliando. (O i tuoi principi su ciò che è un buon codice devono essere rivisti. L'uso di goto è in effetti piuttosto elegante quando è la soluzione corretta al problema. Raramente lo è comunque.)


5
Non esiste un codice usa e getta. Se avessi un penny per ogni volta che il "codice usa e getta" lo ha reso in produzione perché "funziona, non abbiamo il tempo di riscriverlo", sarei un milionario. Ogni riga di codice che scrivi dovrebbe essere scritta in modo tale che un altro programmatore competente possa raccoglierlo domani dopo essere stato colpito da un fulmine stanotte. Altrimenti non scriverlo.
Mark Whitaker,

Non sono d'accordo che si tratti di una falsa dicotomia; IMO ci sono scenari soprattutto nel codice della libreria (non tanto nel codice dell'applicazione) in cui la divisione è molto reale. Vedi la mia risposta per di più.
Marc Gravell

@mark, se l '"altro programmatore competente" è veramente competente, allora il codice

@Mark - Facile. Basta scrivere il codice usa e getta in modo che fallisca qualsiasi test di produzione, forse in qualche modo non risolvibile.
hotpaw2,

@Mark, se il tuo "codice usa e getta" lo rende in produzione, allora questo non è un codice usa e getta. Si noti che ho impiegato del tempo nella mia risposta per chiarire che sto parlando di codice che è letteralmente buttato via: vale a dire, eliminare dopo il primo utilizzo. Altrimenti sono d'accordo con il tuo sentimento e ho detto altrettanto nella mia risposta.
Maaku,

3

Ogni volta che il costo stimato di prestazioni inferiori sul mercato è superiore al costo stimato della manutenzione del codice per il modulo di codice in questione.

Le persone continuano a intrecciare SSE / NEON / etc codificato a mano. assemblaggio per provare a battere alcuni software della concorrenza sul popolare chip della CPU di quest'anno.


Una buona prospettiva di business, a volte i programmatori devono guardare al di là del semplice aspetto tecnico.
Questo

3

Non dimenticare che puoi rendere il codice difficile da leggere con la documentazione e i commenti appropriati.

In generale, profilo dopo aver scritto il codice di facile lettura che svolge la funzione desiderata. I colli di bottiglia potrebbero richiedere di fare qualcosa che lo rende più complicato, ma lo risolvi spiegando te stesso.


0

Per me è una proporzione di stabilità (come nel cemento cementato, nell'argilla cotta nel forno, incastonata nella pietra, scritta con inchiostro permanente). Più instabile è il tuo codice, poiché maggiore è la probabilità che tu debba modificarlo in futuro, più facilmente deve essere flessibile, come l'argilla bagnata, per rimanere produttivo. Sottolineo anche la flessibilità e non la leggibilità. Per me la facilità di modificare il codice è più importante della facilità di leggerlo. Il codice può essere facile da leggere e un incubo da cambiare, e a che serve leggere e comprendere facilmente i dettagli dell'implementazione se sono un incubo da cambiare? A meno che non sia solo un esercizio accademico, in genere il punto di essere in grado di comprendere facilmente il codice in una base di codice di produzione è con l'intento di essere in grado di cambiarlo più facilmente secondo necessità. Se è difficile cambiare, quindi molti dei vantaggi della leggibilità escono dalla finestra. La leggibilità è generalmente utile solo nel contesto della flessibilità e la flessibilità è utile solo nel contesto dell'instabilità.

Naturalmente anche il codice più difficile da mantenere immaginabile, indipendentemente da quanto sia facile o difficile da leggere, non rappresenta un problema se non c'è mai un motivo per cambiarlo, solo usarlo. Ed è possibile ottenere tale qualità, soprattutto per il codice di sistema di basso livello in cui le prestazioni tendono spesso a contare di più. Ho un codice C che uso ancora regolarmente, che non è cambiato dalla fine degli anni '80. Da allora non ha dovuto cambiare. Il codice è fugace, scritto nei giorni difficili, e lo capisco a malapena. Eppure è ancora applicabile oggi, e non ho bisogno di comprenderne l'implementazione per trarne grande utilità.

Scrivere a fondo i test è un modo per migliorare la stabilità. Un altro è il disaccoppiamento. Se il tuo codice non dipende da nient'altro, l'unica ragione per cui cambia è se, di per sé, deve cambiare. A volte una piccola quantità di duplicazione del codice può fungere da meccanismo di disaccoppiamento per migliorare notevolmente la stabilità in un modo che lo rende un degno compromesso se, in cambio, si ottiene un codice che ora è completamente indipendente da qualsiasi altra cosa. Ora quel codice è invulnerabile alle modifiche al mondo esterno. Nel frattempo il codice che dipende da 10 diverse librerie esterne ha 10 volte la ragione per cui cambierà in futuro.

Un'altra cosa utile in pratica è quella di separare la tua libreria dalle parti instabili della tua base di codice, possibilmente anche costruendola separatamente, come potresti fare per le librerie di terze parti (che allo stesso modo dovrebbero essere usate, non modificate, almeno non dal tuo squadra). Proprio quel tipo di organizzazione può impedire alle persone di manometterlo.

Un altro è il minimalismo. Meno il codice tenta di fare, più è probabile che possa fare ciò che fa bene. I progetti monolitici sono quasi permanentemente instabili, poiché più funzionalità vengono aggiunte a loro, tanto più sembrano incomplete.

La stabilità dovrebbe essere il tuo obiettivo principale ogni volta che miri a scrivere un codice che sarà inevitabilmente difficile da modificare, come il codice SIMD parallelizzato che è stato messo a punto con il micro-tuning. Contrastare la difficoltà di mantenere il codice massimizzando la probabilità che non si debba cambiare il codice e quindi non sarà necessario mantenerlo in futuro. Ciò porta a zero i costi di manutenzione, non importa quanto sia difficile mantenere il codice.

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.