Fonctions
Arguments de fonction (2 maximum idéalement)
Limiter le nombre de paramètres de fonction est extrêmement important car cela facilite les tests. Au-delà de trois paramètres, cela provoque une explosion combinatoire nécessitant de tester une multitude de cas différents pour chaque argument.
Un ou deux arguments constituent le cas idéal, trois devant être évités si possible. Au-delà, une consolidation s'impose. Généralement, plus de deux arguments indiquent que votre fonction essaie de faire trop de choses. Si ce n'est pas le cas, un objet de plus haut niveau suffira souvent comme argument.
JavaScript permettant de créer des objets dynamiquement sans boilerplate de classe, utilisez un objet lorsque vous avez besoin de nombreux arguments.
Pour clarifier les propriétés attendues par la fonction, utilisez la syntaxe de déstructuration ES2015/ES6. Avantages :
- La signature de fonction indique immédiatement les propriétés utilisées
- Permet de simuler des paramètres nommés
- La déstructuration clone les valeurs primitives spécifiées de l'objet passé en paramètre, aidant à prévenir les effets de bord. Note : les objets/tableaux déstructurés ne sont PAS clonés.
- Les linters peuvent signaler les propriétés inutilisées - impossible sans déstructuration.
Mauvaise pratique :
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);Bonne pratique :
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});Une fonction doit avoir une seule responsabilité
Règle la plus importante en génie logiciel. Les fonctions multi-tâches sont difficiles à composer, tester et maintenir. Une fonction mono-responsabilité se refactorise facilement et améliore la lisibilité. Si vous ne retenez qu'un seul principe de ce guide, appliquez celui-ci pour surpasser nombre de développeurs.
Mauvaise pratique :
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}Bonne pratique :
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}Le nom d'une fonction doit décrire son action
Mauvaise pratique :
function addToDate(date, month) {
// ...
}
const date = new Date();
// It's hard to tell from the function name what is added
addToDate(date, 1);Bonne pratique :
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);Une fonction doit avoir un seul niveau d'abstraction
Plusieurs niveaux d'abstraction indiquent généralement une surcharge fonctionnelle. Découper les fonctions améliore la réutilisabilité et la testabilité.
Mauvaise pratique :
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...
});
}Bonne pratique :
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.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 parse(tokens) {
const syntaxTree = [];
tokens.forEach(token => {
syntaxTree.push(/* ... */);
});
return syntaxTree;
}Éliminer le code dupliqué
Évitez absolument la duplication de code. Cela multiplie les points de modification lors des changements logiques.
Imaginez gérer un restaurant avec plusieurs inventaires : tomates, oignons etc. Maintenir plusieurs listes obligerait à toutes les modifier lors d'une commande de tomates. Une liste unique simplifie les mises à jour !
La duplication survient souvent pour des éléments similaires aux différences nécessitant des fonctions distinctes. La solution est une abstraction unique gérant ces variations.
Une bonne abstraction est cruciale - suivez les principes SOLID de la section Classes. Une mauvaise abstraction est pire que la duplication. Mais si vous pouvez créer une bonne abstraction, faites-le ! Évitez la répétition pour ne pas modifier de multiples endroits pour un seul changement.
Mauvaise pratique :
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);
});
}Bonne pratique :
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);
});
}Définir des objets par défaut avec Object.assign
Mauvaise pratique :
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);Bonne pratique :
const menuConfig = {
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true
};
function createMenu(config) {
let finalConfig = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);
return finalConfig
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);Ne pas utiliser de flags comme paramètres
Un flag indique une fonction multi-tâches. Les fonctions doivent avoir une seule responsabilité. Découpez les fonctions si elles suivent des chemins de code différents basés sur un booléen.
Mauvaise pratique :
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}Bonne pratique :
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}Éviter les effets de bord (partie 1)
Un effet de bord survient quand une fonction modifie autre chose que ses entrées/sorties : écriture fichier, modification de variable globale, ou transfert d'argent involontaire.
Centralisez ces effets. N'ayez pas plusieurs fonctions ou classes écrivant dans un même fichier - utilisez un service dédié unique.
Évitez les pièges comme l'état partagé non structuré, les types mutables accessibles partout, et la non-centralisation des effets de bord. Cette pratique vous distinguera de la majorité des développeurs.
Mauvaise pratique :
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";
function splitIntoFirstAndLastName() {
name = name.split(" ");
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];Bonne pratique :
function splitIntoFirstAndLastName(name) {
return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];Éviter les effets de bord (partie 2)
En JavaScript, objets et tableaux sont mutables. Manipulez-les prudemment comme paramètres. Une fonction peut modifier leurs propriétés/contenu, causant des bugs ailleurs.
Prenons un panier d'achat modifié par une fonction. Si celle-ci ajoute un article, toutes les fonctions utilisant ce panier seront affectées. Imaginez ce scénario critique :
L'utilisateur clique sur "Acheter", lançant une requête réseau avec le panier. En cas de mauvaise connexion, la fonction retente. Si l'utilisateur ajoute accidentellement un article pendant ce processus, la requête finale inclura l'article non désiré.
Solution : clonez le panier dans addItemToCart, modifiez le clone et retournez-le. Ceci préserve les fonctions utilisant l'ancien panier.
Deux mises en garde :
Les cas nécessitant la modification directe d'objet sont rares. La plupart se refactorisent sans effets de bord.
Le clonage d'objets volumineux peut être coûteux. Utilisez des bibliothèques comme Immutable.js pour optimiser.
Mauvaise pratique :
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};Bonne pratique :
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};Ne pas écrire dans les fonctions globales
Polluer les portées globales risque des conflits entre bibliothèques. Exemple : étendre Array.prototype avec une méthode diff() pourrait entrer en conflit. Préférez les classes ES6 pour étendre les objets globaux.
Mauvaise pratique :
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};Bonne pratique :
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}Privilégier la programmation fonctionnelle
Bien que JavaScript ne soit pas un langage fonctionnel pur comme Haskell, privilégiez ce style pour un code plus propre et testable.
Mauvaise pratique :
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;
}Bonne pratique :
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.reduce(
(totalLines, output) => totalLines + output.linesOfCode,
0
);Encapsuler les conditions
Mauvaise pratique :
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}Bonne pratique :
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}Éviter les conditions négatives
Mauvaise pratique :
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}Bonne pratique :
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}Éviter les conditions
Remplacez les conditions par du polymorphisme. Les if indiquent une fonction multi-tâches. Rappelez-vous : une seule responsabilité.
Mauvaise pratique :
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();
}
}
}Bonne pratique :
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();
}
}Éviter le typage manuel (partie 1)
JavaScript étant non typé, vos fonctions acceptent tout type. Résistez à la vérification manuelle via des APIs cohérentes.
Mauvaise pratique :
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"));
}
}Bonne pratique :
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location("texas"));
}Éviter le typage manuel (partie 2)
Pour les types primitifs, envisagez TypeScript. Ses types statiques surpassent la verbosité des vérifications manuelles. Sinon, misez sur des tests solides et des revues de code.
Mauvaise pratique :
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");
}Bonne pratique :
function combine(val1, val2) {
return val1 + val2;
}Ne pas sur-optimiser
Les navigateurs modernes optimisent déjà abondamment. Ciblez uniquement les goulets d'étranglement avec des outils spécialisés.
Mauvaise pratique :
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}Bonne pratique :
for (let i = 0; i < list.length; i++) {
// ...
}Supprimer le code mort
Le code mort est aussi néfaste que la duplication. Supprimez-le s'il n'est pas utilisé. Il reste disponible dans l'historique de version.
Mauvaise pratique :
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");Bonne pratique :
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");