Les bonnes pratiques JavaScript selon Google
Cet article est une adaptation du Google JavaScript Style Guide en FR. Je n'applique pas nécessairement moi-même toutes les bonnes pratiques listées ci-dessous mais si vous ne vous êtes jamais posé la question : « Comment maintenir un code gardant l'équilibre entre lisibilité et performance avec mon équipe ou les personnes susceptibles de relire mon code » cet article peut s'avérer intéressant. Si vous avez déjà vos pratiques : c'est peut-être l'occasion (comme pour moi) d'en revoir certaines.
Google JavaScript Style Guide en français
Vous pourrez trouver l'article original Google JavaScript Style Guide derrière ce lien. Au moment où je réalise cette traduction, le document est en version 2.93. À partir de maintenant, tout ce qui suit provient de l'article de Google.
Avant-propos
JavaScript est le principal langage de programmation de la partie cliente d'un navigateur web utilisé par beaucoup de projets Google open-source. Ce guide de style représente la liste des bonnes et des mauvaises pratiques lors d'un développement JavaScript.
Règles du langage JavaScript
var
Déclarations des variables avec var : Toujours - En savoir plus
Quand vous ne spécifiez pas var, la variable est attachée au contexte global de l'application, venant écraser des valeurs de variables potentiellement existantes. De la même manière, sans cette déclaration, il est difficile de définir à quelle portée lexicale la variable est rattachée (ex : La variable pourrait tout aussi bien être attachée à document, à window ou à la fonction courante). Donc n'oubliez jamais de déclarer avec var.
Constante
- Utiliser des NAMES_LIKE_THIS comme nom de variable pour les valeurs constantes.
- Utiliser @const (en commentaire) pour indiquer la non réaffectabilité d'une variable (une variable ou une propriété).
- Ne jamais utiliser le mot clé const car il n'est pas supporté par Internet Explorer.
En savoir plus
Valeurs constantes
Si la valeur d'une variable doit restée constante et inchangeable tout au long de l'exécution du programme, on peut lui donner un nom tel que CONSTANT_VALUE_CASE. Les variable nommées en ALL_CAPS impliquent donc le commentaire @const (car la valeur n'est pas réaffectable).
Les types primitifs (Number, String et Boolean) peuvent être des valeurs constantes.
La constance d'un type Object est plus subjective — Les objets peuvent être considérés comme constant uniquement s'ils ne montrent aucun signe de changement d'état. Cela n'est pas nécésairement triviale à appréhender pour un compilateur.
Pointeurs constants (variables et propriétés)
La notation @const en commentaire d'une variable ou d'une propriété implique qu'elle ne sera pas réaffectable. Ceci est appliquable par un compilateur lors de la compilation. Cette convention est cohérente avec le mot-clé const (que nous n'utilisons pas en raison du manque de soutien dans Internet Explorer).
La notation @const sur une méthode implique que cette fonction ne sera pas héritable par des sous classes.
La notation @const sur un constructeur implique que cette classe ne pourra pas être hérité (comme le final de Java).
Exemples
A noter que @const n'implique pas nécéssairement une écriture de variable comme celle-ci : CONSTANT_VALUES_CASE. Cependant, écrire une variable comme cela CONSTANT_VALUES_CASE implique @const.
/**
* Temps maximal pour la Demande en millisecondes.
* @type {number}
*/
goog.example.TIMEOUT_IN_MILLISECONDS = 60 * 1000;
Le nombre de secondes dans une minute ne change jamais. C'est une valeur constante. L'écriture ALL_CAPS requière le commentaire @const, et donc la constante ne peut pas être réaffectée.
Les compilateurs open-source permettront à une variable d'être réaffectée si elle n'est pas marquée comme @const.
/**
* URL associée à la Carte en tant que chaine de caractères.
* @const
*/
MyClass.fetchedUrlCache_ = new goog.structs.Map();
/**
* La Classe ne peut pas être héritée.
* @const
* @constructor
*/
sloth.MyFinalClass = function() {};
Dans ce cas, le pointeur ne sera jamais réaffectée, cependant il peut changer d'un appel à l'autre (C'est pourquoi il est écrit en camelCase, et non en ALL_CAPS).
Point-virgule
Toujours utiliser les point-virgules - En savoir plus
Une utilisation implicite des point-virgules (ne pas les mettre) pose problème, des problèmes difficiles à debboguer. Ne le faites pas. Ça vaudra mieux pour vous.
Il y a quelques endroits ou l'oubli de point-virgule est particulièrement dangereux :
MyClass.prototype.myMethod = function() {
return 42;
} // Pas de point-virgule ici.
(function() {
// Du code à exécuter ici et isolé dans un champ lexical localisé.
})();
Quel est le problème ?
Erreur JavaScript - Tout d'abord la première fonction retourne 42 puis est appelé en tant que paramètre par la seconde fonction, quand 42 est appelé cela cause une erreur.
var x = {
'i': 1,
'j': 2
} // Pas de point-virgule ici.
// Essayons de faire une chose sur Internet Explorer et une autre sur Firefox.
// Je sais que vous ne devez pas écrire du code comme ça, et que c'est un peu tiré par les cheveux.
[normalVersion, ffVersion][isIE]();
Quel est le problème ?
Vous aurez probablement une erreur comme 'no such property in undefined' à l'exécution car il essayera d'appeler x[ffVersion][isIE]().
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // Pas de point-virgule ici.
// Exécution conditionnelle a la bash.
-1 == resultOfOperation() || die();
Quel est le problème ?
die est exécuté à moins que resultOfOperation() ai comme valeur NaN et THINGS_TO_EAT sera assigné par le résultat de la fonction die().
Pourquoi ?
JavaScript a besoin d'un point-virgule à la fin des instructions, sauf quand il peut être sur et certain de deviner les fins d'instructions. Dans chacun des exemples précédents, une déclaration de fonction ou de litéral objet/tableau (écriture au format JSON) est utilisé près d'une instruction. Les crochets de fermeture ne sont pas assez pour deviner la fin d'une instruction. Javascript ne termine jamais une instruction si le caractère suivant est un opérateur de fermeture.
Cela surprend pas mal de gens, aussi soyez sur de ne pas oublier de point-virgules dans vos travaux.
Clarification : point-virgules et fonctions
Les point-virgules doivent être placé à la fin d'une affectation de fonction, mais pas à la fin d'une déclaration de fonction. Cette distinction est facilement illustrée avec l'exemple suivant :
var foo = function() {
return true;
}; // Point-virgule ici.
function foo() {
return true;
} // Pas de point-virgule ici.
Fonction imbriquée
Oui - En savoir plus
Les fonctions imbriquées peuvent s'avérer très utiles, par exemple en ce qui concerne la portée des fonctions ou pour cloisonner des fonctions d'aide au débogguage ou développement. Utilisez les sans retenu.
Déclaration de fonction dans un block
Non - En savoir plus
Ne faites pas ça :
if (x) {
function foo() {}
}
Bien que la plupart des moteurs d'exécution JavaScript supporte les Déclaration de fonction dans les blocks, cela ne fait pas parti des spécifications ECMAScript (voir ECMA-262, partie 13 et 14). Ce type d'implémentation peut donc s'avérer incompatible avec d'autre implémentation ou avec des propositons futures d'évolution des normes ECMAScript. ECMAScript autorise les déclarations de fonction uniquement dans le contexte globale (la racine des scripts) ou celui d'une fonction. À la place, il faut utiliser l'assignation de fonction dans une variable pour créer une fonction dans un block :
if (x) {
var foo = function() {}
}
Exceptions
Oui - En savoir plus
Vous ne pouvez tout simplement pas éviter les exceptions si vous faites quelque chose de non-trivial (à l'aide d'un Framework, etc...). Allez-y.
Exceptions personalisées
Oui - En savoir plus
Sans exceptions personalisées, retourner les informations d'erreur d'une fonction qui elle même retourne une valeur est assez délicat, pour ne pas dire bancale. Les mauvaise solutions sont de passer une variable erreur pour récupérer celle-ci ou d'avoir une propriété dans un objet passé destiné à accueillir l'erreur. Cela s'apparente à un détournement de l'exception de base. Il est donc possible d'utiliser des exceptions customisées quand cela est nécessaire.
Fonctionalités standards
Toujours préférer des fonctionalités standards à des fonctionalités non-standards - En savoir plus
Pour une portabilité et une compatibilité maximum, il faut toujours préférer des fonctionalités standards à des fonctionalités non-standards (ex : string.charAt(3) à la place de string[3] ou l'accès au DOM avec fonctions standards au lieu d'utiliser un raccourci spécifique au compilateur).
Le type Object des types primifs
Non - En savoir plus
Il n'y a pas de raison d'utiliser le type Object correspondant à un type primitif, en plus cela est dangereux :
var x = new Boolean(false);
if (x) {
alert('hi'); // Affiche 'hi' quand même.
}
Ne faites pas ça !
Cependant le cast d'objet par cette méthode est parfais (pas de new).
var x = Boolean(0);
if (x) {
alert('hi'); // Ce message ne s'affichera jamais.
}
typeof Boolean(0) == 'boolean'; // Bon.
typeof new Boolean(0) == 'object'; // Pas sécurisé.
Cela est très utile pour caster les types number, boolean et string.
Hiérarchie multi-niveau par prototype
Pas recommandé - En savoir plus
La Hiérarchie multi-niveau par prototype est la manière dont le JavaScript gère l'héritage. Vous avez une hierarchie multi-niveau quand une fonction définie D est complètement inclue dans une fonction définie B via leurs prototype. Cette hierarchisation est plus difficile à maintenir qu'il n'y parait !
Pour cette raison, il vaut mieux utiliser goog.inherits() de la Closure Library ou quelque chose de similaire.
function D() {
goog.base(this)
}
goog.inherits(D, B);
D.prototype.method = function() {
...
};
Déclaration de méthode et de propriété
/** @constructor */ function SomeConstructor() { this.someProperty = 1; } Foo.prototype.someMethod = function() { ... }; - En savoir plus
Bien qu'il y ai plusieurs manière d'attacher des méthodes et propriétés à un objet créer via new, il vaut mieux préférer :
Foo.prototype.bar = function() {
/* ... */
};
La meilleure manière de procéder pour les autres initialisations est celle effectuée dans le constructeur en tant qu'attribut :
/** @constructor */
function Foo() {
this.bar = value;
}
Pourquoi ?
Les moteurs JavaScript actuelle ont une optimisation basée sur la "forme" d'un objet ; ajouter une propriété à un objet (également d'écraser une valeur incluse dans le prototype) change la forme et dégrade les performances.
delete
Préferez this.foo = null - En savoir plus
Foo.prototype.dispose = function() {
this.property_ = null;
};
à la place de
Foo.prototype.dispose = function() {
delete this.property_;
};
Dans les moteurs JavaScript moderne, changer le nombre de propriété d'un objet est plus lent que de réassigner une valeur. Le mot clé delete doit être évité sauf s'il est nécésaire de détruire une propriété d'un objet itératif avec une liste de clé/valeur, ou pour changer son nom (le nom de la clé dans l'objet).
Closures
Oui mais attention - En savoir plus
La capacité de créer des champs lexicaux ou contexte d'exécution persistant (closure) est la fonctionalité la plus utile et la plus étudiée de JS. Vous trouverez ici une bonne description de comment fonctionne les closures.
Une chose qu'il faut cependant garder à l'esprit est qu'un contexte d'exécution garde un pointeur sur le champ lexical qu'il englobe. Il en résulte que, attacher un contexte d'exécution à un élément du DOM peut créer des références circulaire et une fuite de mémoire. Prenons l'exemple de code suivant comme exemple :
function foo(element, a, b) {
element.onclick = function() { /* utiliser ici a et b */ };
}
Le contexte d'exécution de la fonction garde une référence sur a, et b même s'ils ne sont jamais utilisé. Cependant les éléments eux-mêmes (dans la fonction interne) garde une référence au contexte d'exécution parent, nous avons donc une boucle qui ne sera jamais nettoyée par le récupérateur de mémoire. Une telle situation doit plutôt être développée de cette manière, avec la structure suivante :
function foo(element, a, b) {
element.onclick = bar(a, b);
}
function bar(a, b) {
return function() { /* utiliser a et b */ }
}
eval()
Seulement pour la désérialisation (ex: Pour l'évaluation de réponses RPC) - En savoir plus
eval() rend la sémantique du code confuse et est dangereux à utiliser quand il contient des valeurs entré par un utilisateur. Il y a généralement un meilleur moyen, plus clair et plus sécurisé d'écrire le même code aussi son utilisation n'est pas autorisée.
Pour les réponses RPC vour pouvez utiliser l'objet JSON et lire le résultat avec JSON.parse() à la place de eval().
Nous avons par exemple un serveur qui retourne quelque chose comme ça :
{
"name": "Alice",
"id": 31502,
"email": "looking_glass@example.com"
}
var userInfo = eval(feed);
var email = userInfo['email'];
Si le flux retourné inclut du code JavaScript malicieux, quand il sera évalué grâce à eval(), ce code sera exécuté.
var userInfo = JSON.parse(feed);
var email = userInfo['email'];
Avec JSON.parse() un format JSON invalide (incluant du code executable JavaScript) jettera une exeption.
with() {}
Non - En savoir plus
Utiliser with va rendre obscure la sémantique de votre programme. Cela parceque l'objet passé en paramètre à with peut avoir des propriétés pouvant entré en conflit avec les variables locales à with, cela peut radicalement changer le fonctionnement de votre programme. Par exemple, que fait ce code ?
with (foo) {
var x = 3;
return x;
}
Réponse : rien. La variable locale x peut-être mis à mal par une des propriétes de foo, et peut-être même une propriété d'assignement, qui dans notre cas asignerait 3 à x et pourrait produire un comportement aberant.
this
Seulement dans une fonction constructeur, méthodes, et pour la persistance du champ lexical - En savoir plus
Le sens de this peut-être différent en fonction des situations. Parfois, il fait référence au contexte d'exécution d'un objet (dans la plupard des cas), le contexte de l'appelant (dans eval()), un noeud dans le DOM (quand il est utilisé dans un attribut d'évènement HTML), un objet instancié (constructeur) ou dans d'autre objets (si la fontion est appelé avec call() ou apply().
Parcequ'il est facile de s'emêler quand à la signification du this, sont utilisation doit se limiter à :
- dans les constructeurs
- dans les méthodes des objets (incluant la création de champs lexicaux persistant)
Boucle for-in
Seulement pour les itérations avec clés sur : object/map/hash - En savoir plus
Les boucles for-in sont souvent utilisés à tord avec les Array. Cette erreur vient du fait que la boucle ne s'effectut plus de l'index 0 à l'index length - 1 mais en lisant les clés des objets et des chaines de prototypes. Il y a donc quelques cas ou ce parcourt ne fonctionne pas :
function printArray(arr) {
for (var key in arr) {
print(arr[key]);
}
}
printArray([0,1,2,3]); // Ceci marche.
var a = new Array(10);
printArray(a); // Ceci ne marche pas.
a = document.getElementsByTagName('*');
printArray(a); // Ceci ne marche pas.
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a); // Ceci ne marche pas.
a = new Array;
a[3] = 3;
printArray(a); // Ceci ne marche pas.
Toujours utiliser des boucles normales pour parcourrir les tableaux :
function printArray(arr) {
var l = arr.length;
for (var i = 0; i < l; i++) {
print(arr[i]);
}
}
Les tableaux associatifs
Ne jamais utiliser un Array pour des tableaux de type : map/hash/associatif - En savoir plus
Les tableaux associatifs ne sont pas permis... ou du moins vous ne pouvez pas utiliser des clés d'index autre que des nombres dans les tableaux. Si vous avez besoin de map/hash : utiliser un Object plutôt qu'un Array car les fonctionalités dont vous avez besoin sont celles d'un objet et non celles d'un tableau. Les Array héritent seulement des Object (comme tous les autres objets en JS que vous pouvez utiliser comme Date, RegExp et String).
Déclaration de String sur plusieurs lignes
Non - En savoir plus
Ne faites pas ça :
var myString = 'A rather long string of English text, an error message \
actually that just keeps going and going -- an error \
message to make the Energizer bunny blush (right through \
those Schwarzenegger shades)! Where was I? Oh yes, \
you\'ve got an error and all the extraneous whitespace is \
just gravy. Have a nice day.';
Les espaces blancs en début de chaque ligne ne peuvent pas être retirés au moment de la compilation. Les espaces blancs après le Backslash conduit à des erreurs difficiles à débogguer ; de plus plusieurs moteur d'exécution JavaScript ne le supporte pas car effectivement cela ne fait pas parti des spécifications ECMAScript.
Utiliser la concaténation de chaine à la place :
var myString = 'A rather long string of English text, an error message ' +
'actually that just keeps going and going -- an error ' +
'message to make the Energizer bunny blush (right through ' +
'those Schwarzenegger shades)! Where was I? Oh yes, ' +
'you\'ve got an error and all the extraneous whitespace is ' +
'just gravy. Have a nice day.';
Écriture litérale des Array et Object (JSON)
Oui - En savoir plus
Utiliser la notation litérale (JSON) pour les constructeurs d'Array et d'Object
Les constructeurs de tableau sont source d'erreur à cause du passage des arguments.
// La taille est 3.
var a1 = new Array(x1, x2, x3);
// La taille est 2.
var a2 = new Array(x1, x2);
// Si x1 est un nombre et un entier naturel la taille sera celle de x1.
// Si x1 est un nombre mais n'est pas un entier naturel cela renverra une exception.
// De plus le tableau peut ne contenir qu'un élément avec x1 en tant que valeur.
var a3 = new Array(x1);
// La taille est 0.
var a4 = new Array();
A cause de cela, si quelqu'un change le code et passe 1 argument au lieu de 2, le tableau ne changera pas de taille.
Pour éviter ce genre de problèmes et cas étrange, utiliser toujours la version litérale.
var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];
Les constructeur d'objet n'ont pas le même problème : mais pour la lisibilité et la cohérance l'écriture litérale pour les objets est utilisé.
var o = new Object();
var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;
Peut-être écrit :
var o = {};
var o2 = {
a: 0,
b: 1,
c: 2,
'strange key': 3
};
Modifier le prototype des objets natifs
Non - En savoir plus
Modifier les objets natifs comme Object.prototype et Array.prototype est strictement interdit. Modifier d'autres objets natifs comme Function.prototype est moins dangereux mais toujours difficile à déboguer en production et doit être évité.
Les commantaires conditionnels d'Internet Explorer
Non - En savoir plus
Ne faites pas :
var f = function () {
/*@cc_on if (@_jscript) { return 2* @*/ 3; /*@ } @*/
};
Les commentaires conditionnels IE peuvent empêcher les outils automatiques de fonctionner correctement.
Convention de nommage et commentaires JavaScript
Cette partie sera traitée dans un second article vers lequel je mettrai un lien ici prochainement.
0 Commentaire
Choisir un Avatar depuis une url
Adresse : (64px x 64px)ou Changer la couleur de fond
et Choisir un Avatar dans la liste
Votre commentaire a été ajouter ci-dessous ! Si vous désirez le supprimer ultérieurement, servez vous du code suivant :
Les commentaires sont actuellement affiché du plus rescent au plus ancien. Vous pouvez inverser l'ordre en cliquant ci-dessous :
Soyez le premier à laisser un commentaire !
Je trouve ce commentaire pertinent ?