All rights reserved © Pierstoval
Moon art from Lawlie.
(Support artists by paying them)
Background from Solar System Scope

mercredi 21 décembre 2016

[Learning Sails] #2 - Les bonnes pratiques

Ceci est la suite du premier article ICI

On va parler de ce que j'ai vu après avoir tablé des semaines sur l'asynchronicité globale de Javascript et de la difficulté de tout faire de façon asynchrone dans une situation de communication HTTP.

Let's go.


Dans un premier temps, je dois préciser que j'ai eu énormément de soucis avec l'ORM, l'accès à la base de données ne fonctionnait pas et j'ai du passer une ou deux semaines sur la gestion d'utilisateurs (inscription/connexion).

D'abord voulant utiliser Passport.js, j'ai abandonné l'idée au profit d'une solution 100% manuelle. Passport est beaucoup trop lourd pour moi, pour l'instant.

Cette méthode ne fonctionnant toujours pas, même après des dizaines de copier/coller, j'ai fait des tonnes de recherche, j'ai même posté un article sur Human Coders pour parler des frameworks NodeJS, parce que même si j'apprends vite, je suis incapable d'apprendre sans cadre ni règles.

Et je me suis aperçu qu'à un moment, j'avais oublié d'exécuter une fonction de callback.

*doh*

Et donc, après m'être tapé la tête contre le mur plusieurs fois pour avoir fait une bêtise aussi débile que de rajouter return cb(); à un endroit de mon code, j'ai continué mon apprentissage.

Asynchrone = callbacks


Maintenant que c'est bien compris, je ne devrais plus avoir ce problème.

Mais dans un framework javascript, ce qui tue, c'est l'asynchronicité des actions.

Autrefois, on parlait comme ça à notre application :
Récupère une requête HTTP
├── Écrit un log dans un fichier disant "Quelqu'un a voulu créer un utilisateur"
├── Crée un utilisateur
├── Sauvegarde-le en base de données
└── Renvoie au navigateur une page web disant "Utilisateur créé !"


La numérotation est importante : on effectue les actions les unes après les autres. Autrement dit, de façon procédurale (voir des détails sur la programmation procédurale, ça vaut le détour).

Maintenant, on fait les choses comme ça :

Récupère une requête HTTP
├── Crée un utilisateur
│   └── (Une fois celui-ci créé) Sauvegarde-le dans la base de données
│       └── (Une fois sauvegardé) Renvoie au navigateur une page web disant "Utilisateur créé !"
└── Écrit un log dans un fichier disant "Quelqu'un a voulu créer un utilisateur"

Les actions sont effectuées de façon asynchrone (en parallèle les unes des autres) mais surtout, le paradigme est différent.
On n'est plus dans de la programmation procédurale, mais dans de la programmation événementielle (idem que précédemment, lisez l'article associé sur la programmation événementielle sur Wikipédia, ça aide à comprendre).

Dans notre premier exemple, si l'une des étapes échoue et renvoie une erreur, dans ce cas aucune des autres étapes ne peut être effectuée.

Dans le deuxième exemple, si l'une des étapes échoue, toutes les étapes "qui en dépendent" ne seront pas exécutées, mais les étapes qui les suivent le pourront, elles.
Dans cet exemple, l'action d'écrire d'un log dans le fichier se fera dans tous les cas, puisque la création d'utilisateur est exécutée "en parallèle".

Les deux méthodes ont chacune des avantages et des inconvénients.

Dans la programmation procédurale, l'avantage énorme est de pouvoir couper toute action dès qu'il y a une erreur, d'arrêter le script à tout moment. C'est très pratique pour justement empêcher qu'une erreur en entraîne des dizaines d'autres.
L'inconvénient c'est qu'il est, du coup, impossible d'effectuer des tâches en parallèle, ce qui réduit donc notre application à un thread (tâche) unique, nous forçant donc à découper nos applications de plusieurs façons différentes en fonction de nos besoins.

Du coup, les pratiques changent


À force de faire du PHP depuis maintenant plus de 8 ans (ça passe vite...), changer de paradigme, ça change beaucoup de choses.

Comme je l'ai déjà dit, PHP c'est du procédural. Javascript, c'est de l'événementiel. Rien que là, y'a un changement.

Mais c'est difficile à trouver. PHP est un langage flexible, mais moins que Javascript (ah, les joies des langages stricts comme C ou Java).

Du coup, pour PHP nous avons un groupe de développeurs, une sorte de consortium, qui décide d'une liste de bonnes pratiques à respecter via des recommandations.

Ce groupe s'appelle PHP-FIG (PHP Framework Interop Group) et a défini des PSR (PHP Standard Recommendations) pour apprendre à faire du PHP de la bonne façon.

En plus de ça, Composer, le meilleur gestionnaire de dépendances au monde (ok, je vends un peu le truc) impose lui aussi des règles de bonne conduite 
  • Semantic Versioning
  • Autoload
  • Respect des dépendances (en gros, dépendre du strict minimum, et d'une version minimum la plus basse possible pour ce dont on a besoin)
  • Il recommande (sans l'imposer) un respect de la rétrocompatibilité, connu aussi sous le nom de "respect des backward compatibility breaks" (dont le but est de faire en sorte qu'en utilisant semver (voir plus haut), on impose qu'une fonctionnalité introduite en version 2.3 (par exemple) soit toujours présente en version 2.8, et que si elle est dépréciée, on la supprime uniquement en 3.0, et pas en 2.9, au risque de briser la rétrocompatibilité. En quelques mots, hein.).
  • Et plein d'autres choses un peu plus implicites et subtiles que l'on retrouve dans les pull-requests un peu partout sur Github

En Javascript, on n'a pas ça.
Enfin presque.

Sauf que ce n'est vraiment pas assez valorisé.

Mais ça existe "à peu près".

En lisant ce fil Reddit qui parle justement de ce lien entre PHP-FIG et NodeJS, j'ai découvert qu'il existe finalement déjà des recommandations en Javascript, notamment par JS the right way qui non seulement a pas mal de liens et de documentation sur plein de sujets, mais en plus explique bien comment implémenter des design patterns précis avec du Javascript, ce qui est vraiment cool.

Donc oui, les bonnes pratiques existent.

The devil: copy/paste


Le mal absolu c'est de copier/coller sans comprendre.

Et du coup, c'est pour ça que j'ai abandonné Passport.js (voir plus haut) : parce qu'il fallait juste faire du copier/coller.

Pour réussir un gestionnaire d'utilisateur avec juste inscription + connexion, j'ai regardé tout le code de Sails qu'il fallait utiliser. J'ai passé des dizaines d'heures (sans mentir) à lire le code source, regarder plein de documentation, à tester, faire du débogage à tout va avec plein de choses, et à surtout ne pas faire de copier/coller hâtif.

D'ailleurs, à titre d'anecdote, dans ma vie je n'ai fait qu'un seul copier/coller sans comprendre 100% du code : c'est lorsque j'ai utilisé l'algorithme de Dijkstra pour gérer le calcul d'itinéraires pour le jeu de rôle "Les Ombres d'Esteren". J'ai du mettre à jour l'algorithme pour m'adapter à mon code et aux données supplémentaires (mode de transport, durée de trajet, etc.) mais aujourd'hui j'ai encore du mal avec les subtilités de l'implémentation elle-même, que j'ai trouvée en copiant depuis ce ticket sur CodeReview, de Stack Exchange.


Conclusion


Quand j'utilise Symfony et que je découvre une nouvelle classe, une nouvelle fonctionnalité, je ne peux pas m'en servir sans regarder comment les développeurs du framework ont créé leur classe. Quelle est la documentation, le fonctionnement interne, les idées et théories/théorèmes derrière.

Et je pense que c'est le meilleur moyen de devenir un expert dans une technologie lambda :

Il faut connaître les bonnes pratiques, et connaître le cœur du fonctionnement d'un système qu'on utilise.