Je suis développeur web freelance et propose des formations à Symfony2 ! Contactez-moi pour en discuter.

C’est bientôt Noël. Et si vous vous faisiez le cadeau d’apprendre quelque chose de radicalement nouveau, comme un nouveau langage de programmation ?

Lire la suite »

Lorsque l’on souhaite écrire du code concis, l’héritage permet de gagner du temps en factorisant le code dans une classe dont plusieurs vont hériter, et se partager les fonctionnalités.

Dans une application AngularJS, il est donc régulièrement nécessaire de faire hériter dans un contrôleur les fonctionnalités d’un contrôleur de base. Je ne parle pas ici d’imbrication, c’est-à-dire d’un « gros » contrôleurs dont une sous partie du DOM est gérée par un ou plusieurs autres contrôleurs, en charge de fonctionnalités restreintes localement. Je vais parler d’héritage au sens objet du terme, bien que cette notion soit un peu floue en JavaScript (j’entends déjà les puristes du fond qui hurlent, mais ça n’est pas l’objet de cet article).

L’héritage de contrôleurs est par exemple utile pour une application de type CRUD, où les divers contrôleurs vont vouloir réaliser des opérations similaires sur différents modèles, avec des particularités pour chacun d’entre elles. On va donc vouloir mettre l’essentiel des fonctionnalités partagées dans un contrôleur de base, et fournir le comportement spécifique dans les fils.

Voici comment je fais pour faire hériter des propriétés, des méthodes, le scope et ce qu’il surveille d’un contrôleur de base vers des contrôleurs fils. Tout part d’un contrôleur de base.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var BaseController = function($scope, DataStorage) {
    this.scope = $scope;
    this.DataStorage = DataStorage;
    this.scope.pageNb = 1;

    var me = this;
    this.scope.$watch ('pageNb', function() {
        me.getList();
        me.scope.isAllselected = false;
    });

    this.scope.displayCreationPopup = _.bind (this.displayCreationPopup, this);
    this.scope.hideCreationPopup    = _.bind (this.hideCreationPopup, this);

    this.scope.showCreationPopup    = false;
}

BaseController.prototype.displayCreationPopup = function (){
    this.scope.showCreationPopup = true;
}

BaseController.prototype.hideCreationPopup = function (){
    this.scope.showCreationPopup = false;
}

Plusieurs choses sont à noter :
– Je définis les propriété (objets et fonctions) à faire hériter dans le prototype. Le prototype définit toutes les propriété communes à toutes les instances de BaseController. C’est donc très bien pour nous dans le cas présent, mais il faut faire attention à ce que l’on met dedans, ce n’est pas toujours ce que l’on va vouloir.
– Bien qu’il s’agisse d’un contrôlleur, on ne l’enregistre pas via angular.module(…).controller. En effet, c’est notre classe abstraite, elle ne contient pas le code métier que l’on souhaite exécuter tel quel, et ne contient pas à proprement parler de code complet.
– D’ailleurs, dans $scope.$watch, on fait appel à la méthode getList() qui n’est pas définie ! Je la définis plus tard, dans les contrôlleurs fils. Mais afin d’éviter de la duplication de code, comme tous les controlleurs font appel à getList lors d’un changement de numéro de page, le code métier correspondant est écrit ici.
– Je fournis le $scope en paramètres du contrôlleur de base, et un service de stockage. Comme le code n’est pas exécuté tel quel, DataStorage n’a pas besoin d’être un vrai service (en fait, il n’a même pas besoin d’être défini). Tous ce dont on a besoin, c’est d’avoir une variable dans laquelle appeler des méthodes. En java ou autre, on aura un type plus précis sur cette variablee, afin d’éviter de pouvoir y mettre n’importe quoi. Dans les divers classes filles, nous injecterons un vrai service, dépendant du contrôlleur dont nous avons besoin afin d’exécuter la logique métier.

Et maintenant, le code métier d’un exemple de classe fille.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// On hérite le contrôleur de celui de base.
var NewsController = function($scope, NewsStorage, $injector) {
    $injector.invoke(BaseController, this, {$scope: $scope, DataStorage:NewsStorage});
   
    this.scope.getList    = _.bind (this.getList, this);
    this.getList();
}
NewsController.prototype = Object.create (BaseController.prototype);

// Une fonction quelconque de récupération de liste de news
// Exemple de ce qu'on pourrait avoir avec de l'asynchrone type requête Ajax :
NewsController.prototype.getList = function (){
    var me = this;
    this.scope.displayLoader = true;
    this.NewsStorage.getList (this.scope.pageNb, function(result){
            me.scope.news = result.data.news;
            me.scope.nbPages = result.data.nbPages;
            me.scope.displayLoader = false;
        }
    );
}

// On injecte le contrôleur dans l'application
var module = angular.module ('myApp.controllers', ['common.controllers', 'common.stores', 'myApp.stores']);
module.controller('NewsController', [
    '$scope',
    'NewsStorage',
    '$injector',
    NewsController
]);

Les 2 lignes importantes d’un point de vue de l’héritage sont
$injector.invoke(BaseController, this, {$scope: $scope, DataStorage:NewsStorage});
et
NewsController.prototype = Object.create (BaseController.prototype);

La première injecte les données présentes dans le $scope du père (en lui fournissant au passage les bons services). Elle fournit aussi tout ce qui s’y rattache : on conserve le $scope.$watch par l’héritage.
La seconde ligne étend le prototype, c’est à dire qu’elle duplique les méthodes dans le contrôlleur fils. Oui, c’est du javascript, pas du C++ ou du Java hein, l’héritage reste ici une notion un peu abstraite (haha).

Cette fois ci, le contrôleur News est bien injecté dans l’application. On le configure, et on lui donne les bons services, c’est eux qui sont fournis au père.
On implémente la méthode getList(), que l’on peut imaginer faire un appel ajax ou dans un cache quelconque (applicatif ou localstorage, soyons fous) pour récupérer les données nécessaires.

Vu que les méthodes sont héritées, depuis la vue, on peut donc appeler showCreationPopup() et hideCreationPopup(). On peut également utiliser les propriété du scope, et rajouter les nôtres.

Vous pouvez au passage remarquer que je bind régulièrement mes fonctions via la librairie underscore sur l’objet this. Cela permet de ne pas perdre l’objet contrôlleur lorsque les appels se font depuis angular, qui utilise comme objet d’appel le $scope local et non le contrôlleur. Dans cet exemple, tous les bindings ne sont pas nécessaires, mais quand je fais un hideCreationPopup() depuis une vue, c’est le binding dont je viens de parler qui fait qu’on peut changer la valeur de showCreationPopup, et que ce changement se répercute sur la vue.

Cela permet d’isoler le code récurrent dans une classe de base afin d’en faciliter sa réutilisation, ce qui permet, pour les contrôleurs fils, de n’implémenter que la logique spécifique.

Récemment, la question de savoir s’il faut ou non mettre en prod un projet en fin de semaine a fait apparaître quelques trucs humouristiques, tel que Estcequonmetenprodaujourdhui.info ou bien l’image de CommitStrip.

Mon opinion sur la question est plus proche de celle développée dans l’article d’Yves, même si je trouve sa vision un peu trop manichéenne.

Selon lui les clés d’un déploiement réussi c’est, entre autres :

  • Un système de déploiement
  • Des tests
  • La maîtrise du volume de changement

Toujours selon lui, lorsque l’on maîtrise ces 3 éléments, on peut déployer n’importe quand.

Je le rejoins tout à fait sur le système de déploiement. Sans avoir un process complexe d’intégration continue, il me semble irresponsable de travailler sans un outil de déploiement qui permette d’annuler une mise en prod en cas de problème, et de revenir à un état antérieur stable. Pas besoin d’un système complexe pourvu que ça marche et qu’il soit fiable : je travaille actuellement avec un outil qui doit faire bien moins 30 lignes de script shell, et qui fait très largement ce dont j’ai besoin. Je peux déployer en une commande dans un terminal, et en cas de problèmes, revenir en arrière en une commande. Plein de solutions plus complètes existent, mais cela pourrait être le sujet d’articles dédiés.

Pour le reste, je suis plus modéré : il arrive que du code soit non testé car difficile à tester de manière automatisé, ou qu’il y ait des effets de bords difficiles à reproduire… bref dans la vraie vie, un déploiement, ça ne se passe pas comme ça devrait. N’empêche qu’avec un peu de bon sens et de bons outils, on peut mettre en prod un vendredi, pourvu qu’on maîtrise ce qui est fait.

C’est justement là où je voudrais en venir. Le bon sens.

Voici la scène à laquelle j’ai participé, impuissant :
Il y a 5 développeurs.
Celui qui doit mettre en production est arrivé récemment dans la société et doit réaliser son premier déploiement. Il doit donc être assisté pour utiliser les outils (qu’il ne connait pas), et pour vérifier qu’il n’oublie rien.
Un des autres développeurs doit partir à 17h et faire du télétravail le lendemain. 2 autres donnent une formation à l’étranger (6h de décalage horaire, mais joignables via messagerie instantanée). Le dernier, sur les lieux, n’a que peu d’expérience sur le projet, et n’a pas la main sur de nombreux outils (base de prod, outils serveur, etc…).
La mise en production ayant déjà été retardée à cause de divers problèmes pratiques, le moment où la mise en production peut effectivement avoir est jeudi vers 16h30.
Outils de tests automatisés : aucuns (à mon grand désespoir).
Impact de la mise en production : l’intégralité du site, car liée aux URls (le but étant d’améliorer le SEO).

Fallait-il mettre en production ce jeudi-là ?

Tel que je le présente, il me semble évident que non.

En effet avec n’importe quel outil de gestion de version, comme celui utilisé ici, il est possible pour ce développeur de continuer à travailler sur d’autres choses en attendant de pouvoir mettre en ligne lorsque les conditions sont propices. Je n’aurais pas fait ce déploiement de type ‘big bang’ le lendemain, mais j’aurais attendu le lundi. Le développeur le plus expérimenté travaillant depuis chez lui et 2 des autres n’étant joignables qu’à partir de 15h, celà n’aurait pas été idéal, d’autant plus que l’essentiel de l’activité de la société a lieu au cours du weekend, et qu’il est inconcevable que le site soit KO durant cette période. Bref, dans l’idéal j’aurais attendu lundi. Si ce n’était pas possible (admettons), j’aurais au moins attendu le lendemain, plutôt que de presser un développeur déjà stressé par l’impact potentiellement néfaste de son travail, et par la journée déjà bien avancée.

La mise en production a donc bien eu lieu. La mise en préproduction ayant en effet déjà démarré, les autres développeurs étaient bloqués pour faire les leurs. Divers projets n’attendant que l’arrivée d’un développeur rendaient également pressant ce déploiement. Plutôt que de revenir en arrière et faire le déploiement tranquillement le lendemain, il a été demandé de finir ce qui avait été commencé, afin de laisser la place aux autres par la suite. Intérêt faible : le gain de ce déploiement se consaste après plusieurs semaines, et via la gestion de version, tout le monde aurait pu s’y retrouver convenablement.

Je suis donc convaincu que le choix qui a été réalisé était plus mauvais qui puisse être fait.

Il n’a pas fallu beaucoup de temps pour me donner raison : le déploiement a échoué à cause de nombreux effets de bord qui n’apparaissaient pas dans les autres environnements (ce qui aurait pu  être évité), le déploiement a finalement été repoussé, et une nouvelle branche de travail a du être mise en place pour travailler en parallèle. Quand au malheureux développeur, il est resté jusqu’à 23h pour essayer de déployer, puis revenir en arrière.

Bref, ne pas oublier d’avoir du bon sens. C’est ce que font de nombreuses boites, qui n’ont plus à prouver qu’elles savent avoir une application en ligne. Déployer ne doit pas avoir à ressembler à ceci.

Lors de la conférence JS.Everywhere à Paris, j’ai pu assister à une conférence très intéressante donnée par Rodney Rehm (@rodneyrehm) sur plein de mauvaises choses qui sont présentes un peu partout dans les API Javascript (et jusque dans JQuery). C’est intéressant, mais la portée du sujet est bien plus large: les nombreux conseils s’appliquent au reste du monde open source, et à l’écriture de librairies en général, peu importe le langage. Et c’est aussi valable ailleurs que pour le web.

Le problème de nombreuses API, c’est qu’elles sont diffusées librement, alors que leur code souffre de plusieurs défauts. Sans forcément regarder sous le capot, on se rend compte que de nombreuses choses ne vont dans les méthodes qui sont accessibles dans beaucoup de librairies libres. Il semblerait qu’à un moment, elles perdent de vue l’idée qu’elle peuvent être utilisées par beaucoup de monde, souvent avec des approche du code différentes. La conséquences, c’est qu’elles deviennent inutilement difficiles d’utilisation, les rendant ainsi pénibles et freinant leur adoption.

Pour pallier à celà, 3 idées sont à garder en tête lors du design d’une API : être simple, flexible, et robuste. Lire la suite »