clean-code-javascript
Lista dei contenuti
- Lista dei contenuti
- Introduzione
- Variabili
- Utilizza nomi di variabili comprensibili e pronunciabili
- Usa la stessa semantica per lo stesso tipo di variabili
- Utilizza nomi che possano essere cercati
- Utilizza nomi di variabili esplicartivi
- Evita mappe mentali
- Non contestualizzare inutilmente
- Utilizza i valori di default (predefiniti), anzichè usare condizioni o valutazioni minime
- Funzioni
- Argomenti di una funzione (idealmente 2 o anche meno)
- Un metodo dovrebbe fare una sola cosa
- I nomi delle funzioni dovrebbero farti capire cosa fanno
- Le funzioni dovrebbero avere un solo livello di astrazione
- Rimuovi il codice duplicato
- Estendi un oggetto con Object.assign
- Non usare valori flag (true/false) come parametri di una funzione
- Evitare effetti inattesi (parte 1)
- Evitare effetti inattesi (parte 2)
- Non aggiungere funzioni globali
- Preferisci la programmazione funzionale a quella imperativa
- Incapsula le condizioni
- Evita di verificare condizioni in negativo
- Evita le condizioni
- Evita la validazione dei tipi (parte 1)
- Evita la validazione dei tipi (part 2)
- Non ottimizzare eccessivamente
- Rimuovi il codice inutilizzato
- Oggetti e strutture dati
- Classi
- SOLID
- Single Responsibility Principle (SRP) (Principio di singola responsabilitΓ )
- Open/Closed Principle (OCP) (Principio aperto/chiuso)
- Liskov Substitution Principle (LSP) (Principio di sostituzione di Liskov)
- Interface Segregation Principle (ISP) (Principio di segregazione delle interfacce)
- Dependency Inversion Principle (DIP) (Principio di inversione delle dipendenze)
- Test
- ConsequenzialitΓ
- Gestione degli errori
- Formattazione
- Commenti
- Traduzioni
Introduzione
Principi di Ingegneria del Software, dal libro di Robert C. Martin Clean Code, adattati a JavaScript.
Non si tratta di una guida stilistica, bensì una guida per cercare di produrre software leggibile, riutilizzabile e rifattorizzabile in JavaScript.
Non tutti i principi di questa guida devono essere seguiti alla lettera, e solo alcuni sono universalmente condivisi. Sono linee guida e niente piΓΉ, ma sono state tutte apprese in anni di esperienza collettiva dall'autore di Clean code.
Il nostro lavoro come software engeneer esiste da soli 50 anni e stiamo ancora cercando di apprendere molto. Quando l'architettura del software godrà della stessa anzianità dell'architettura in sè, probabilmente avremo regole più rigide da seguire. Per ora facciamo si che queste linee guida servano come termine di paragone per valutare la qualità del software che tu ed il tuo team producete.
Un ultima cosa: conoscere queste regole non farΓ di te immediatamente un developer migliore, e lavorare per tanti anni come tale non ti eviterΓ di commettere errori. Ogni singola parte di codice parte come bozza, inizialmente, per per poi prendere forma esattamente come una scultura di argilla. Solo alla fine perfezioneremo il nostro software, quando revisioneremo il codice con i nostri colleghi. Ma non abbatterti tu la prima volta che il tuo codice sarΓ revisionato e richiederΓ miglioramenti: Abbatti il codice!
Variabili
Utilizza nomi di variabili comprensibili e pronunciabili
Da evitare
const yyyymmdstr = moment().format('YYYY/MM/DD');Bene:
const currentDate = moment().format('YYYY/MM/DD');Usa la stessa semantica per lo stesso tipo di variabili
Da evitare
getUserInfo();
getClientData();
getCustomerRecord();Bene:
getUser();Utilizza nomi che possano essere cercati
Leggiamo molto piΓΉ codice di quanto non ne scriveremo mai. Γ importante che il codice che noi scriviamo sia leggibile e ricercabile. Nominando variabili che non assumono uno specifico contesto all'interno del nostro software, possiamo irritare chi lo legge. Fai in modo che i nomi delle tue variabili siano ricercabili. Strumenti come buddy.js e ESLint possono aiutarti ad identificare, per esempio, costanti non rinominate.
Da evitare
// Cosa caspita significa 86400000?
setTimeout(blastOff, 86400000);Bene:
// Dichiarala come costante in maiuscolo.
const MILLISECONDI_IN_UN_GIORNO = 86400000;
setTimeout(blastOff, MILLISECONDI_IN_UN_GIORNO);Utilizza nomi di variabili esplicartivi
Da evitare
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);Bene:
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);Evita mappe mentali
Essere espliciti Γ¨ meglio che non esserlo.
Da evitare
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// A cosa fa riferimento esattamente `l`?
dispatch(l);
});Bene:
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location) => {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});Non contestualizzare inutilmente
Se il nome della tua classe/oggetto ti indica a cosa fa riferimento, non ripeterlo nei nomi delle sue proprietΓ o funzioni.
Da evitare
const Car = {
carMake: 'Honda',
carModel: 'Accord',
carColor: 'Blue'
};
function paintCar(car) {
car.carColor = 'Red';
}Bene:
const Car = {
make: 'Honda',
model: 'Accord',
color: 'Blue'
};
function paintCar(car) {
car.color = 'Red';
}Utilizza i valori di default (predefiniti), anzichè usare condizioni o valutazioni minime
I valori di default, generalmente sono piΓΉ chiari dei valutazioni minime. Tieni presente che se non utilizzerai questo approccio, la tua funzione restituirΓ solo undefined come valore di default.
Tutti gli altri valori "falsy" come '', "", false, null, 0, e
NaN, non saranno sostituiti da un valore predefinito.
Da evitare
function createMicrobrewery(name) {
const breweryName = name || 'Hipster Brew Co.';
// ...
}Bene:
function createMicrobrewery(name = 'Hipster Brew Co.') {
// ...
}Funzioni
Argomenti di una funzione (idealmente 2 o anche meno)
Limitare il numero di argomenti di una funzione è incredibilmente importante perchè ti permette di testarla più facilmente. Avere più di 3 argomenti può portare ad un'esplosione di combinazioni da testare, che produrranno una lunga serie di casi da verificare.
1 o 2 argomenti sono l'ideale e dovremmo evitarne un terzo se possibile. Generalmente se la tua funzione ha piΓΉ di 2 argomenti, forse, sta facendo troppe operazioni. Nei casi in cui questo non sia del tutto vero, un oggetto puΓ² aiutare ad ovviare a questo problema.
Dal momento in cui JavaScript permette la creazione di oggetti al volo puoi usare un oggetto, se pensi che il tuo metodo richieda molti argomenti.
Per rendere evidente cosa la funzione si aspetta di ricevere, puoi utilizzare la sintassi destrutturata (destructuring syntax) di ES2015/ES6 che ha diversi vantaggi:
-
Quando qualcuno osserva la firma della tua funzione, Γ¨ immediatamente chiaro che proprietΓ saranno utilizzate
-
Destrutturare, oltretutto, clona i valori primitivi passati alla funzione. Questo puΓ² prevenire effetti inattesi. Nota: oggetti ed array destrutturati nell'oggetto usato come argomento NON saranno clonati.
-
Un Linter puΓ² avvisarti che non stai utilizzando alcune delle proprietΓ del tuo oggetto, diversamente non sarebbe possibile.
Da evitare
function createMenu(title, body, buttonText, cancellable) {
// ...
}Bene:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});Un metodo dovrebbe fare una sola cosa
Questa Γ¨ di sicuro la regola piΓΉ importante nell'ingegneria del software. Quando un metodo si occupa di piΓΉ di un solo aspetto sarΓ piΓΉ difficile da testare, comporre e ragioraci sopra. Se Γ¨ possibile far eseguire al metodo una sola azione sarΓ piΓΉ facile il suo refactor e la leggibilitΓ del tuo codice sarΓ maggiore e piΓΉ chiara. Anche se non dovesse rimanerti in mente altro di questa guida, sarai comunque piΓΉ avanti di molti sviluppatori.
Da evitare
function emailClients(clients) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}Bene:
function emailActiveClients(clients) {
clients
.filter(isActiveClient)
.forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}I nomi delle funzioni dovrebbero farti capire cosa fanno
Da evitare
function addToDate(date, month) {
// ...
}
const date = new Date();
// Difficile da dire esattamente cosa viene aggiunto tramite questa funzione
addToDate(date, 1);Bene:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);Le funzioni dovrebbero avere un solo livello di astrazione
Quando hai piΓΉ di un livello di astrazione, la tua funzione generalmente sta facendo troppe cose. Dividere in piΓΉ funzioni aiuta a riutilizzarle e testare piΓΉ facilmente.
Da evitare
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
});
});
const ast = [];
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
});
}Bene:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const ast = lexer(tokens);
ast.forEach((node) => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
tokens.push( /* ... */ );
});
});
return tokens;
}
function lexer(tokens) {
const ast = [];
tokens.forEach((token) => {
ast.push( /* ... */ );
});
return ast;
}Rimuovi il codice duplicato
Fai del tuo meglio per evitare codice duplicato. Duplicare il codice è un male, perchè vuol dire che c'è più di un punto da modificare nel caso in cui dovessi cambiare alcune logiche.
Immagina di avere un ristorante e di dover tener traccia del tuo magazzino: la riserva di pomodori, cipolle, aglio, spezie, etc. Se hai piΓΉ di una lista in cui tieni traccia di queste quantitΓ dovrai aggiornarle tutte ogni volta che servirai, per esempio, un piatto con dei pomodori. Al contrario, se dovessi avere una sola lista, avrai un solo posto un cui dovrai tenere traccia delle modifiche sulle quantitΓ in magazzino.
Generalmente si duplica il codice perchè ci sono due o tre piccole differenze tra una parte e l'altra del software. Questo permette di condividere le parti comuni del codice, ma allo stesso tempo avrai dei duplicati di parti che fanno la stessa cosa. Rimuovere questi duplicati, significa creare un'astrazione che permette di gestire queste differenze attraverso un unico metodo/modulo/classe.
Ottenere la sufficiente astrazione puΓ² essere complicato. Per questo dovresti seguire i principi SOLID, approfonditi nella sezione Classi. Un'astrazione non ottimale potrebbe anche essere peggio del codice duplicato, per cui fai attenzione! Non ripeterti, altrimenti dovrai aggiornare tutte le occorrenze della stessa logica ogni volta che vorrai cambiare qualcosa.
Da evitare
function showDeveloperList(developers) {
developers.forEach((developer) => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach((manager) => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}Bene:
function showEmployeeList(employees) {
employees.forEach((employee) => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case 'manager':
data.portfolio = employee.getMBAProjects();
break;
case 'developer':
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}Estendi un oggetto con Object.assign
Da evitare
const menuConfig = {
title: null,
body: 'Bar',
buttonText: null,
cancellable: true
};
function createMenu(config) {
config.title = config.title || 'Foo';
config.body = config.body || 'Bar';
config.buttonText = config.buttonText || 'Baz';
config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);Bene:
const menuConfig = {
title: 'Order',
// User did not include 'body' key
buttonText: 'Send',
cancellable: true
};
function createMenu(config) {
config = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
// config adesso sarΓ : {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);Non usare valori flag (true/false) come parametri di una funzione
Un valore di tipo flag indica che la tua funzione puΓ² eseguire piΓΉ di una sola operazione. Una funzione dovrebbe eseguire una sola operazione. Separa la tua funzione se deve eseguire piΓΉ di una operazione in base al parametro flag che riceve in input.
Da evitare
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}Bene:
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}Evitare effetti inattesi (parte 1)
Una funzione puΓ² generare un effetto collaterale se fa altro oltre a ricevere un valore e restituirne uno o piΓΉ. L'effetto collaterale potrebbe essere scrivere su un file, modificare una variabile globale o accidentalmente girare tutti i tuoi soldi ad uno sconosciuto.
Probabilmente, e occasionalmente, avrai bisogno di generare un effetto collaterale: come nell'esempio precedente magari proprio scrivere su un file. Quello che dovrai fare Γ¨ centralizzare il punto in cui lo fai. Non avere piΓΉ funzioni e classi che fanno la stessa cosa, ma averne un servizio ed uno soltanto che se ne occupa.
La questione piΓΉ importante Γ¨ evitare le insidie che stanno dietro ad errate manipolazioni di oggetti senza alcuna struttura, utilizzando strutture che possono essere modificate da qualunque parte. Se riuscirai ad evitare che questo accada...sarai ben piΓΉ felice della maggior parte degli altri programmatori.
Da evitare
// Variable globale utilizzata dalla funzione seguente.
// Nel caso in cui dovessimo utilizzarla in un'altra funzione a questo punto si tratterebbe di un array e potrebbe generare un errore.
let name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split(' ');
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];Bene:
function splitIntoFirstAndLastName(name) {
return name.split(' ');
}
const name = 'Ryan McDermott';
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];Evitare effetti inattesi (parte 2)
In Javascript i valori primitivi sono passati come valori, mentre oggetti ed array vengono passati come reference. Nel caso di oggetti ed array, se la tua funzione modifica l'array contenente un carrello della spesa, per esempio, aggiungendo o rimuovendo un oggetto da acquistare, tutte le altre funzioni che utilizzeranno l'array carrello saranno condizionati da questa modifica. Questo potrebbe essere un bene ed un male allo stesso tempo. Immaginiamo una situazione in cui questo Γ¨ un male:
l'utente clicca sul tasto "Acquista", che richiamerΓ una funzione acquista che effettua una richiesta ed invia l'array carrello al server. Per via di una pessima connessione, il metodo acquista riproverΓ ad effettuare la richiesta al server. Cosa succede se nello stesso momento accidentalmemte l'utente clicca su "Aggiungi al carrello" su di un oggetto che non ha intenzione di acquistare prima che venga eseguita nuovamente la funzione?
VerrΓ inviata la richiesta con il nuovo oggetto accidentalmente aggiunto al carrello utilizzando la funzione aggiungiOggettoAlCarrello.
Un'ottima soluzione Γ¨ quella di di clonare sempre l'array carrello, modificarlo e restituire il clone.
Questo ci assicurerΓ che che nessun'altra funzione che gestisce il carrello subirΓ cambiamenti non voluti.
Due precisazioni vanno fatte su questo approccio:
-
Potrebbe essere che tu voglia realmente modificare l'oggetto in input, ma vedrai che utilizzando questo approccio ti accorgerai che questi casi sono veramente rari. La maggior parte delle volte dovrai utilizzare questo approccio per non generare effetti inattesi
-
Clonare oggetti molto grandi potrebbe essere davvero dispendioso in termini di risorse. Fortunatamente questo non è un problema perchè esistono ottime librerie che permettono di utilizzare questo approccio senza dispendio di memoria e più velocemente rispetto al dovelo fare manualmente.
Da evitare
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};Bene:
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};Non aggiungere funzioni globali
Contaminare o aggiungere delle variabili globali Γ¨ una pratica sconsigliata, in quanto potresti entrare in conflitto con altre librerie e chi utiliza le tue API potrebbe non accorgersene fintanto che non si trova in produzione, generando un'eccezione.
Facciamo un esempio pratico: supponiamo che tu voglia estendere il costrutto Array nativo di JavaScript aggiungendo il metodo diff che mostra le differenze tra due array. Come puoi fare?
Potresti scrivere il metodo utilizzando Array.prototype, che però potrebbe entrare in conflitto con con un'altra libreria che fa la stessa cosa. Cosa succederebbe se anche l'altra libreria utilizzasse diff per trovare le differenze tra due array? Ecco perchè è molto meglio utilizzare le classi ES2015/ES6 e semplicemente estendere Array.
Da evitare
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};Bene:
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}Preferisci la programmazione funzionale a quella imperativa
Programmazione funzionale - Programmazione imperativa
Javascript non Γ¨ un linguaggio funzionale alla stregua di Haskell, ma entrambi hanno qualcosa che li accomuna. I linguaggi funzionali generalmente sono piΓΉ puliti e facili da testare. Preferisci questo stile se possibile.
Da evitare
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}Bene:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
const totalOutput = programmerOutput
.map(output => output.linesOfCode)
.reduce((totalLines, lines) => totalLines + lines);Incapsula le condizioni
Da evitare
if (fsm.state === 'fetching' && isEmpty(listNode)) {
// ...
}Bene:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}Evita di verificare condizioni in negativo
Da evitare
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}Bene:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}Evita le condizioni
Sembrerebbe un task impossibile. Ad un rapido sguardo molti sviluppatori potrebbero pensare "come posso pensare di far funzionare qualcosa senza utilizzare un if?"
La risposta Γ¨ che puoi utilizzare il polimorfismo per ottenere lo stesso risultato in molti casi.
La seconda domanda generalmente è "Ottimo! Ma perchè dovrei farlo?".
La risposta Γ¨ data in uno dei concetti precedentemente descritti: una funzione dovrebbe eseguire una sola operazione.
Quando hai una Classe con delle funzioni che utilizzano lo stato if stai dicendo all'utente che la tua funzione puΓ² fare piΓΉ di una operazione.
Da evitare
class Airplane {
// ...
getCruisingAltitude() {
switch (this.type) {
case '777':
return this.getMaxAltitude() - this.getPassengerCount();
case 'Air Force One':
return this.getMaxAltitude();
case 'Cessna':
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}Bene:
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}Evita la validazione dei tipi (parte 1)
JavaScript Γ¨ un linguaggio non tipizzato, il che significa che le tue funzioni possono accettare qualunque tipo di argomento. Qualche volta potresti essere tentato da tutta questa libertΓ e potresti essere altrettanto tentato di veririfcare il tipo di dato ricevuto nella tua funzione. Ci sono molti modi per evitare di dover fare questo tipo di controllo. Come prima cosa cerca scrivere API consistenti.
Da evitare
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location('texas'));
}
}Bene:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}Evita la validazione dei tipi (part 2)
Se stai lavorando con tipi di dati primitivi come stringhe o interi e non puoi utilizzare il paradigma del polimorfismo, ma senti ancora l'esigenza di validare il tipo di dato considera l'utilizzo di TypeScript. Γ una vlidissima alternativa al normale JavaScript che fornicsce una validazione di tipi statica utilizzando la sintassi JavaScript. Il problema con la validazione manuale dei tipi utilizzando lo standard JavaScript Γ¨ che richiede tanto codice extra che non compensa la scarsa leggibilitΓ del codice ottenuto. Mantieni pulito il tuo codice, scrivi dei test validi e cerca di fare delle buone revisioni del coidice. Altrimenti utilizza TypeScript (che come detto in precedenza Γ¨ una validissima alternativa!)
Da evitare
function combine(val1, val2) {
if (typeof val1 === 'number' && typeof val2 === 'number' ||
typeof val1 === 'string' && typeof val2 === 'string') {
return val1 + val2;
}
throw new Error('Must be of type String or Number');
}Bene:
function combine(val1, val2) {
return val1 + val2;
}Non ottimizzare eccessivamente
I browser moderni eseguono un sacco di ottimizzazione "sottobanco". Molte volte, quando cerchi di ottimizzare il tuo codice, stai perdendo tempo prezioso. Ci sono tante guide che aiutano a capire quali tipi di ottimizzazione sono superflui e quali no. Utilizza queste guide nel frattempo, fintanto che queste lacune non verranno colmate.
Da evitare
// Nei vecchi browser ogni volta che in un ciclo verifichi `list.length` potrebbe
// essere dispendioso per via del suo ricalcolo. Nei browser moderni
// questa operazione Γ¨ stata ottimizzata
for (let i = 0, len = list.length; i < len; i++) {
// ...
}Bene:
for (let i = 0; i < list.length; i++) {
// ...
}Rimuovi il codice inutilizzato
Il codice inutilizzato Γ¨ dannoso quanto il codice duplicato. Non c'Γ¨ motivo per cui tenerlo nel tuo codebase. Se realmente non viene utilizzato rimuovilo! SarΓ comunque presente nella storia del tuo file se utilizzi un sistema di versioning nel caso in cui dovesse servirti.
Da evitare
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');Bene:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');Oggetti e strutture dati
Utilizza getter e setter
Utilizzare getter e setter per acceedere ai dati di un oggetto puΓ² essere meglio che accedere direttamente alle sue proprietΓ . Ti starai sicuramente chiedendo il motivo. Eccoti qualche motivo per cui utilizzare getter e setter:
- Quando hai bisogno di eseguire un'operazione sul dato recuperato, non devi andare a modificarlo ogni volta che accedi al dato nel tuo codice.
- Valida il dato nel momento in cui lo setti con
set - Incapsula la rappresentazione interna
- Γ piΓΉ facile loggare e gestire gli errori quando imposti o recuperi un dato
- Puoi caricare i dati del tuo oggetto in modalitΓ lazy, per esempio caricandoli dal server.
Da evitare
function makeBankAccount() {
// ...
return {
balance: 0,
// ...
};
}
const account = makeBankAccount();
account.balance = 100;Bene:
function makeBankAccount() {
// questa proprietΓ Γ¨ privata
let balance = 0;
// un "getter", la rende pubblica restituendola in questo modo
function getBalance() {
return balance;
}
// un "setter", la rende pubblica in questo modo
function setBalance(amount) {
// ... e la valida prima di impostarla
balance = amount;
}
return {
// ...
getBalance,
setBalance,
};
}
const account = makeBankAccount();
account.setBalance(100);Imposta proprietΓ private in un oggetto
PuΓ² essere fatto attraverso le closure (per ES5 e versioni precedenti).
Da evitare
const Employee = function(name) {
this.name = name;
};
Employee.prototype.getName = function getName() {
return this.name;
};
const employee = new Employee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefinedBene:
function makeEmployee(name) {
return {
getName() {
return name;
},
};
}
const employee = makeEmployee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John DoeClassi
Utilizza le classi ES2015/ES6 piuttosto che le funzioni di ES5
Γ molto difficile ottenere leggibilitΓ sull'ereditarietΓ , costrutti e metodi in definizioni di oggetti in ES5. Se hai bisogno di ereditarietΓ (e bada bene, non Γ¨ detto che tu ne abbia bisogno), utilizza il costrutto Class di ES2015/ES6. Altrimenti utilizza piccole funzioni fintanto che non avrai bisogno di gestire oggetti piΓΉ complessi.
Da evitare
const Animal = function(age) {
if (!(this instanceof Animal)) {
throw new Error('Instantiate Animal with `new`');
}
this.age = age;
};
Animal.prototype.move = function move() {};
const Mammal = function(age, furColor) {
if (!(this instanceof Mammal)) {
throw new Error('Instantiate Mammal with `new`');
}
Animal.call(this, age);
this.furColor = furColor;
};
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};
const Human = function(age, furColor, languageSpoken) {
if (!(this instanceof Human)) {
throw new Error('Instantiate Human with `new`');
}
Mammal.call(this, age, furColor);
this.languageSpoken = languageSpoken;
};
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};Bene:
class Animal {
constructor(age) {
this.age = age;
}
move() { /* ... */ }
}
class Mammal extends Animal {
constructor(age, furColor) {
super(age);
this.furColor = furColor;
}
liveBirth() { /* ... */ }
}
class Human extends Mammal {
constructor(age, furColor, languageSpoken) {
super(age, furColor);
this.languageSpoken = languageSpoken;
}
speak() { /* ... */ }
}Concatena i metodi
Questo pattern Γ¨ molto utilizzato in JavaScript e puoi trovarne applicazione in molte liberie come jQuery e Lodash. Permette al tuo codice di essere maggiormente espressivo e meno verboso.
Proprio per questo motivo, insisto, utilizza la concatenazione dei metodi e guarda come puΓ² essere piΓΉ pulito il tuo codice. Nei metodi della tua classe, semplicemente restituisci il riferimento this alla fine di ogni metodo, in modo da poter concatenare altri metodi.
Da evitare
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
}
setModel(model) {
this.model = model;
}
setColor(color) {
this.color = color;
}
save() {
console.log(this.make, this.model, this.color);
}
}
const car = new Car('Ford','F-150','red');
car.setColor('pink');
car.save();Bene:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
// NOTA: restituisci this per poter concatenare altri metodi.
return this;
}
setModel(model) {
this.model = model;
// NOTA: restituisci this per poter concatenare altri metodi.
return this;
}
setColor(color) {
this.color = color;
// NOTA: restituisci this per poter concatenare altri metodi.
return this;
}
save() {
console.log(this.make, this.model, this.color);
// NOTA: restituisci this per poter concatenare altri metodi.
return this;
}
}
const car = new Car('Ford','F-150','red')
.setColor('pink')
.save();Preferisci una struttura compositiva all'ereditarietΓ
Come dichiarato dalla Gang of four in Design Patterns dovresti preferire la strutturazione all'ereditarietΓ quando puoi. Ci sono validi motivi per utilizzare l'ereditarietΓ e altrettanto validi motivi per utilizzare la strutturazione. Il punto principale di questo assunto Γ¨ che mentalmente sei portato a preferire l'ereditarietΓ . Prova a pensare alla strutturazione per risolvere il tuo problema: tante volte Γ¨ davvero la soluzione migliore. Ti potresti chiedere: "Quando dovrei utilizzare l'ereditarietΓ ?". Dipende dal problema che devi affrontare, ma c'Γ¨ una discreta lista di suggerimenti che ti potrebbero aiutare a capire quando l'ereditarietΓ Γ¨ meglio della strutturazione:
- L'estensione che stai mettendo in atto rappresenta una relazione di tipo "Γ¨-un" e non "ha-un" (Umano->Animale vs. Utente->DettagliUtente).
- Puoi riutilizzare il codice dalla classe padre
- Vuoi fare cambiamenti globali a tutte le classi estese tramite la classe di partenza.
Da evitare
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
// ...
}
// Male because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
constructor(ssn, salary) {
super();
this.ssn = ssn;
this.salary = salary;
}
// ...
}Bene:
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}
// ...
}
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}SOLID
Single Responsibility Principle (SRP) (Principio di singola responsabilitΓ )
Come indicato in Clean code, "Non dovrebbe mai esserci più di un solo motivo per modificare una classe". La tentazione è sempre quella di fare un'unica classe con molte funzionalità , come quando vuoi portarti un unico bagaglio a bordo. Il problema con questo approccio è che le tue classi non saranno concettualmente coese e ti potrebbero dare più di una ragione per modificarle in seguito. Minimizzare il numero di volte in cui modificare una classe è importante. à importante perchè quando ci sono troppe funzionalità in una classe è difficile capire che effetto avrà sulle classe che la estendono, nel caso in cui farai un cambiamento.
Da evitare
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}Bene:
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
// ...
}
}
class UserSettings {
constructor(user) {
this.user = user;
this.auth = new UserAuth(user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...
}
}
}Open/Closed Principle (OCP) (Principio aperto/chiuso)
Come dichiarato da Bertrand Meyer, "Le entitΓ di un software (Classi, moduli, funzioni, etc.) dovrebbero essere aperte all'estensione, ma chiuse alla modifica. Cosa significa esattamente? Quello che intende Γ¨ che dovresti dare ai tuoi utilizzatori la possibilitΓ di aggiungere nuove funzionalitΓ , non modificando quelle esistenti.
Da evitare
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = 'nodeAdapter';
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
if (this.adapter.name === 'ajaxAdapter') {
return makeAjaxCall(url).then((response) => {
// transform response and return
});
} else if (this.adapter.name === 'httpNodeAdapter') {
return makeHttpCall(url).then((response) => {
// transform response and return
});
}
}
}
function makeAjaxCall(url) {
// request and return promise
}
function makeHttpCall(url) {
// request and return promise
}Bene:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = 'ajaxAdapter';
}
request(url) {
// request and return promise
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = 'nodeAdapter';
}
request(url) {
// request and return promise
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then((response) => {
// transform response and return
});
}
}Liskov Substitution Principle (LSP) (Principio di sostituzione di Liskov)
Questo nome sembra molto piΓΉ spaventoso di quello che in realtΓ significa. Formalmente la sua defnizione Γ¨ "Se S Γ¨ un sottotipo di T, allora gli oggetti di tipo T possono essere sostituiti con oggetti di tipo S (per esempio un oggetto di tipo S puΓ² sostituire un oggetto di tipo T) senza modificare alcuna della proprietΓ del software (correttezza, compito da svolgere, etc.)". Questa definizione suona comunque complessa.
Forse una spiegazione piΓΉ esaustiva potrebbe essere: "Se hai una classe figlio che estende una classe genitore allora le due classi possono essere intercambiate all'interno del codice senza generare errori o risultati inattesi". Potrebbe esssere ancora poco chiaro, ma vediamo con un esempio (Quadrato/Rettangolo): matematicamente il Quadrato Γ¨ un Rettangolo, ma se il tuo modello eredita una relazione di tipo "Γ¨-un", potresti avere presto qualche problema.
Da evitare
class Rectangle {
constructor() {
this.width = 0;
this.height = 0;
}
setColor(color) {
// ...
}
render(area) {
// ...
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width;
}
setHeight(height) {
this.width = height;
this.height = height;
}
}
function renderLargeRectangles(rectangles) {
rectangles.forEach((rectangle) => {
rectangle.setWidth(4);
rectangle.setHeight(5);
const area = rectangle.getArea(); // Sbagliato: Restituisce 25 anche
// per il quadrato. Dovrebbe essere 20.
rectangle.render(area);
});
}
const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);Bene:
class Shape {
setColor(color) {
// ...
}
render(area) {
// ...
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Shape {
constructor(length) {
super();
this.length = length;
}
getArea() {
return this.length * this.length;
}
}
function renderLargeShapes(shapes) {
shapes.forEach((shape) => {
const area = shape.getArea();
shape.render(area);
});
}
const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);Interface Segregation Principle (ISP) (Principio di segregazione delle interfacce)
JavaScript non utilizza Interfacce, quindi non Γ¨ possibile applicare questo principio alla lettera. Tuttavia Γ¨ importante nonostante la sua mancanza di tipizzazione.
ISP indica che "Gli utenti non dovrebbero mai esssere forzati a dipendere da interfacce che non utilizza.". Le interfacce sono contratti impliciti in JavaScript per via del duck-typing Un buon esempio in JavaScript potrebbe essere fatto per le classi che richiedono la deinizione di un set di proprietà molto grande. Non utilizzare classi che richiedono la definizione di molte proprietà per essere istanziate è sicuramente un beneficio perchè spesso non tutte queste proprietà richiedono di essere impostate per utilizzare la classe. Rendere questi parametri opzionali evita di avere un'interfaccia pesante.
Da evitare
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.animationModule.setup();
}
traverse() {
// ...
}
}
const $ = new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
animationModule() {} //Il piΓΉ delle volte potremmo non dover animare questo oggetto
// ...
});Bene:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.options = settings.options;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.setupOptions();
}
setupOptions() {
if (this.options.animationModule) {
// ...
}
}
traverse() {
// ...
}
}
const $ = new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
options: {
animationModule() {}
}
});Dependency Inversion Principle (DIP) (Principio di inversione delle dipendenze)
Questo principio sostanzialmente indica due cose:
- Moduli ad alto livello non dovrebbero dipendere da molidi a basso livello. Entrambi dovrebbero dipendere da moduli astratti.
- L'astrazione non dovrebbe dipendere da classi concrete. Le classi concrete dovrebbero dipendere da astrazioni.
A primo impatto questo concetto potrebbe essere difficile da capire, ma nel caso in cui tu abbia lavorato con AngularJS avrai sicuramente visto l'applicazione di questo principio nel concetto di Dependency injection (DI). Nonostante non sia esattamente identico come concetto, DIP evita che i moduli di alto livello conoscano i dettagli dei moduli di basso livello pur utilizzandoli.
Uno dei benefici di questo utilizzo Γ¨ che riduce la dipendenza tra due moduli.
La dipendenza tra due moduli è un concetto negativo, perchè ne rende difficile il refactor.
Come detto in precedenza, non essendoci il concetto di interfaccia in JavaScript, tutte le dipendenze sono contratte implicitamente.
Nell'esempio successivo, la dipendenza implicita Γ¨ che tutte le istanze di InventoryTracker avranno un metodo requestItems.
Da evitare
class InventoryRequester {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
class InventoryTracker {
constructor(items) {
this.items = items;
//Da evitare: abbiamo creato una dipendenza specifica per ogni istanza.
//Dovremmo fare in modo che requestItems dipenda dal metodo `request`
this.requester = new InventoryRequester();
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();Bene:
class InventoryTracker {
constructor(items, requester) {
this.items = items;
this.requester = requester;
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
class InventoryRequesterV1 {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
class InventoryRequesterV2 {
constructor() {
this.REQ_METHODS = ['WS'];
}
requestItem(item) {
// ...
}
}
//Avendo dichiarato la nostra dipendenza esternamente ed aggiunta dall'esterno, la possiamo
//sostituire facilemente con un'altra che utilizza WebSockets
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
inventoryTracker.requestItems();Test
Testare Γ¨ piΓΉ importante che rilasciare. Se non hai test o non ne hai un numero adeguato, non saprai se ad ogni rilascio puoi rompere qualcosa. Decidere quale sia il numero sufficiente di test dipende dal tuo team, ma cercare di coprire il 100% dei casi (per tutti gli stati ed i branch) vuol dire avere massima tranquillitΓ durante i rilasci. Questo significa che oltre ad utilizzare una suite di test valida, dovresti utilizzare anche un buon strumento di copertura.
Non ci sono scuse per non scrivere test. C'Γ¨ un'abbondanza di ottimi framewerk per i test in JavaScript quindi cerca quello piΓΉ adatto alle tue esigenze. Quando tu ed il tuo team avrete individuato quello piΓΉ giusto per voi, dovrete iniziare sempre a scrivere i test per ogni modulo/feature che introdurrete nel vostro software. Se il vostro approccio preferito Γ¨ quello del TestDrivenDevelopment (TDD) ottimo, ma assicurati di individuare i tuoi obiettivi prima di rilasciare ogni singola feature o eseguire il refactor di una esistente.
Un singolo comportamento per test
Da evitare
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles date boundaries', () => {
let date;
date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});Bene:
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles 30-day months', () => {
const date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
});
it('handles leap year', () => {
const date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
});
it('handles non-leap year', () => {
const date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});ConsequenzialitΓ
utilizza le Promise, non funzioni di callback
Le funzioni di callback non sono sempre chiare e possono generare un eccessivo numero di nidificazioni. Con ES2015/ES6 sono nativamente e globalmente accessibili. Utilizzale!
Da evitare
import { get } from 'request';
import { writeFile } from 'fs';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {
if (requestErr) {
console.error(requestErr);
} else {
writeFile('article.html', response.body, (writeErr) => {
if (writeErr) {
console.error(writeErr);
} else {
console.log('File written');
}
});
}
});Bene:
import { get } from 'request';
import { writeFile } from 'fs';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) => {
return writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});Async/Await sono addirittura piΓΉ chiari delle Promise
Le Promise sono una valida e chiara alternativa alle funzioni di callback, ma ES2017/ES8 offrono anche async and await che possono essere addirittura una soluzione piΓΉ migliore.
Tutto quello che devi fare non Γ¨ niente altro che scrivere una funzione che abbia prefisso async e puoi scrivere la tua logica senza dover concatenare con la kyword then.
Utilizza questo approccio se hai la possibilitΓ di utilizzare le feature ES2017/ES8!
Da evitare
import { get } from 'request-promise';
import { writeFile } from 'fs-promise';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) => {
return writeFile('article.html', response);
})
.then(() => {
console.log('File written');
})
.catch((err) => {
console.error(err);
});Bene:
import { get } from 'request-promise';
import { writeFile } from 'fs-promise';
async function getCleanCodeArticle() {
try {
const response = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
await writeFile('article.html', response);
console.log('File written');
} catch(err) {
console.error(err);
}
}Gestione degli errori
Generare errori Γ¨ una buona cosa. Vuol dire che l'esecuzione del tuo codice ha identificato precisamente quando nel tuo software qualcosa Γ¨ andato storto e ti permette di interromperne l'esecuzione nello stack corrente terminando il processo (in Node), e notificandolo attraverso la console.
Non ingnorare gli errori intercettati
Non fare niente con gli errori intercettati non rende possibile correggerli o reagire all'errore. Loggare gli errori nella console (console.log) non ti assicura di non perderti nel mare di log stampati in console.
Se invece inserisci il tuo codice all'interno del costrutto try/catch vuol dire riconoscere la possibilitΓ che esista un errore nel caso mettere in piedi un modo per gestirlo.
Da evitare
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}Bene:
try {
functionThatMightThrow();
} catch (error) {
// Un'ozione (piΓΉ visibile del console.log):
console.error(error);
// Un'altra opzione:
notifyUserOfError(error);
// Un'altra opzione:
reportErrorToService(error);
// Oppure usale tutte e tre!
}Non ignorare le Promise quando vengono rigettate
Per la stessa ragione per cui non dovresti ignorare gli errori con try/catch.
Da evitare
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
console.log(error);
});Bene:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
// Un'ozione (piΓΉ visibile del console.log):
console.error(error);
// Un'altra opzione:
notifyUserOfError(error);
// Un'altra opzione:
reportErrorToService(error);
// Oppure usale tutte e tre!
});Formattazione
La formattazione Γ¨ soggettiva. Come molte di quelle sopracitate, non esiste una regola assoluta e veloce che devi seguire. Il punto principale, perΓ², Γ¨ NON DISCUTERE della formattazione. Ci sono un sacco di strumenti che automatizzano questo processo. Usane uno. Γ uno spreco di tempo e denaro per gli sviluppatori discutere della formattazione.
Utilizza le maiuscole in modo consistente
JavaScript non Γ¨ tipizzato, per questo l'uso delle maiuscole puΓ² darti indicazioni sulle tue variabili, funzioni, etc. Queste regole sono soggettive, per questo tu ed il tuo team potrete scegliere quella che volete. Il punto Γ¨: non importa quale regola sceglierete, l'importante Γ¨ essere consistenti.
Da evitare
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}Bene:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}Richiami e dichiarazioni di funzioni dovrebbero essere vicini
Se una funzione ne richiama un'altra, mantieni queste funzioni verticalmente vicine nel sorgente. Idealmente, mantieni il richiamo subito sopra la dichiarazione. Generalmente tendiamo a leggere il codice dall'alto verso il basso, come un giornale. Proprio per questo manteniamolo leggibile seguendo questa modalitΓ .
Da evitare
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getManagerReview() {
const manager = this.lookupManager();
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();Bene:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
getManagerReview() {
const manager = this.lookupManager();
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();Commenti
Commenta solo il codice che ha un alto livello di complessitΓ
Commentare Γ¨ una scusa, non un requisito. Un buon codice spesso si spiega da solo. Da evitare
function hashIt(data) {
// The hash
let hash = 0;
// Length of string
const length = data.length;
// Loop through every character in data
for (let i = 0; i < length; i++) {
// Get character code.
const char = data.charCodeAt(i);
// Make the hash
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash &= hash;
}
}Bene:
function hashIt(data) {
let hash = 0;
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash &= hash;
}
}Non lasciare parti del tuo codice commentate all'interno dei sorgenti
I sistemi di versioning esistono per un motivo. Lascia il tuo codice vecchio alla storia.
Da evitare
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();Bene:
doStuff();Non utilizzare i commenti come un diario
Ricordati di usare sistemi di version control. Non c'Γ¨ motivo per cui codice non utilizzato, codice commentato e specialmente commenti con riferimenti a date esistano nel tuo file.
Usa git log per avere lo storico!
Da evitare
/**
* 2016-12-20: Removed monads, didn't understand them (RM)
* 2016-10-01: Improved using special monads (JP)
* 2016-02-03: Removed type-checking (LI)
* 2015-03-14: Added combine with type-checking (JR)
*/
function combine(a, b) {
return a + b;
}Bene:
function combine(a, b) {
return a + b;
}Evita di specificare di cosa si tratta
Generalmente Γ¨ solo fastidioso. Lascia che le tue funzioni e le tue variabili, insieme ad una corretta indentazioni ti diano una struttura visiva del tuo codice.
Da evitare
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
menu: 'foo',
nav: 'bar'
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
// ...
};Bene:
$scope.model = {
menu: 'foo',
nav: 'bar'
};
const actions = function() {
// ...
};Traduzioni
Questa guida Γ¨ disponibile in altre lingue:
Brazilian Portuguese: fesnt/clean-code-javascript
Spanish: andersontr15/clean-code-javascript
Chinese:
German: marcbruederlin/clean-code-javascript
Korean: qkraudghgh/clean-code-javascript-ko
Polish: greg-dev/clean-code-javascript-pl
Russian:
Vietnamese: hienvd/clean-code-javascript/
Japanese: mitsuruog/clean-code-javascript/
Indonesia:
andirkh/clean-code-javascript/
Italiano:
frappacchio/clean-code-javascript/
