Les bonnes pratiques en Javascript

Le problème exposé dans ce sujet a été résolu.

Salut à tous ! ^^

J’essaye de coder en respectant au maximum les règles de bonnes pratiques.
Je puise actuellement ces dernières dans le Zen de Python, en essayant de les adapter à mes codes.

En Javascript, j’essaye de toujours expliciter le type de mes variables et de m’y tenir.
Mais c’est parfois très difficile d’y arriver tout en gardant un code optimisé.

J’essaye de transmettre les bonnes pratiques au travers d’exemples.
Dans mon dernier programme, je n’y suis pas arrivé et je n’arrive pas à résoudre ce problème.

Dans le fichier hours.js, lignes 24 à 26, je suis obligé de changer le type de mes variables et je n’aime pas trop.
De plus, les .toString() deviennent vite encombrants et rendent illisible le code.  (~ - ~)


Que puis-je améliorer dans ce programme pour respecter au mieux les règles de bonnes pratiques ?

Dites tout ce qui vous passe par la tête en voyant cet exemple. :-°

Edité par ache, rajout du code dont il est question :

// Parce que ce programme doit être parfait
'use strict';


// Spécifier une valeur par défaut pour
// connaître le type de la variable
var today       =   new Date(),
    i           =   0,
    j           =   0,
    now         =   '',
    miaou       =   '',
    hours       =   '',
    minutes     =   '',
    seconds     =   '';


function updateTime()
{
    hours       =   today.getHours();
    minutes     =   today.getMinutes();
    seconds     =   today.getSeconds();

    // Gestion des zéros
    hours       =   (hours < 10)   ? '0' + hours.toString()   : hours.toString();
    minutes     =   (minutes < 10) ? '0' + minutes.toString() : minutes.toString();
    seconds     =   (seconds < 10) ? '0' + seconds.toString() : seconds.toString();

    // Sans la ligne ci-dessous, ça ne fonctionnera pas
    today       =   new Date();

    return hours + ' : ' + minutes + ' : ' + seconds;
}


function updateClocks()
{
    now = updateTime();

    for (i = 1; i <= 60; i++)
    {
        for (miaou = '', j = 0; Math.random() < 0.8 && j < 7; j++)
        {
            miaou += 'u';
        }

        document.getElementById('clock' + i.toString()).value = 'Miaou' + miaou + ' ! ' + now;
    }
}


window.onload = function()
{
    // On remet les pendules à l'heure toutes les secondes
    setInterval(updateClocks, 1000);
};
+0 -0

Est-ce qu’il ne faudrait pas mettre l’affectation today = new Date(); au début de la fonction updateTime ?

En JS, comme dans beaucoup d’autres langages, les objets Date ne se mettent pas à jour à la date actuelle tout seuls. Donc tes pendules sont en retard d’une seconde.

EDIT

Par ailleurs, en JS, on préfère réaliser des setTimeout plutôt qu’un setInterval. Observe la différence entre tes horloges et celle de ton ordinateur au fur et à mesure que la page reste chargée pour voir pourquoi ;)

+0 -0

Dans le fichier hours.js, lignes 24 à 26, je suis obligé de changer le type de mes variables et je n’aime pas trop.
De plus, les .toString() deviennent vite encombrants et rendent illisible le code.  (~ - ~)


Ludwig

Pour ça tu peux déjà remplacer tes transformationd toString par la fonction padStart

Il y a tout une histoire avec la non utilisation de variable globale et le fait de préférer let et const à var.

Ici, par exemple today ferait mieux d’être const et local à la fonction updateTime. i et j devraient être déclarés uniquement dans updateClock.

Edit: Je me suis permis de rajouter ton code en secret afin que même dans 2 mois les gens comprennent de quoi on parle.

+0 -0

Merci pour vos réponses, j’ai mis à jour le script en tenant compte de vos remarques ! ^^

hours.js :

// On évite d'utiliser des variables non-déclarées
'use strict';

var i, now, miaou;

function updateClocks()
{
    now = (new Date()).toLocaleTimeString();

    for (i = 1; i <= 60; i++)
    {
        miaou = 'u'.repeat(Math.floor(Math.random() * 9));

        document.getElementById('clock' + i).value = 'Miaou' + miaou + ' ! ' + now;
    }

    setTimeout(updateClocks, 1000);
}

document.addEventListener('DOMContentLoaded', updateClocks);

Est-ce qu’il ne faudrait pas mettre l’affectation today = new Date(); au début de la fonction updateTime ?

Bien vu, c’est corrigé !

En JS, comme dans beaucoup d’autres langages, les objets Date ne se mettent pas à jour à la date actuelle tout seuls. Donc tes pendules sont en retard d’une seconde. Par ailleurs, en JS, on préfère réaliser des setTimeout plutôt qu’un setInterval. Observe la différence entre tes horloges et celle de ton ordinateur au fur et à mesure que la page reste chargée pour voir pourquoi ;)

amael

Malgré l’utilisation de setTimeout(), c’est encore le cas. Je pense que c’est impossible d’être pile à l’heure, sauf si on joue avec les millisecondes ?

Pour ça tu peux déjà remplacer tes transformationd toString par la fonction padStart

firm1

Merci de l’info !
Après une petite balade dans la doc, j’ai trouvé repeat() qui est similaire.


Également, je n’ai plus explicitement spécifié le type des variables dans mon code.
Je pense que c’est incohérent avec la philosophie du langage qui cherche justement à s’en abstraire.
S’il y a lieu de spécifier le type des variables, c’est sans doute dans des cas biens spécifiques où l’optimisation prend beaucoup d’importance.

Que pensez-vous de cette nouvelle version ?

@ache je viens de voir ton message, je vais le lire de ce pas :)

+0 -0

En gros :

for (let i = 1; i <= 60; i++)

Et :

const now = (new Date()).toLocaleTimeString();

Pareil pour miaou :)

+0 -0
for (let i = 1; i <= 60; i++)

ache

Dans la logique, il y a quelque chose qui m’échappe.

Je mets à jour les horloges toutes les secondes. Donc, à chaque seconde, on déclare une variable locale appelée i, puis on lui affecte la valeur 1.

J’ai l’impression qu’en utilisant une variable globale, on optimise le programme : à chaque seconde, on n’a plus besoin de re-déclarer la variable i, on peut se contenter d’uniquement lui affecter la valeur 1.

Évite les timeout et intervals, ce qu’il faut utiliser ici c’est window.requestAnimationFrame.

On est généralement à 60fps, à chaque frame tu vérifies si c’est le moment de changer l’heure (est-ce qu’on n’est plus dans la même seconde qu’avant), si oui tu changes l’heure. Sinon tu fais rien, c’est pas lourd.

+1 -0

J’ai remplacé var par let mais je ne sais pas encore très bien justifier pourquoi, je me renseignerai sur le sujet un peu plus tard.

@cepus :

J’ai essayé d’adopter ce nouvel outil (pour moi) mais parfois, l’heure affichée sur la page web est en décalage d’une demi-seconde avec celle de l’ordinateur.

hours.js :

// On évite d'utiliser des variables non-déclarées
'use strict';

let i, now, miaou, temp;

function refreshPage()
{
    for (i = 1; i <= 60; i++)
    {
        miaou = 'u'.repeat(Math.floor(Math.random() * 9));

        document.getElementById('clock' + i).value = 'Miaou' + miaou + ' ! ' + now;
    }
}

function updateClocks()
{
    temp = (new Date()).toLocaleTimeString();

    if (temp != now)
    {
        now = temp;
        refreshPage();
    }

    requestAnimationFrame(updateClocks);
}

document.addEventListener('DOMContentLoaded', updateClocks);

Je trouve que le code a déjà bien évolué grâce à vos conseils, merci merci ! :-°

Comme tu t’intéresses aux bonnes pratiques, voici une meilleure variante que la tienne :

'use strict';

let seconds = (new Date()).getSeconds();
document.addEventListener('DOMContentLoaded', updateClocks);

function updateClocks () {
  const now = (new Date()).getSeconds();

  if (seconds !== now) {
    seconds = now;
    refreshPage();
  }

  requestAnimationFrame(updateClocks);
}

function refreshPage () {
  for (let i = 1; i <= 60; i++) {
    const miaou = 'u'.repeat(Math.floor(Math.random() * 9));

    document.getElementById('clock' + i).value = 'Miaou' + miaou + ' ! ' + (new Date()).toLocaleTimeString();
  }
}
  • @ache a raison, écoute-le. ;)
  • Pour var : ne l’utilise jamais. Utilise const partout, sauf où let est nécessaire.
  • Utilise l’égalité stricte sauf dans des cas très particuliers : === et !==, pas == ni !=.
+0 -0

En fait, on peut très bien utiliser var.

Je ne sais pas si c’est une bonne pratique ou non, mais voici ce que j’ai appris dans mes cours :

(function() {
    var myfunction = {
        init : function() {
            // Code JavaScript ici
        }
    }
    
    myfunction.init();
})();

Si je me souviens bien, ça permet d’isoler le code pour ne pas qu’il interfère avec les autres codes (et on peut donc utiliser var sans aucun problème).

+0 -0

Merci pour les conseils :)

@cepus, je reprends une question qui me trotte à l’esprit.
Dans ton code, à la ligne 18, tu déclares i avec let ; puis tu lui affectes la valeur 1 (dans le cas présent, tu as couplé ces deux étapes en une seule instruction).

Ces deux étapes se produisent 1 fois par seconde.

Pourquoi n’as-tu pas déclaré i au début du programme en global, pour ainsi rester avec i = 1 et te retrouver avec une seule étape par seconde ?

J’ai l’impression que ma version est plus optimisée, càd demandera moins d’efforts pour l’ordinateur (mais peut-être qu’alors je ne respecte plus les bonnes pratiques).

J’ai vraiment besoin de savoir si je me trompe ou pas car j’ai l’impression d’être le seul développeur au monde à procéder ainsi et peut-être que quelque chose cloche, mais j’ai alors vraiment envie de savoir pourquoi.

J’ai encore un tas de questions mais celle-ci me semblait la plus importante car elle peut potentiellement concerner tout ce que j’ai développé jusqu’à présent.

En fait, on peut très bien utiliser var. Je ne sais pas si c’est une bonne pratique ou non, mais voici ce que j’ai appris dans mes cours […]

FougereBle

Bien sûr qu’on peut, mais il ne faut pas. C’est pas parce qu’il faut pas qu’on ne peut pas.

Si je me souviens bien, ça permet d’isoler le code pour ne pas qu’il interfère avec les autres codes (et on peut donc utiliser var sans aucun problème).

C’est bien là le problème : le scoping de var est foireux comparé à let et const. Ces derniers sont là pour corriger le problème sans changer le comportement de var parce qu’en JS on veut une rétrocompatibilité parfaite, donc jamais changer le comportement de var.

Pour résumer :

  • var est obsolète
  • let et const remplacent var
  • Utilise soit var, soit let et const, de préférence ces derniers parce que var est obsolète.

Pourquoi n’as-tu pas déclaré i au début du programme en global, pour ainsi rester avec i = 1 et te retrouver avec une seule étape par seconde ?

J’ai vraiment besoin de savoir si je me trompe ou pas car j’ai l’impression d’être le seul développeur au monde à procéder ainsi et peut-être que quelque chose cloche, mais j’ai alors vraiment envie de savoir pourquoi.

En fait cette optimisation n’a pas d’intérêt ici. C’est une différence de nanosecondes, on s’en fiche. Comme c’est anecdotique au point que mesure la différence n’est même pas possible, autant s’en tenir à un code propre et respectant les bonnes pratiques. Ça a un tas d’avantages : c’est plus lisible, moins en proie à des bugs, etc.

+0 -0

Ouf ! C’est bien d’une nano-optimisation de ma part, je ne suis pas fou ! :-°

En fait, j’ai une vision différente des bonnes pratiques parce que j’ai toujours codé seul pour des mini-projets.

J’en viens à me demander si je dois me plier aux bonnes pratiques 'générales’, et si oui dans quel intérêt ? (là j’ai mis du temps à poster car je me pose la question… après réflexion ça dépend surtout du contexte dans lequel je programme, mais je continue à réfléchir…).

Les intérêts des bonnes pratiques en programmation sont diverses :

  • la performance, si cela est important ;
  • la correction et la portabilité du programme,
  • maintenabilité et lisibilité.

J’en oublie sûrement, mais même pour des petits projets tu y trouveras quelque chose d’important.

J’en viens à me demander si je dois me plier aux bonnes pratiques 'générales’, et si oui dans quel intérêt ?

Ludwig

Un risque, si tu ne t’y plies pas "parce que je fais mes propres petits projets dans mon coin", c’est que tu développes tes propres idiosyncrasies. C’est à dire que tu vas développer ton propre style personnel, tes propres patterns, etc qui différeront de ce à quoi les gens sont habitués. Les autres seront déroutés le jour où ils devront comprendre ton code, et tu seras de plus en plus dérouté ou déconnecté quand tu devras comprendre le code des autres. Tu seras sur une voie parallèle, isolé. En inventant ta propre façon de faire, tu réinventes la roue mais jamais ta roue ne sera aussi efficace que celle qui a évolué sur le long terme grâce aux connaissances approfondies des milliers de développeurs qui te précèdent.

Pour prendre un exemple stylistique, tu as posté ça :

function updateClocks()
{
    now = updateTime();

    for (i = 1; i <= 60; i++)
    {

Il y a une super bonne raison de ne PAS mettre de retour à la ligne avant une { en JavaScript1. Ça ne veut pas dire que ça ne marchera pas si tu mets ce retour à la ligne, ça veut juste dire qu’il vaut mieux ne pas le mettre, et ça, ça vaut mieux pour tout le monde. Tu as donc deux options :

  1. continuer comme tu fais maintenant jusqu’au jour où tu découvres pourquoi on ne fait pas ça, après avoir perdu une demi-journée à débugger, et à ce moment décider entre faire une exception là où le bug se produit ou changer tout le style de tous tes projets, c’est à dire changer ton style, ce qui est pénible quand on le fait sur le tard
  2. faire comme tout le monde et ne pas mettre ce retour à la ligne (idéalement après avoir compris pourquoi)

Idem pour les micro-optimisations. Ne les fait que quand c’est strictement nécessaire, et mets un commentaire expliquant pourquoi c’est nécessaire. Il y a bien d’autres choses à penser que les micro-optimisations quand on développe. Avec l’expérience, tu feras des choix pragmatiques.


  1. ASI: automatic semicolon insertion

+1 -0

Je suis encore en réflexion sur ton premier paragraphe.

Concernant l’ASI, j’ai essayé de comprendre où cela pourrait poser souci (en lisant cet article).

Après lecture de l’article, je pense que (à rectifier !) :

  1. Ma syntaxe actuelle est correcte mais l’ASI pourrait venir ajouter des ; là où ça ne devrait pas. Ces ; sont traités comme des empty statement
  2. Dans certaines syntaxes ouvertes sur plusieurs lignes, notamment pour la boucle for, l’ASI pourrait ajouter des ; et cela peut générer des erreurs car cela viendrait compromettre la syntaxe de la boucle for. Ces erreurs sont celles dont tu parlais pour la demi-journée de debug ^-^
  3. Les empty statement ne sont pas nuisibles au programme, mais doivent être évités (bonnes pratiques)

ça m’aiderait de préciser quels sont les points vrais & faux ci-dessus ^^

Idem pour les micro-optimisations. Ne les fait que quand c’est strictement nécessaire, et mets un commentaire expliquant pourquoi c’est nécessaire. Il y a bien d’autres choses à penser que les micro-optimisations quand on développe. Avec l’expérience, tu feras des choix pragmatiques.

Ce que je ne comprends pas, c’est pourquoi ces mini-optimisations ne font pas partie des bonnes pratiques usuelles.

Par exemple, en quoi ce code-ci :

// Remarque : nomsDesFonctions / noms_des_variables

'use strict';

let i, seq_u, clocks_time, real_time;

function refreshPage()
{
    for (i = 1; i <= 60; i++)
    {
        seq_u = 'u'.repeat(Math.floor(Math.random() * 9));

        document.getElementById('clock' + i).value = 'Miaou' + seq_u + ' ! ' + clocks_time;
    }
}

function updateClocks()
{
    real_time = (new Date()).toLocaleTimeString();

    if (real_time != clocks_time)
    {
        clocks_time = real_time;
        refreshPage();
    }

    requestAnimationFrame(updateClocks);
}

document.addEventListener('DOMContentLoaded', updateClocks);

…est-il moins bien que celui qui respecte les bonnes pratiques, sauf pour les micros-optimisations ?

Essaie de lancer le code suivant, et regarde ce que cela te retourne. Probablement pas ce à quoi tu penses.

(function()
{
  return
    {
      a: 1
    };
})().a

Et sinon, je crois que @cepus et les autres t’ont déjà donné une bonne réponse à la plupart des questions.

Plutôt que supposer que tu es le seul à faire les choses mieux que les autres et qu’ils devraient faire comme toi, c’est peut-être mieux d’essayer de comprendre pourquoi ils le font de la sorte.

  1. Ma syntaxe actuelle est correcte mais l’ASI pourrait venir ajouter des ; là où ça ne devrait pas. Ces ; sont traités comme des empty statement

Exactement. Pas besoin d’apprendre les règles de la spec, tiens-toi juste à ne pas faire :

function foo ()
{
  return
    {
      a: true
    }
}

et tu gagnes sur tous les tableaux.

  1. Dans certaines syntaxes ouvertes sur plusieurs lignes, notamment pour la boucle for, l’ASI pourrait ajouter des ; et cela peut générer des erreurs car cela viendrait compromettre la syntaxe de la boucle for. Ces erreurs sont celles dont tu parlais pour la demi-journée de debug ^-^

Par exemple. Ou lis le code ci-dessus et devine ce qu’il retourne. Vérifie ensuite si t’avais raison en faisant console.log(foo()).

  1. Les empty statement ne sont pas nuisibles au programme, mais doivent être évités (bonnes pratiques)

Disons ça comme ça, oui, ça a l’avantage d’être une règle simple.

Ce que je ne comprends pas, c’est pourquoi ces mini-optimisations ne font pas partie des bonnes pratiques usuelles.

Parce qu’elles nuisent à la lisibilité et maintenabilité de ton code, et parce que les variables globales sont à proscrire autant que faire se peut.

  • Une variable doit avoir le scope le plus petit possible
  • Déclare une variable le plus proche possible d’où tu l’utilises la première fois

[edit] Oh comment @dab m’a grillé mon joli exemple. C’est pas surprenant qu’on ait pensé au même exemple mais c’est marrant qu’on l’ait fait de façon aussi semblable, jusqu’au nom de variable. :lol:

+1 -0

La plupart des implémentations modernes de javascript passent par une étape de compilation JIT, et utiliser des variables globales peut compliquer l’analyse du programme par le compilateur. Déclarer des variable ayant un type dont la construction est triviale en tant que globales peut donc s’avérer contre-productif au niveau des performances.

Ceci dit, je ne suis pas un développeur javascript et je peux toujours me tromper.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte