Casa / Skype / Criteri per un buon codice nella programmazione. Regole di programmazione PHP (14 regole) Regole di programmazione

Criteri per un buon codice nella programmazione. Regole di programmazione PHP (14 regole) Regole di programmazione

Oggi parleremo di come dovrebbe apparire il codice, che richiede un codice bello e leggibile.

Nonostante il fatto che il programma venga eseguito da una macchina, il codice del programma è scritto da persone e per le persone: non è un caso che i linguaggi di programmazione di alto livello abbiano sintassi e comandi leggibili dall'uomo. I moderni progetti software sono sviluppati da gruppi di programmatori, a volte separati non solo dallo spazio degli uffici, ma anche dai continenti e dagli oceani. Fortunatamente, il livello di sviluppo della tecnologia consente di utilizzare le competenze dei migliori sviluppatori, indipendentemente dall'ubicazione dei loro datori di lavoro. Questo approccio allo sviluppo impone seri requisiti alla qualità del codice, in particolare alla sua leggibilità e comprensibilità.

Esistono molti approcci ben noti ai criteri di qualità del codice, che quasi tutti gli sviluppatori impareranno prima o poi. Ad esempio, ci sono programmatori che aderiscono al principio di progettazione KISS (Keep It Simple, Stupid!). Questo metodo di sviluppo è abbastanza giusto e merita rispetto, oltre a riflettere regola universale buon codice: semplicità e chiarezza. Tuttavia, la semplicità deve avere dei limiti: l'ordine nel programma e la leggibilità del codice non dovrebbero essere il risultato della semplificazione. Oltre alla semplicità, ci sono alcune regole più semplici. E risolvono una serie di problemi.

  • Fornire una facile copertura del codice con test e debug. Il test unitario è il processo di test dei moduli, ovvero delle funzioni e delle classi che fanno parte del programma. Quando si crea un programma, uno sviluppatore deve considerare la possibilità di testarlo fin dall'inizio della scrittura del codice.
  • Facilitare la percezione del codice e l'utilizzo del programma. Ciò è facilitato dalla denominazione logica, da una buona interfaccia e da uno stile di implementazione.
  • Garantire la facilità del follow-up. La struttura ben congegnata e implementata del programma consente di risolvere problemi relativi al funzionamento del programma su un nuovo hardware o una nuova piattaforma.
  • Semplificare il processo di implementazione di ulteriori modifiche. Più la struttura è ottimizzata, più facile sarà modificare il codice, aggiungere nuove funzionalità, migliorare le prestazioni e modificare l'architettura.
  • Garantire la sostenibilità del programma. Se apporti modifiche o se ci sono problemi, puoi facilmente apportare correzioni. Inoltre, la corretta gestione degli errori facilita notevolmente il funzionamento del prodotto software.
  • Garantire che il progetto possa essere supportato da più sviluppatori o intere comunità (particolarmente importante per i progetti open source).

Qualsiasi codice è l'implementazione delle idee di uno sviluppatore con un obiettivo specifico: creare intrattenimento, scrivere software aziendale, sviluppare capacità di programmazione, creare software industriale, ecc. È importante accettare inizialmente le regole per creare un buon codice e applicarle - tale abitudine funzionerà per il programmatore quanto più intensamente il progetto raggiungerà una larga scala.

Criteri per un buon codice nella programmazione: 8 regole di GeekBrains

Segui un unico stile di codice. Se un programmatore viene a lavorare in un'organizzazione, soprattutto in una grande, molto spesso gli vengono presentate le regole per la formattazione del codice in un particolare progetto (accordo sullo stile del codice). Questo non è un capriccio accidentale del datore di lavoro, ma la prova di un approccio serio.

Eccone alcuni regole generali che potresti incontrare:

osservare la sillabazione delle parentesi graffe e del rientro: ciò migliora notevolmente la percezione dei singoli blocchi di codice
segui la regola verticale: parti della stessa query o condizione devono trovarsi nello stesso rientro

if (tipo di a ! == "non definito" &&
tipo b! == "non definito" &&
typeof c === "stringa") (
//la tua roba
}

rispettare la spaziatura - inserire spazi dove migliorano la leggibilità del codice; questo è particolarmente importante in condizioni composte, come le condizioni di loop.

per (var i = 0; i< 100; i++) {
}

In alcuni ambienti di sviluppo è possibile impostare inizialmente le regole di formattazione del codice caricando le impostazioni fascicolo separato(disponibile in Visual Studio). Pertanto, tutti i programmatori del progetto ottengono automaticamente lo stesso tipo di codice, il che migliora notevolmente la percezione. È noto che è abbastanza difficile reimparare dopo molti anni di pratica e abituarsi alle nuove regole. Tuttavia, in qualsiasi azienda, lo stile del codice è una legge che deve essere rigorosamente seguita.

Non usare i "numeri magici". Non è un caso che i numeri magici vengano definiti anti-pattern di programmazione, ovvero le regole su come non scrivere il codice di un programma. Molto spesso, un numero magico come anti-modello è una costante utilizzata nel codice, il cui significato non è chiaro senza un commento. Tali numeri non solo complicano la comprensione del codice e ne compromettono la leggibilità, ma causano anche problemi durante il refactoring.

Ad esempio, nel codice c'è una riga:
DrawWindow(50, 70, 1000, 500);

Ovviamente non causerà errori nel codice, ma il suo significato non è chiaro a tutti. È molto meglio non essere pigri e scrivere subito in questo modo:

intero sinistro = 50;
int superiore = 70;
larghezza intera = 1000;
altezza intera = 500;
DrawWindow(sinistra, alto, larghezza, altezza);

A volte i numeri magici emergono quando si utilizzano costanti generalmente accettate, ad esempio quando si scrive il numero π. Diciamo che il progetto è stato realizzato:

Cerchio quadrato = 3,14*rad*rad

Cosa c'è che non va? Ma c'è del male. Ad esempio, se nel corso del lavoro è necessario effettuare un calcolo con elevata precisione, sarà necessario cercare tutte le occorrenze della costante nel codice e questo è uno spreco di risorse lavorative. Allora è meglio scriverlo così:

cost float pi = 3,14;
Cerchio quadrato = pi greco rad rad

A proposito, a volte gli altri programmatori potrebbero non ricordare il valore della costante che hai utilizzato, quindi semplicemente non la riconoscono nel codice. Pertanto è meglio evitare di scrivere numeri senza dichiararli nelle variabili, ed è meglio dichiarare anche i valori costanti. A proposito, queste costanti sono quasi sempre nelle librerie integrate, quindi il problema si risolve da solo.

Usa nomi significativi per variabili, funzioni, classi. Tutti i programmatori conoscono il termine "offuscamento del codice": l'offuscamento deliberato dell'anno di programma utilizzando un'applicazione di offuscamento. Viene fatto per nascondere l'implementazione e trasformare il codice in un insieme indistinto di caratteri, rinominare variabili, modificare i nomi di metodi, funzioni e così via ... Sfortunatamente, succede che il codice sembra confuso anche senza offuscamento - proprio a causa dei nomi senza significato di variabili e funzioni: var_3698, myBestClass, NewMethodFinal, ecc... Questo non solo intralcia gli sviluppatori che contribuiscono al progetto, ma porta anche a una quantità infinita di commenti. Nel frattempo, rinominando la funzione, puoi eliminare i commenti: il suo nome stesso dirà cosa fa.

Verrà fuori il cosiddetto codice autodocumentante, una situazione in cui le variabili e le funzioni sono denominate in modo tale che guardando il codice sia chiaro come funziona. L’idea del codice autodocumentato ha molti sostenitori e oppositori, le cui argomentazioni vale la pena ascoltare. In generale, ti consigliamo di trovare un equilibrio e di utilizzare saggiamente sia i commenti, i nomi delle variabili "parlanti" che le funzionalità di autodocumentazione del codice, laddove ciò sia giustificato.

Ad esempio, prendiamo un codice come questo:

// trova la foglia, scrivi a r
se (x != nullo) (
mentre (x.a!= null) (
x = x.a;
r = x.n;
}
}
altro(
r = "";
}

Dovrebbe essere chiaro dal commento cosa fa esattamente il codice. Ma non è del tutto chiaro cosa significhino x.a e x.n. Proviamo ad apportare modifiche in questo modo:
// trova la foglia, scrivi il nome in leafName
if (nodo!= null) (
while (nodo.next!= null) (
nodo = nodo.successivo;
nomefoglia = nome.nodo;
}
}
altro(
nomefoglia = "";
}

A questo punto, vale la pena parlare separatamente dei commenti, poiché gli ascoltatori fanno sempre molte domande sull'opportunità e sulla necessità di utilizzarli. Quando ci sono molti commenti, portano a una scarsa leggibilità del codice. È quasi sempre possibile apportare modifiche al codice in modo tale da eliminare la necessità di commenti. Nei progetti di grandi dimensioni i commenti sono giustificati nel caso di utilizzo delle API, indicando il collegamento del codice di moduli di terze parti, indicando punti controversi o che richiedono ulteriori elaborazioni.

Combiniamo due regole più importanti in un unico chiarimento. Crea metodi come nuovo livello astrazioni con nomi significativi E mantenere i metodi compatti. In generale, oggi la modularità del codice è a disposizione di ogni programmatore, il che significa che dovresti cercare di creare astrazioni ove possibile. L'astrazione è un modo per nascondere i dettagli di implementazione della funzionalità. Creando piccoli metodi separati, il programmatore ottiene un buon codice, diviso in blocchi che contengono l'implementazione di ciascuna funzione. Questo approccio spesso aumenta il numero di righe di codice. Esistono anche alcune raccomandazioni che indicano che la lunghezza del metodo non supera le 10 righe. Naturalmente, la dimensione di ciascun metodo dipende interamente dallo sviluppatore e dipende da molti fattori. Il nostro consiglio: è semplice, rendi il metodo compatto in modo che un metodo svolga un compito. Le singole entità remote sono più facili da migliorare, ad esempio inserendo la validazione dei dati di input proprio all'inizio del metodo.

Per illustrare queste regole, prendiamo l'esempio del paragrafo precedente e creiamo un metodo il cui codice non richiede commenti:
string GetLeafName(Nodo nodo)(
if (nodo!= null) (
while (nodo.next!= null) (
nodo = nodo.successivo;
}
restituisce nodo.nome;
}
ritorno"";
}

E infine, nascondi l'implementazione del metodo:

NomeFoglia = OttieniNomeFoglia(nodo); …

Controlla l'input all'inizio dei metodi. A livello di codice, è imperativo eseguire controlli sui dati di input in tutti o quasi tutti i metodi. Ciò è dovuto al comportamento dell'utente: i futuri utenti possono inserire qualsiasi dato che possa causare il blocco del programma. In qualsiasi metodo, anche quello utilizzato una sola volta, è fondamentale organizzare la convalida dei dati e creare la gestione degli errori. Vale la pena farlo perché il metodo non solo funge da livello di astrazione, ma è anche necessario per il riutilizzo. In linea di principio è possibile dividere i metodi in quelli in cui è necessario verificare e quelli in cui non è necessario farlo, ma per completa sicurezza e protezione dall '"utente astuto" è meglio controllare tutti dati in ingresso.

Nell'esempio seguente inseriamo un controllo per garantire che l'input non riceva null.
Elenco GetEvenItems(Elenco elementi) (
assert(elementi!= null);

Risultato della lista = nuova Lista();
foreach (int i in elementi) (
se (i % 2 == 0) (
risultato.add(i);
}
}
risultato restituito;
}

Implementare solo la relazione "è" con l'ereditarietà. Altrimenti, composizione. La composizione è uno dei modelli chiave volti a rendere il codice più facile da leggere e, a differenza dell'ereditarietà, non viola il principio di incapsulamento. Supponiamo che tu abbia una classe Timone e una classe Ruota. La classe Car può essere implementata come discendente della classe antenata Rudder, ma necessita anche delle proprietà della classe Wheel.

Di conseguenza, il programmatore inizia a produrre eredità. Ma anche dal punto di vista della logica ristretta, la classe Auto è una composizione di elementi. Supponiamo che esista un codice come questo, quando viene creata una nuova classe utilizzando l'ereditarietà (la classe ScreenElement eredita i campi e i metodi della classe Coordinate ed estende questa classe):

Coordinata della classe (
intx pubblico;
pubblico int y;
}
class ScreenElement: Coordinate(
simbolo di carattere pubblico;
}

Usiamo la composizione:
Coordinata della classe (
intx pubblico;
pubblico int y;
}
classe ElementoSchermo(
coordinare pubblico;
simbolo di carattere pubblico;
}

La composizione è un buon sostituto dell'ereditarietà, questo modello è più semplice per comprendere ulteriormente il codice scritto. Puoi seguire questa regola: scegli l'ereditarietà solo se la classe desiderata è simile alla classe antenata e non utilizzerà i metodi di altre classi. Inoltre, la composizione salva il programmatore da un ulteriore problema: elimina il conflitto di nomi che si verifica durante l'ereditarietà. La composizione ha anche uno svantaggio: moltiplicare il numero di oggetti può avere un impatto sulle prestazioni. Ma ancora una volta, questo dipende dalla portata del progetto e dovrebbe essere valutato dallo sviluppatore caso per caso.

Interfaccia separata dall'implementazione. Qualsiasi classe utilizzata in un programma è costituita da un'interfaccia (ciò che è disponibile quando si utilizza la classe dall'esterno) e da un'implementazione (metodi). Nel codice, l'interfaccia dovrebbe essere separata dall'implementazione, sia per rispettare uno dei principi dell'OOP, l'incapsulamento, sia per migliorare la leggibilità del codice.

Facciamo una lezione:

class Square ( bordo float pubblico; area float pubblica; )

Un'altra classe:

classe Quadrato( float pubblico GetEdge(); float pubblico GetArea(); public void SetEdge( galleggiare e) ; public void SetArea( galleggiare a) ; bordo flottante privato; area galleggiante privata; )

Il secondo caso è preferibile perché nasconde l'implementazione con il modificatore di accesso privato. Oltre a migliorare la leggibilità del codice, la separazione dell'interfaccia dall'implementazione, unita al mantenimento delle dimensioni dell'interfaccia, presenta un altro importante vantaggio: in caso di malfunzionamento del programma, è necessario controllare solo poche funzioni per trovare la causa del guasto. Quanto più funzioni e dati sono aperti, tanto più difficile è risalire all'origine dell'errore. L'interfaccia però deve essere completa e deve permettere di fare tutto il necessario, altrimenti è inutile.

Durante il webinar è stata posta la domanda: “È possibile scrivere bene subito e non refactoring?” Probabilmente, tra qualche anno o addirittura decenni di programmazione, questo sarà possibile, soprattutto se si avrà una prima visione dell’architettura del programma. Ma non è mai possibile prevedere lo stato finale del progetto attraverso diversi rilasci e iterazioni di perfezionamento. Ecco perché è importante ricordare sempre queste regole, che garantiscono la sostenibilità e la capacità di sviluppo del tuo programma.

La maggior parte degli articoli scritti sul tema dell'assunzione di programmatori sembrano più o meno gli stessi. Di norma, tali articoli consigliano di "assumere solo i migliori". Confesso che non sono contento di questo consiglio perché sembra troppo vago. È come se venissi in un concessionario di automobili e chiedessi al venditore che tipo di macchina ti consiglierebbe, e lui ti risponderebbe che "Migliore" non è indicato per nessuna auto nel concessionario di automobili.

Per favore, non fraintendermi, non ti sto consigliando di cercare deliberatamente programmatori mediocri. Naturalmente, tutti vogliono assumere solo i programmatori più talentuosi ed esperti. La posta in gioco è alta nelle decisioni di assunzione. La tua decisione influenzerà il lavoro dell'intero team e dei suoi singoli membri. Come dicono:

“Rifiutare un buon candidato è molto meglio che accettarne uno cattivo… Se hai anche il minimo dubbio, non assumere.”

Ma i consigli standard mi danno ancora fastidio. Non è tanto il consiglio in sé, ma il fatto che le persone tendono a fraintenderlo. Se applicata senza ulteriori aggiustamenti, questa pratica crea fondamentalmente un senso di superiorità. Questo effetto è particolarmente comune tra i programmatori, poiché l'elitarismo è in qualche modo insito in noi. Quando sentiamo che dovremmo assumere solo i “migliori”, questo consiglio subisce una trasformazione subconscia:

"Il meglio?" Ma sono io! Sono il migliore". Ovviamente devo assumere persone dotate, intelligenti e simpatiche quanto me. E perché insozzare la mia eccellente squadra con ogni sorta di marmaglia?

Chi sono i migliori programmatori?

Naturalmente, questo approccio non crea le migliori condizioni per il processo decisionale. La regola standard funziona molto meglio se la capisci in modo leggermente diverso:

“Voglio formare la squadra più efficiente possibile. Assumendo un ulteriore dipendente non cerco solo l'ampliamento numerico dell'organico. Ogni persona assunta deve migliorare la mia squadra in qualche modo. Non sto cercando qualcuno dotato come me. Piuttosto, ho bisogno di un uomo che sia più dotato di me in almeno una direzione importante.

Capo

Il capo peggiore è quello che si sente minacciato dalla sua squadra. Consapevolmente o no, teme "il meglio" e quindi assume costantemente persone contro le quali sembrerà vantaggioso.

Probabilmente puoi vivere in una grande azienda con questo approccio. Ho il forte sospetto che lo Shaggy Boss dei fumetti di Dilbert sia stato disegnato dal vero.

Ma nel mondo delle piccole aziende di sviluppo software, le cose sono molto diverse. Se sei il fondatore o il "capo guru" di una piccola azienda, esamina te stesso in modo attento, onesto e obiettivo. Se sei una di quelle persone che si sentono minacciate dai propri dipendenti, fermati e pensa. Fino a quando non riuscirai a risolvere questo problema, le possibilità di costruire una squadra efficace saranno pari a zero.

Personale di programmatori

Il vero significato della regola standard non è quello di divertire il nostro ego, ma di ricordarci di non aver paura di cercare dipendenti migliori. Tuttavia è necessario scoprire più precisamente cosa significa realmente la parola “migliore”.

Cerca persone inclini all'introspezione

I lavoratori “migliori” non smettono mai di imparare.

Uno dei criteri più importanti nella valutazione dei candidati, considero quello che io stesso chiamo la "derivata prima". Questa persona sta studiando? Sta andando avanti o sta fermo? (Alcuni dei miei pensieri su questo argomento sono pubblicati nell'articolo Career Calculus sul mio blog).

Le persone che prendono sul serio il loro successo futuro hanno maggiori probabilità di avere successo. Questa mentalità è spesso il segnale più forte per le decisioni di assunzione.

Ciò non significa che devi assumere solo le persone che vogliono avere successo. Tutte le persone vogliono avere successo. Consiglio di assumere persone che prendono sul serio l'apprendimento continuo. Queste persone non perdono tempo cercando di convincerti quanto sanno. Non sono concentrati sul passato, né sul futuro. Mentre li intervisti, loro intervistano te, cercando di vedere cosa possono imparare da te.

Come trovare una persona simile?

Una caratteristica importante è che le persone determinate ad apprendere costantemente sanno bene ciò che non sanno. Conoscono i loro punti deboli e non hanno paura di parlarne.

Nelle interviste, a un candidato viene spesso chiesto di descrivere il suo principale punto debole. Anche se questa domanda è terribilmente tradizionale, mi piace.

Sfortunatamente, molti candidati cercano di eludere la risposta. Vanno in una libreria e comprano un libro sui colloqui. Il libro avverte che porrò loro questa domanda e offre modi "creativi" per eludere una risposta sincera:

  • A volte lavoro troppo.
  • A volte la mia attenzione ai dettagli infastidisce gli altri membri della band.

Quando chiedo a un candidato di parlare dei suoi punti deboli, mi aspetto una risposta intelligente, sincera e sicura. Quando sento un candidato ammettere la sua debolezza, fa impressione. Ma se un candidato dà una risposta evasiva, presa da un libro, comincio a pensare al candidato successivo.

Assumi sviluppatori, non programmatori

In una piccola azienda, i "migliori" programmatori sono quelli che non si limitano alla programmazione stessa. Prova ad assumere sviluppatori, non programmatori. Sebbene queste parole siano spesso usate in modo intercambiabile, le distinguo. Riguarda sulla differenza tra la semplice programmazione e l'appartenenza a un team di prodotto. Ecco una citazione da un articolo che ho scritto su questo argomento sul mio blog:

“In questo articolo, un “programmatore” si riferisce a qualcuno che si occupa esclusivamente di codificare nuove funzionalità e [se sei fortunato] correggere bug. I programmatori non scrivono le specifiche. Non creano casi di test automatizzati. Non aiutano il supporto sistemi automatizzati le build sono aggiornate. Non aiutano i clienti a decidere problemi tecnici. Non aiutano a scrivere la documentazione, non partecipano ai test e non leggono nemmeno il codice. Tutto ciò che fanno è scrivere un nuovo codice. Non vale la pena tenere queste persone in una piccola azienda.

Invece di “programmatori” (persone specializzate nella scrittura di codice), servono “sviluppatori” (persone che contribuiscono in molti modi al successo del prodotto).

Cosa significa la regola standard? Quale attributo dovrebbe essere misurato per determinare se un candidato è il “migliore”?

Di solito questa regola è intesa solo in relazione alle abilità di codifica. Ma i programmatori veramente bravi sono intelligenti. Capiscono cose che di solito non vengono insegnate e possono lavorare 10 volte in modo più efficiente rispetto al programmatore medio. Naturalmente, sarebbe saggio cercare una di queste personalità "dieci volte", soprattutto nelle grandi organizzazioni dove specialisti come i programmatori "puliti" sono abbastanza appropriati. Ma in una piccola azienda ci vuole versatilità. Spesso è necessario che i membri del team svolgano diverse funzioni, non limitate alla scrittura del codice. In questi casi è molto importante trovare il miglior sviluppatore e questa persona non è necessariamente il miglior programmatore.

Quindi, la regola standard funziona abbastanza bene, ma è necessario passare dalle raccomandazioni generali a quelle più specifiche. Per riassumere quanto detto nelle sezioni precedenti, ecco 10 domande da porsi quando si valuta un candidato sviluppatore:

  1. Questo candidato può fare per il gruppo ciò che nessun altro può fare?
  2. È in un processo di apprendimento costante?
  3. Questo candidato è consapevole dei propri punti deboli e può discuterne con calma?
  4. Quanto è versatile questo candidato ed è in grado di fare "tutto il necessario" per garantire il successo commerciale del prodotto?
  5. Il candidato appartiene al numero dei programmatori "dieci volte"?
  6. Ha una laurea conseguita in un'università rispettabile?
  7. Se il candidato ha un dottorato di ricerca, ci sono altre indicazioni sulla sua capacità di sviluppare prodotti commerciali?
  8. Il candidato ha esperienza di lavoro in team che hanno sviluppato prodotti commerciali?
  9. Il candidato può fornire esempi di buon codice?
  10. Al candidato piace programmare abbastanza da scrivere codice nel tempo libero?

Non è richiesta una risposta positiva a tutte e 10 le domande. Non dirò nemmeno il numero massimo di risposte positive necessarie per accettare un candidato. L'assunzione è una lotteria e ogni domanda può servire da segnale per valutare l'idoneità di un candidato.

In definitiva, qualsiasi decisione in materia di assunzione viene presa per volontà e in questo caso non sono possibili garanzie. Eppure, se presti attenzione a questi problemi, aumenteranno le probabilità che non dovrai pentirti della tua decisione in seguito.

Quindi ora hai un problema se stai scrivendo una libreria che verrà utilizzata sia dal codice della vecchia scuola scritto con wchar_t definito come alias per unsigned short , sia dal codice della nuova scuola scritto con wchar_t come tipo interno distinto. Quale tipo di dati è necessario utilizzare per i parametri stringa?

Questa è una traduzione di La triste storia degli specificatori di formato Unicode in stile printf in Visual C++ .

Windows ha implementato Unicode prima della maggior parte degli altri sistemi operativi. Di conseguenza Soluzioni Windows perché molti problemi differiscono dalle decisioni prese da coloro che hanno aspettato che la polvere si calmasse¹. L'esempio più chiaro di ciò è Utilizzo di Windows UCS-2 come codifica Unicode. All'epoca era la codifica consigliata dall'Unicode Consortium perché Unicode 1.0 supportava solo 65"536 caratteri². L'Unicode Consortium cambiò idea cinque anni dopo, ma ormai era troppo tardi per Windows, che aveva già rilasciato Win32s, Windows NT 3.1, Windows NT 3.5, Windows NT 3.51 e Windows 95, che utilizzavano tutti UCS-2³.

Ma oggi parleremo delle stringhe di formato in stile printf.

Questa è una traduzione di Se FlushInstructionCache non fa nulla, perché devi chiamarlo rivisitato .

Dovresti chiamare la funzione FlushInstructionCache quando generi o modifichi il codice eseguibile in fase di esecuzione, in modo che il processore, quando esegue il codice generato/modificato, legga le istruzioni che hai scritto e non le vecchie istruzioni che potrebbero rimanere nel processore cache delle istruzioni.

Lo abbiamo già imparato. Questo perché è sufficiente una semplice chiamata di funzione per svuotare la cache dei comandi.

Ma su Windows NT, la funzione FlushInstructionCache lo fa vero lavoro, perché deve avvisare tutti gli altri processori di svuotare le proprie cache.

Tuttavia, se guardi Windows 10, scoprirai che la funzione FlushInstructionCache assomiglia alla versione di Windows 95: lei non fa nulla.

Qual è il problema qui?

Ultimamente ho visto poco codice veramente buono, molto mediocre e Molto molto male. (Molto di quello che ho scritto prima, soprattutto quando ho iniziato, appartiene a quest'ultimo, ahimè.) Leggendo articoli casuali su Internet e libri professionali, sono giunto alla conclusione che scrivere un buon codice è facile. Incredibilmente difficile, ma allo stesso tempo facile. In effetti, è così semplice che si riduce a tre regole.

  1. Scrivi codice per le persone, non per le macchine.
  2. Ogni pezzo di codice dovrebbe eseguire un'attività.
Seguendoli costantemente, scriverai un buon codice. In qualsiasi linguaggio di programmazione e in qualsiasi paradigma. Il problema è che è molto difficile. Tutti richiedono disciplina e gli ultimi due, nella maggior parte dei casi, richiedono anche una riflessione prolungata.
Scrivi codice per le persone, non per le macchine
Questa è la più importante delle tre regole ed è alla base delle altre due. Scrivi un codice facile per un essere umano; lasciare il duro lavoro al computer. Utilizzare nomi di variabili e metodi significativi. Non creare catene logiche confuse dove possono essere applicate catene logiche semplici. Non cercare di inserire il più possibile su una riga. Osservare stile uniforme scrivere codice con rientro significativo. Se i tuoi file sono così ingombranti che diventa difficile scorrerli, suddividili in più file più piccoli.

Molti programmatori cercano di scrivere qualcosa che, a loro avviso, funzioni più velocemente, riduca le dimensioni dell'applicazione - in una parola, "faciliti" il lavoro del computer. È fantastico, ma non dimenticare che è più importante scrivere codice che sia facile da leggere e gestire per altre persone.

Il tuo compilatore (o interprete) può gestire stili di codice completamente diversi. Per lui N E numeroOggetti- È lo stesso. Per gli esseri umani, no. Ci vorrà molto tempo per leggere il codice, anche se lo scopo della variabile ti sembra ovvio.

Immagina di aver creato una piccola sceneggiatura per te stesso e dopo alcuni anni di aver bisogno di cambiarla un po'. Cosa preferiresti vedere: uno script ben strutturato, con commenti e un nome chiaro, o una funzione senza un solo commento e con variabili il cui scopo è quasi impossibile da comprendere?

Se fai qualcosa di non ovvio per ottimizzare un pezzo di codice, descrivi in ​​un commento cosa fa esattamente. Ma non dimenticare che nella maggior parte dei casi non sarai in grado di ottimizzare il programma meglio del compilatore. La realtà è che lui è più intelligente di te. È un dato di fatto: i compilatori sono stati migliorati nel corso di decenni di duro lavoro da parte di professionisti. Ci sono delle eccezioni, ma confermano solo la regola.

Scrivi codice che le persone possano capire.

Non ripeterti
Non riesco a contare quante volte ho visto lo stesso pezzo di codice in diverse parti del programma. Proprio oggi stavo lavorando su una funzione di grandi dimensioni nel codice legacy e ho visto le stesse condizioni in due diverse parti del condizionale. Ho dovuto prendermi il tempo per assicurarmi che fosse solo un errore del programmatore.

Quando ripeti lo stesso frammento in più punti, prima o poi dovrai ritornarci per correggere qualche errore o aggiungere qualcosa di nuovo. Ciò rende difficile la manutenzione del codice. Invece di ripeterti, inserisci il frammento in una classe o funzione separata che può essere richiamata secondo necessità. Se chiami più metodi nello stesso ordine in posti diversi, racchiudili in una funzione separata.

Pensa a cosa sarebbe più facile da capire guardando il codice: uno snippet di 30 righe che libera un blocco di memoria o una chiamata di funzione clearMapVariableMemory()?
Potrebbe essere necessario studiare lo snippet in seguito, ma anche in questo caso sarà più semplice lavorare con una singola funzione.

Lo stesso principio può essere applicato anche ai dati. Se utilizzi spesso le stesse variabili, spostale in una classe o tipo di dati separato.

Se segui questa regola, tutte le modifiche saranno universali: non dovrai rielaborare dozzine di punti per apportare una piccola correzione.

Non ripeterti.

Ogni frammento di codice dovrebbe eseguire un'attività
L'ultima regola si basa sulle due precedenti: ogni parte del tuo codice dovrebbe fare solo una cosa. È vero a tutti i livelli: per espressioni, funzioni e metodi, classi e oggetti.

Qualche anno fa, uno sviluppatore mi mostrò un pezzo di codice che non funzionava. Il programmatore ha impiegato diverse ore per capirlo. Di conseguenza, il problema è stato riscontrato nelle istruzioni C post-incrementali (in casi particolari, il loro comportamento differisce da compilatore a compilatore). Successivamente, leggendo un libro sullo sviluppo, ho notato che il vero problema non era nelle dichiarazioni, ma nel fatto che un frammento era responsabile dell'esecuzione di molti compiti diversi.

Per quanto ricordo, la riga contenente l'errore faceva parte di un'operazione ternaria. Questo, a sua volta, eseguiva diverse operazioni sui puntatori per calcolare la condizione, e molte altre, a seconda del risultato dell'espressione condizionale. Era buon esempio scrivere codice principalmente per la macchina, non per la persona: nessuno tranne l'autore del frammento capirà cosa fa esattamente la riga, anche dopo averla letta più volte. Se il programmatore che ha scritto il codice avesse suddiviso la logica in più parti, avrebbe impiegato molto meno tempo per risolvere il problema.

Non dovresti estrarre, elaborare e modificare i dati con lo stesso metodo. La descrizione verbale di ciascuna funzione dovrebbe rientrare in una frase. Lo scopo di ogni riga dovrebbe essere chiaro. Ciò rende il codice comprensibile alle persone. Nessuno di noi può memorizzare nella propria testa una funzione lunga diverse migliaia di righe. Né esiste una ragione significativa per creare tali funzioni invece di diversi piccoli metodi utilizzati insieme.

Va notato che questo non è così difficile: per completare un compito di grandi dimensioni, è sufficiente suddividerlo in più compiti più piccoli, ognuno dei quali si trova in una funzione separata. È importante assicurarsi che lo scopo di ciascuna funzione rimanga chiaro, altrimenti il ​​codice diventa troppo confuso.

Ogni parte del tuo codice dovrebbe eseguire un'attività.

Conclusione
Scrivere un buon codice è un duro lavoro. Programmavo da quattro anni, non così a lungo, ma abbastanza a lungo per riscontrare parecchi problemi, incluso il mio. Mi è diventato chiaro che complichiamo lo sviluppo non osservandoli regole semplici. Osservarli correttamente è difficile: non è sempre ovvio dove sia necessaria una classe o un metodo separato. È un'abilità. Ci vuole molto tempo per diventare un buon programmatore. Ma se non seguiamo queste regole, scrivere e mantenere il codice diventa ancora più difficile.

Traduzione dell'articolo

15 regole per scrivere codice di qualità

Esistono una miriade di modi per scrivere codice errato. Fortunatamente, per elevarsi al livello del codice di qualità, è sufficiente seguire 15 regole. Osservarli non ti renderà un maestro, ma ti permetterà di imitarlo in modo convincente.

Regola 1. Seguire gli standard di codifica.

Ogni linguaggio di programmazione ha il proprio standard di formattazione del codice, che indica come rientrare, dove inserire spazi e parentesi, come denominare gli oggetti, come commentare il codice e così via.

Ad esempio, in questo pezzo di codice, secondo lo standard, ci sono 12 errori:

Per(i=0 ;i

Studia attentamente lo standard, impara a memoria le nozioni di base, segui le regole come comandamenti e i tuoi programmi saranno migliori di quelli scritti da laureati.

Molte organizzazioni adattano gli standard alle loro esigenze specifiche. Ad esempio, Google ha sviluppato standard per più di 12 linguaggi di programmazione. Sono ben pensati, quindi dai un'occhiata se hai bisogno di aiuto con la programmazione di Google. Gli standard includono anche impostazioni dell'editor per aiutarti a seguire lo stile e strumenti speciali per verificare che il tuo codice sia conforme a quello stile. Usali.

Regola 2. Assegna nomi descrittivi.

Limitati a telescriventi lente e goffe, gli antichi programmatori utilizzavano contratti per nomi di variabili e procedure per risparmiare tempo, pressioni di tasti, inchiostro e carta. Questa cultura è presente in alcune comunità per mantenere la compatibilità con le versioni precedenti. Prendiamo, ad esempio, la funzione C wcscspn (intervallo di complemento di stringhe di caratteri larghi) che rompe il linguaggio. Ma questo approccio non è applicabile al codice moderno.

Utilizza nomi lunghi e descrittivi come complementSpanLength per aiutare te stesso e gli altri a comprendere il tuo codice in futuro. Le eccezioni sono alcune variabili importanti utilizzate nel corpo di un metodo, come iteratori di loop, parametri, valori temporanei o risultati di esecuzione.

È molto più importante pensare a lungo e intensamente prima di nominare qualcosa. Il nome è accurato? Intendevi prezzo più alto o prezzo migliore? Il nome è sufficientemente specifico da evitarne l'uso in altri contesti per oggetti dal significato simile? Non sarebbe meglio chiamare il metodo getBestPrice invece di getBest? Si adatta meglio di altri nomi simili? Se hai un metodo ReadEventLog, non dovresti chiamare un altro NetErrorLogRead. Se chiami una funzione, il suo nome descrive il valore restituito?

Infine, alcune semplici regole di denominazione. I nomi delle classi e dei tipi devono essere sostantivi. Il nome del metodo deve contenere un verbo. Se un metodo determina se alcune informazioni su un oggetto sono vere o false, il suo nome deve iniziare con "is". I metodi che restituiscono le proprietà dell'oggetto devono iniziare con "get" e i metodi che impostano i valori delle proprietà devono iniziare con "set".

Regola 3. Commenta e documenta.

Inizia ogni metodo e procedura con una descrizione in un commento di ciò che fa il metodo o la procedura, i parametri, il valore restituito e possibili errori ed eccezioni. Descrivi nei commenti il ​​ruolo di ciascun file e classe, il contenuto di ciascun campo della classe e i passaggi principali del codice complesso. Scrivi commenti mentre sviluppi il codice. Se pensi che li scriverai più tardi, ti stai ingannando.

Inoltre, assicurati che la tua applicazione o libreria disponga di un manuale che spieghi cosa fa il tuo codice, ne definisca le dipendenze e fornisca istruzioni per crearlo, testarlo, installarlo e utilizzarlo. Il documento dovrebbe essere breve e maneggevole; spesso è sufficiente solo un file README.

Regola 4. Non ripeterti.

Non copiare e incollare mai il codice. Invece, estrai la parte comune in un metodo o classe (o macro, se necessario) e usala con i parametri appropriati. Evitare di utilizzare dati e porzioni di codice simili. Utilizzare anche le seguenti tecniche:

  • Crea riferimenti API dai commenti utilizzando Javadoc e Doxygen.
  • Generazione automatica di unit test basati su annotazioni o convenzioni di denominazione.
  • Genera PDF e HTML dalla stessa fonte di markup.
  • Ottenere la struttura della classe dal database (o viceversa).

Regola 5. Controlla gli errori e reagisci.

I metodi possono restituire indicazioni di errore o generare eccezioni. Elaborarli. Non fare affidamento sul fatto che il disco non si riempia mai, che il tuo file di configurazione sia sempre lì, che la tua applicazione venga eseguita con autorizzazioni complete, che le richieste di allocazione di memoria abbiano sempre successo o che la connessione non si interrompa mai. Sì, una buona gestione degli errori è difficile da scrivere e rende il codice più lungo e difficile da leggere. Ma ignorare gli errori semplicemente nasconde il problema sotto il tappeto, dove un utente ignaro un giorno lo troverà.

Regola 6. Dividi il codice in parti brevi e separate.

Ogni metodo, funzione o blocco di codice dovrebbe rientrare in una normale finestra dello schermo (25-50 righe). Se è troppo lungo, tagliatelo in pezzi più corti. Anche all'interno di un metodo, dividi il codice lungo in blocchi, la cui essenza puoi descrivere in un commento all'inizio di ciascun blocco.

Inoltre, ogni classe, modulo, file o processo deve eseguire un determinato tipo di attività. Se una parte di codice esegue attività completamente diverse, dividerla di conseguenza.

Regola 7. Utilizza le API del framework e le librerie di terze parti.

Scopri quali funzionalità sono disponibili tramite l'API del tuo framework. e anche cosa possono fare le librerie avanzate di terze parti. Se le librerie sono supportate dal gestore dei pacchetti di sistema, è probabile che lo siano bella scelta. Usa un codice che ti impedisca di reinventare la ruota (con un'inutile forma quadrata).

Regola 8. Non esagerare con il design.

Progetta solo ciò che è rilevante adesso. Il tuo codice può essere reso abbastanza generico per supportarlo ulteriori sviluppi, ma solo se questo non lo rende troppo complicato. Non creare classi parametrizzate, fabbriche, gerarchie profonde e interfacce nascoste per risolvere problemi che non esistono nemmeno: non puoi immaginare cosa accadrà domani. D'altra parte, quando la struttura del codice non si adatta al compito, sentiti libero di rifattorizzarlo.

Regola 9: Sii coerente.

Fai le stesse cose allo stesso modo. Se stai sviluppando un metodo la cui funzionalità è simile a quella di uno esistente, utilizza un nome simile, un ordine dei parametri simile e una struttura del corpo simile. Lo stesso vale per le lezioni. Crea campi e metodi simili, fornisci loro interfacce simili e associa nuovi nomi a quelli esistenti in classi simili.

Il tuo codice deve essere conforme alle convenzioni del tuo framework. Ad esempio, è buona norma rendere gli intervalli semiaperti: chiusi (inclusivi) a sinistra (all'inizio dell'intervallo) e aperti (esclusivi) a destra (alla fine). Se non ci sono accordi per un caso particolare, fai una scelta e attieniti ad essa fanaticamente.

Regola 10 Evitare problemi di sicurezza.

Il codice moderno raramente funziona in modo isolato. Il rischio di diventare bersaglio di attacchi è inevitabile. Non devono provenire da Internet; l'attacco può avvenire attraverso l'input della tua applicazione. A seconda del linguaggio di programmazione e del dominio, potrebbe essere necessario preoccuparsi di buffer overflow, cross-site scripting, SQL injection e altri problemi simili. Studia questi problemi ed evitali nel tuo codice. Non è difficile.

Regola 11 Utilizzare strutture dati e algoritmi efficienti.

Il codice semplice è spesso più facile da mantenere rispetto al codice modificato per migliorarne l'efficienza. Fortunatamente, puoi bilanciare manutenibilità ed efficienza utilizzando le strutture dati e gli algoritmi forniti dal framework. Utilizza mappe, set, vettori e algoritmi che funzionano con essi. Ciò renderà il tuo codice più pulito, più veloce, più scalabile e più efficiente in termini di memoria. Ad esempio, se memorizzi mille valori in un insieme ordinato, l'operazione di intersezione troverà elementi comuni con un altro insieme nello stesso numero di operazioni, non in un milione di confronti.

Regola 12: utilizzare i test unitari.

La complessità del software moderno lo rende più costoso da installare e più difficile da testare. Un approccio produttivo sarebbe quello di accompagnare ogni pezzo di codice con test che verifichino la correttezza del suo lavoro. Questo approccio semplifica il debug perché consente di rilevare prima gli errori. Il test unitario è essenziale quando si programma in linguaggi tipizzati dinamicamente come Python e JavaScript perché rilevano eventuali errori solo in fase di runtime, mentre i linguaggi tipizzati staticamente come Java, C# e C++ possono rilevarne alcuni in fase di compilazione. Il test unitario consente inoltre di eseguire il refactoring del codice in tutta sicurezza. Puoi utilizzare XUnit per semplificare la scrittura dei test e automatizzarne l'esecuzione.

Regola 13: Mantieni il codice portabile.

A meno che tu non abbia un motivo speciale, non utilizzare funzionalità disponibili solo su una particolare piattaforma. Non fare affidamento su determinati tipi di dati (come numeri interi, puntatori e timestamp) per avere una lunghezza specifica (32 bit, ad esempio) perché questa impostazione varia a seconda della piattaforma. Mantieni i messaggi del programma separati dal codice e codifica le impostazioni specifiche della cultura (ad esempio, separatori decimali e interi o formato della data). Sono necessari accordi affinché il codice possa essere eseguito in diversi paesi, quindi rendi la localizzazione il più semplice possibile.

Regola 14: Rendi costruibile il tuo codice.

Un semplice comando dovrebbe compilare il tuo codice in un modulo pronto per la distribuzione. Il comando dovrebbe consentire di creare ed eseguire rapidamente i test necessari. Per raggiungere questo obiettivo, utilizza uno strumento di creazione automatizzata come Make , Apache Maven o Ant . Idealmente, dovresti impostare un sistema di integrazione che controllerà, creerà e testerà il tuo codice ogni volta che cambia.

Regola 15: Metti tutto sotto il controllo della versione.

Tutti i tuoi elementi, codice, documentazione, fonti di strumenti, script di creazione, dati di test, dovrebbero essere nel controllo del codice sorgente. Git e GitHub rendono questo compito economico e senza problemi. Ma sono a tua disposizione anche molti altri potenti strumenti e servizi. Dovresti essere in grado di creare e testare il tuo programma su un sistema configurato semplicemente scaricandolo dal repository.

Conclusione.

Rendendo queste 15 regole parte della tua pratica quotidiana, ti ritroverai con un codice più facile da leggere, ben testato, con maggiori probabilità di funzionare correttamente e molto più facile da modificare quando sarà il momento. Inoltre risparmierai a te stesso e ai tuoi utenti molti grattacapi.