SOLID – L: Liskov substitution

« Un carré est un rectangle ». Voilà ce que les gentils professeurs de géométrie nous ont dit. Mais, peut-on en dire de même en orienté objet ?

Carre extend Rectangle ?

En orienté objet, un rectangle est constitué de 2 attributs : « largeur » et « hauteur » avec leurs « setter » et « getter ». Rien d’incroyable:

Un carré, par contre, n’a qu’un attribut : « coté », avec un setter et un getter. Mais imaginons qu’il étende la classe Rectangle, comme dans « la vraie vie ». Comment l’objet Carre va-t-il gérer les méthodes setter et getter de rectangle ? Les méthodes « set/getHauteur » et « set/getLargeur » ne sont pas relevantes ou pertinentes pour un carré ! Essayons de contourner le problème:

Tout cela « fonctionne », mais avouez qu’on est plus dans de la bidouille pour faire rentrer un carré dans un rectangle. Ceci est une violation du principe de substitution de Liskov. Notre amie Barbara Liskov (oui c’est une dame) a un jour dit :

Si « S » est un sous-type de « T », alors tout objet de type « T » peut être remplacé par un objet de type « S » sans altérer les propriétés désirables du programme concerné.

Pour en revenir à nos carrés et rectangles, cela veut dire que partout où, dans votre code, vous vous attendez à recevoir un rectangle en paramètre, vous pouvez le remplacer par un carré, sans que cela pose un souci.

Ce n’est malheureusement pas le cas, car le développeur qui travaille sur un rectangle veut pouvoir changer la largeur et la hauteur indépendamment l’un de l’autre. C’est impossible avec un carré, ce qui « altère les propriétés d’un programme ».

Exemple concret

Imaginons que vous fassiez un jeu de voitures. Vous avez une classe « Voiture » avec quelques méthodes :

Votre jeu fait un carton et suite à la pression médiatique, votre patron cède, et vous demande d’ajouter des voitures volantes. « VoitureVolante extend Voiture », ça vous parait pas mal:

Sauf que vous avez la méthode « changePneu » qui vous ennuie un peu, car une voiture volante n’a pas de pneus. Que va faire votre méthode dans ce cas-là? Que pouvez-vous faire ? Au choix :

  • Réécrire la méthode et ne rien faire (méthode vide)
  • Réécrire la méthode et lancer une exception « PasDePneuException ».
  • Réécrire la méthode et nettoyer les réacteurs ou changer le train d’atterrissage. Mais est-ce le bon nom de méthode ?

Aucune des trois réponses n’est viable. En effet, cette méthode n’a pas lieu d’exister dans le cas d’un voiture qui n’a pas de pneu. Conclusion, une voiture volante n’est pas une voiture.

Ça sent le problème…

Voici donc quelques pistes qui vous feront sentir que vous êtes sur la mauvaise voie :

  • Vous avez une méthode qui n’a pas lieu d’être dans votre classe, mais vous devez l’implémenter.
  • Vous avez du réécrire une méthode de la classe parente car celle-ci ne doit « surtout pas » être exécutée ou ne rien faire.
  • Vous avez réécrit une méthode de votre parent et vous lancez une exception, alors que celle-ci n’était pas prévue dans la classe de base. Du coup, vous changez tous les appels en les englobant par un « try/catch »
  • Vous utilisez des « instanceof » pour exécuter (ou non) une méthode spécifique (aussi mauvais que le try/catch)
  • Vous devez modifier une classe parente pour que votre nouvelle classe fonctionne. Par exemple, ajouter des arguments optionnels.

C’est liste est non-exhaustive, mais assez complète. Evidemment, il faut rester vigilant : Certaines de ces affirmations ne sont pas toujours une preuve qu’il y a un problème. Par exemple, les méthodes vides sont monnaies courantes lorsque nous utilisons le pattern « Template method » ou « Null object ». Nous parlerons de ces patterns (et des autres) dans un autre article.

Comment faire rouler ma voiture volante ? Utiliser le pattern « Adapteur »

Bon alors évidemment, c’est difficile de dire à votre patron : « Boss ! Vous savez, dans notre jeu, la voiture bonus qui vole… Hum… Je ne peux pas l’implémenter car elle ne répondra pas au principe de substitution de Liskov ». S’il vous répond qu’il comprend et qu’il ne faut pas l’implémenter, c’est que vous avez vraiment un patron très cool!

Pour ce genre de problème, une des solutions est d’utiliser un pattern : L’adaptateur. Je vais vous en parler très brièvement, car je compte aussi en écrire un article. En gros, c’est faire rentrer un carré dans un rond (ou un rectangle!), ou bien transformer le port « Lightning » de votre IPhone en USB. Voici un petit bout de code qui devrait vous faire comprendre le principe.

D’abord nous avons la nouvelle interface IVoiture et la classe VoitureVolante qui n’ont pas ou peu de point commun:

Ensuite, on va rendre la voiture volante compatible avec la voiture de base grâce à l’adaptateur:

Et à l’exécution, c’est assez simple:

Certes, nous avons créé 2 classes « au lieu » d’une, mais la classe VoitureVolante ne se retrouve pas avec des méthodes poubelles, et ne fait que ce qu’elle doit faire.

Conclusion

Alors, qu’est-ce qu’il en ressort ? Sachez juste que l’orienté objet, ce n’est pas toujours « comme dans la vraie vie ». N’étendez pas une classe qui contient trop de méthodes pour votre nouvelle classe. Le but est de se dire : Si je n’étendais pas cette classe (ou l’interface), est-ce que j’aurais de toute façon écrit cette méthode ? S’appellerait-elle de la même manière ? Aurait-elle le même comportement globale? Est-ce que ma sortie (return) et mes erreurs (throw new Exception) sont identiques à ma classe parente?

Si vous avez répondu « oui » à toutes les questions, alors, félicitations, vous avez répondu au principe de substitution de Liskov.

SOLID – O: Open/Closed

Le deuxième principe de SOLID nous apprend qu’il faut être ouvert à l’extension mais fermé à la modification.

Mais qu’est-ce que cela veut bien pouvoir dire ? Comment faire ? Est-ce que c’est possible ? Fermé aux modifications ? Cela veut dire que je ne vais plus modifier mon code ? Comme disait mon bon vieux binôme lors de mes études : Oui et non.

Oui… et non, mais pensez YAGNI et KISS!

En « open/closed », le code n’est modifié que lorsque votre client vient avec un nouveau type de demande. Qu’est-ce qu’un type de demande ? Prenons par exemple une méthode qui reçoit un « Xml » en paramètre. Votre client demande qu’il puisse donner un « Yaml » ou un « Csv ». Si vous commencez à écrire un « switch » sur le type de l’argument, qu’il faut alimenter à chaque nouvelle demande, alors vous violez ce principe. Si, par contre, vous n’avez rien à changer, c’est gagné !

Si votre client vient avec une nouvelle demande dont il en a le secret, du style : « Je veux que mon xml soit filtré, nettoyé et colorié en rouge », alors là, dans tous les cas, vous allez devoir modifier le code, faire des nouvelles méthodes, les appeler, etc…

L’essentiel est de retenir ceci : Le principe « open/closed » se limite aux faits que vous auriez déjà pu rencontrer lors de vos développements. Et surtout, surtout, surtout (surtout !), n’essayez pas d’anticiper la demande du client. Pourquoi ? Tout d’abord, parce que cela alourdit votre code et votre architecture pour « rien », vu que pour le moment, on ne l’utilise pas. Ce qui vous fera peut-être perdre du temps lors de la modification du code. Ensuite, parce qu’il faut maintenir les tests unitaires pour ces cas-là (qu’on n’utilise pas). Et enfin, parce que le client a une sorte de sixième sens, et vous demandera toujours quelque chose que vous n’aviez pas prévu.

KISS (Keep it stupid simple) et YAGNI (You aint gonna need it) sont les maitres-mots même pour le principe « open/closed ».

Ce qu’il ne faut pas perdre de vue, c’est que si le client demande une modification, c’est souvent le moment de refactoriser le code pour « préparer » les futures demandes du même type. La bonne blague ! Je vous dis de « préparer l’avenir » alors que 3 phrases plus haut, j’abordais YAGNI et KISS ? Laissez-moi une chance de m’expliquer ! La parole est à l’avocat de la couronne, c’est à vous, maitre Sneyters.

Comment ne pas procéder ?

Voici un bref résumé d’un mois de développement sur une application révolutionnaire : Le File Importer 2000. Le nom est un peu (beaucoup) has-been, mais que voulez-vous…

  • jour 0 : Votre client vous demande de prendre un csv sur un ftp distant pour l’importer dans votre application. Facile ! Votre classe se connectera sur un ftp, va récupérer le fichier distant afin de le mettre dans un dossier local. En code très simplifié, cela donne:

  • jour 14 : Vous avez fini et votre client vous dit qu’il se peut que le fichier ne soit plus sur un ftp, mais sur un compte dropbox. En bon exécutant, vous allez rajouter la notion de « dropbox » à côté du ftp.

Pour éviter une trop forte régression, j’indique que mon deuxième paramètre est optionnel. Cela m’évite de changer les appelants.

  • Jour 30 : Vos 2 types de récupération sont prêts et votre client vous dit qu’il est aussi possible qu’il le mette lui-même dans le dossier « /upload » de votre application. Pas bien grave, votre booléen se transforme en « int » sur lequel vous allez faire un switch pour savoir si on utilise le ftp, le compte dropbox ou le dossier « /upload ». Là, par contre, vous allez devoir modifier les appelants, c’est-à-dire ceux qui utilisent l’ancien système de booléen.

 

Et voilà, après un mois tout va comme sur des roulettes ! Par contre, alors que la demande est toujours du même type, à savoir « Ajouter un nouveau type d’input », vous devez modifier votre méthode, et parfois le code qui l’appelle. En tant que développeur, vous savez que modifier du code, c’est s’exposer à des problèmes de différents types:

  • problèmes de régression,
  • problèmes de maintenance de tests unitaires,
  • problèmes de « merge » de branches,
  • etc.

Comment procéder ?

Reprenons l’énoncé en mode « open/closed ».

  • Jour 0: Création de la classe. Rien ne change par rapport à la version « not open/closed » étant donné qu’on est aussi en mode KISS, on ne va rien prévoir du tout. Le code reste donc exactement:

 

  • Jour 14: Ajout de « DropBox ». C’est là qu’il faut se poser la bonne question : On nous demande que l’input puisse être, soit un ftp soit un compte « dropbox ». Faisons preuve d’un peu d’abstraction (mot-clé), et utilisons l’injection de dépendance (encore un mot clé). Ainsi, nous allons créer une interface « InputStream » qui sera implémentée par les classes « Ftp » et « DropBox ».

Notre méthode se voit doté d’un nouvel argument qui est un objet de type « InputStream ». Du coup, le code sera le même pour la classe « Ftp » et « DropBox » et ne sera plus modifié (fermé à la modification) tant que la demande ne change pas. De plus, elle fonctionnera avec d’autres types d’input qui implémente l’interface « InputStream » (elle est ouverte à l’extension).

Bien sûr, cette modification de code, en engendre une autre: Les appelants. Souvenez-vous, on faisait la même chose en mode « not open/closed », mais 15 jours plus tard (le jour 30). Il y a donc des chances pour qu’il y ait moins de code impacté.

  • Jour 30: Ajout du dossier local : On crée juste une nouvelle classe « LocalFolder » qui implémente l’interface « InputStream » et qui représente un dossier local (« /upload » dans notre cas). Le code de ma méthode n’a pas été modifié, mes tests unitaires non plus. J’ai donc peu de chance d’avoir des problèmes de régression.

Et voilà, notre classe répond bien au principe « open/closed » ! Cependant, comme je vous l’ai dit en début d’article, c’est « closed » uniquement sur ce qu’on connaît, car il est impossible d’anticiper les demandes d’un client… Il sera toujours plus malin que vous.

Dernier truc pour la route

Un petit truc, qui ne marche pas à tous les coups, mais qui peut vous aider : Lorsque le client vous demande d’ajouter une fonctionnalité, mais que vous devez modifier du code, c’est que votre code n’est peut-être pas si « open/closed ». Pour notre File Importer 2000, le client nous demande d’ajouter différents types d’input possibles, il faut donc ajouter de nouvelles classes, il ne faut pas modifier la méthode qui gère l’import. « Add new feature » n’est pas « modify existing logic ».

Ça vaut ce que ça vaut, mais ça ne coûte rien de se poser la question…

SOLID – S: Single Responsibility

Nous allons commencer une série d’articles reprenant les gros principes de base en orienté objet. Ce sont 5 points à ne pas rater! Si vous débutez, cela va clairement vous aider à bien structurer votre code. Si vous êtes déjà dans le bain, cela peut vous servir de piqûre de rappel.

Nous aurons donc 5 articles pour chaque lettre de l’acronyme SOLID.

  • S : Single responsibility
  • O: Open/Closed
  • L: Liskov substitution
  • I: Interface segregation
  • D: Dependency inversion

Commençons aujourd’hui par le premier principe: La responsabilité unique.

Single Responsibility

Le premier principe SOLID est à la fois simple et compliqué. Simple à comprendre, mais difficile à bien appliquer.

« S » comme « Single Responsibility » consiste à dire qu’une classe n’a qu’une, et une seule, raison de changer. On peut aussi dire qu’une classe ne remplit qu’un rôle ou qu’elle n’a qu’une seule responsabilité. Qu’est-ce ce qu’un rôle? Il en existe probablement des milliers, mais en voici une petite liste pour vous faire une idée :

  • Affichage
  • Logique de vérification de donnée(s)
  • Envoi d’un e-mail
  • Communication avec la base de données
  • Système de cache
  • Représentation d’une entité
  • Routage en fonction de l’URL
  • Logique de paiement
  • Etc.

Chacun son rôle, chacun son chemin

Si on a une classe « Utilisateur » qui vérifie qu’un e-mail est valide, cela veut dire qu’elle a 2 rôles : Représentation et vérification. Cette classe a donc 2 raisons d’être modifiée:

  • Ajout ou modification d’un attribut de l’entité (ex : Ajout du nom de famille)
  • Modification de la vérification d’e-mail, en passant d’une expression régulière à une méthode un peu plus avancée.

Donc, si on utilise une entité, on embarque la vérification de l’e-mail: pas si grave. Par contre, si on veut vérifier un e-mail, on doit instancier un « Utilisateur ». Les 2 rôles sont couplés, nous avons donc une cohésion forte.

On le sait : plus les responsabilités sont couplées, plus les méthodes poussent comme des champignons, plus elles ont de chances d’être modifiées, plus elles sont susceptibles d’avoir des bugs, plus elles sont fragiles, plus elles sont rigides… Bref, tout ça n’annonce rien de bon.

Des tests unitaires pour les méthodes privées ?

Vouloir faire des tests unitaires de méthodes privées n’est pas un bon présage non plus. Si vous voulez les tester grâce à la « Reflection » ou que vous voulez les « mocker » (pas de chance c’est impossible), c’est que souvent, elles cachent une logique qui devrait probablement être externalisée dans une autre classe. Voici l’exemple d’une classe qui normalise une adresse et qui y ajoute des coordonnées grâce à l’api de « Google Maps ».

 

Voilà un bout de code qui fonctionne bien et qui n’expose que ce qui doit l’être. Cependant, on peut y constater que l’objet « AdresseNormalizer » va récupérer les informations via un webservice et les convertit en un objet « JolieAdresse » : Deux responsabilités. C’est encore plus embêtant si ma classe « JolieAdresse » est une classe spécifique à mon projet, je ne pourrai jamais réutiliser ma super classe « AdresseNormalizer ». Tristesse!

Cela se corse lorsque l’on veut tester unitairement la conversion du retour de « Google Maps » en objet. Se baser sur une ressource externe pour vos tests unitaires (que ce soit celui de Google ou un autre prestataire) peut renvoyer des résultats aléatoires. Il faut donc « mocker » l’appel, mais c’est impossible ici car tout est dans une méthode privée. La seule solution est de diviser la classe en deux :

  • Une classe « GoogleMap » qui va chercher les informations d’une adresse donnée, pour les renvoyer au format « json ».
  • Pourquoi pas une méthode statique « createFromGoogleMap » dans la classe « JolieAdresse » qui reçoit un objet de type « GoogleMap »

Notez que, grâce à ce refactoring, la classe « GoogleMap » peut être réutilisée dans un autre projet, sans aucun problème. Le maître mot, selon moi, pour le principe de « Single Responsibility » est la réutilisabilité. Chaque classe ou ensemble de classes, pourrait être vu comme un plugin.

N’hésitez pas à refactorer votre code avant qu’il ne soit trop tard. En effet, une classe qui a 2 responsabilités est parfois synonyme de « porte ouverte » : On se dit « si elle peut gérer l’html, on peut y ajouter la gestion du cache » et « si elle gère le cache, on peut y ajouter l’enregistrement dans Memcached ou en base de données? », etc. Les responsabilités s’accumulent et vous voilà coincé avec une classe qui fait le café et qui a peut-être un constructeur avec beaucoup trop d’arguments.

Choisir Bonux

Je terminerai par un diction de Maité qui est aussi valable pour tous les principes que nous allons voir: c’est une question de bon sens. Il est parfois (mais il faut pouvoir le justifier) préférable de vivre avec une classe qui a deux responsabilités, plutôt que deux classes qui n’en n’ont qu’une. L’exemple de la classe « Modem » est assez connu:

On voit clairement que cette classe a deux responsabilités : La gestion de la connexion d’une part et la gestion des messages de l’autre. Cependant, faut-il les séparer ? Vous n’allez pas aimer la réponse, mais cela dépend du contexte. Je vous propose de voir ce qu’en dit Uncle Bob, dans son livre dont voici l’extrait (page 112).

C’est un peu un cas à part, surtout qu’il parle de compilation qui ne nous intéresse que très peu en PHP. Cependant, il est intéressant de voir que même la compilation peut influencer la stratégie à avoir pour coder vos classes… Vous auriez fait quoi vous? Une ou deux classes?

Les perles de code: volume 3

Encore un épisode des perles (authentique) de code. Au programme aujourd’hui, on monte crescendo:

  • De la programmation orienté null
  • Une fonction qui révolutionne les tableaux
  • Un nouveau moyen révolutionnaire de faire des boucles

 

1. Null n’est une option

On commence par une première perle assez soft. J’ai rajouté des commentaires pour les plus distraits:

Question de review: Pourquoi faire le test dans la méthode « getSuperGroup », vu qu’au pire, on renvoi « null » (qui est la valeur de $this->superGroup).

 

2. Le raccourci qui change la vie

Je pense qu’ici, il n’y a pas grand chose à dire, si ce n’est que c’est somptueusement somptueux:

On retiendra tout de même que la doc/annotation a le mérite d’exister 🙂

3. La boucle version 2.0

Parce que faire 2 boucles, c’est mieux qu’une…

Si ça, c’est pas beau? Faire un tableau qui va juste servir à faire une boucle, c’est une idée originale.

 

Oui, les reviewers ont encore un bel avenir devant eux 🙂

Déploiement avec Capistrano et Github (sous Windows)

Le déploiement d’un site internet est toujours une opération un peu stressante lorsque l’on a beaucoup de taches à effectuer. Ça l’est encore plus lorsque celles-ci nécessitent des opérations manuelles comme l’upload de presque tous (surtout pas le dossier « upload »!) les fichiers via ftp, modification d’un table Sql, ajout d’un cron, etc… Bref si toutes ces opérations ne sont pas automatisées, cela peut prendre du temps et, comme tout bon humain que vous êtes, vous vous exposez à commettre des erreurs.

Pensez-vous que Facebook procède de la sorte? Je n’en suis pas sûr. D’autant plus qu’ils vont jusqu’à plusieurs déploiements par jour et qu’ils ont des centaines (voire des milliers) de serveurs. Je plains le petit monsieur de Facebook qui ouvre son « FileZilla » à chaque mise-à-jour. 🙂

Aujourd’hui, le déploiement automatisé existe et est à la porté de tous grâce à des outils comme « Capistrano ». Alors pourquoi ne pas vous y mettre?

Voici les quelques étapes pour déployer du code hébergé sur « Github ». J’ai pris Windows comme plateforme de déploiement, mais j’aurais très bien pu prendre « MacOs » ou « Linux ». La seule différence se situe dans l’installation de git et ruby.

 

Installer git

Git sera notre outils de versioning. Pour exécuter ses commandes, nous avons besoin de l’installer. Voici l’adresse pour l’installateur Windows: http://git-scm.com/download/win.

Après un « next, next, next », je vous propose de lancer « Git Bash » (gardez le ouvert, nous en aurons encore besoin) qui doit se trouver dans « C:/Program Files (x86)/Git » si vous n’avez pas créer de raccourci. Nous allons voir si vous pouvez accéder à votre repository sur github. Mettons nous d’abord dans un nouveau dossier qui va nous servir de test.

Ceci va créer un répertoire « mechant » dans votre dossier personnel (C:/Users/[votre utilisateur]), et vous y emmener.

 

Test de de la communication avec github

Récupérez ensuite l’adresse de votre repository Git. Pour ma part, j’ai pris « https://github.com/Nikoms/deployme.git » qui est un simple exemple créé pour l’occasion.

Il est possible que l’on vous interpelle:

The authenticity of host ‘github.com (192.30.252.130)’ can’t be established.
RSA key fingerprint is xx:xx:xx:xx…….
Are you sure you want to continue connecting (yes/no)?

Confirmez en tapant « yes ». Voici ce que vous devriez voir:

$ git ls-remote https://github.com/Nikoms/deployme.git
952c31e17af96d440057be81e2d09c8ed63c1fa3        HEAD
952c31e17af96d440057be81e2d09c8ed63c1fa3        refs/heads/master

Si vous ne voulez pas utiliser github en mode https, vous devrez générer des clés ssh. Je vous propose de suivre les recommandations ici : https://help.github.com/articles/generating-ssh-keys

 

Installer Ruby

Etant donné que Capistrano fonctionne sous Ruby, vous devez d’abord l’installer. Je vous rassure, vous n’allez pas coder une ligne de code 🙂

Comme nous lancerons notre déploiement sous windows, l’installation se fera via un simple « .exe » que vous trouverez ici: http://rubyinstaller.org/downloads/. N’oubliez pas d’indiquer qu’il faut ajouter « Ruby » dans votre variable « PATH » lors de l’installation.

Pour être certain que tout soit bien installé, dans « Git Bash », tapez « gem -v » (prononcez jèm). Si windows reconnait la commande, vous verrez la version installée.

 

Installation de Capistrano

Installons « la bête », via la console « Git Bash ». Voici la commande:

Voici le résultat:

Fetching: highline-1.6.19.gem (100%)
Successfully installed highline-1.6.19
Fetching: net-ssh-2.7.0.gem (100%)
Successfully installed net-ssh-2.7.0
Fetching: net-sftp-2.1.2.gem (100%)
Successfully installed net-sftp-2.1.2
Fetching: net-scp-1.1.2.gem (100%)
Successfully installed net-scp-1.1.2
Fetching: net-ssh-gateway-1.2.0.gem (100%)
Successfully installed net-ssh-gateway-1.2.0
Fetching: capistrano-2.15.5.gem (100%)
Successfully installed capistrano-2.15.5
Parsing documentation for highline-1.6.19
Installing ri documentation for highline-1.6.19
Parsing documentation for net-ssh-2.7.0
Installing ri documentation for net-ssh-2.7.0
Parsing documentation for net-sftp-2.1.2
Installing ri documentation for net-sftp-2.1.2
Parsing documentation for net-scp-1.1.2
Installing ri documentation for net-scp-1.1.2
Parsing documentation for net-ssh-gateway-1.2.0
Installing ri documentation for net-ssh-gateway-1.2.0
Parsing documentation for capistrano-2.15.5
Installing ri documentation for capistrano-2.15.5
6 gems installed

Voilà, c’était dur je sais, mais ça, c’est fait!

Création du projet

Vous êtes donc, à ce stade, dans votre dossier « mechant » dans « Git Bash ». Nous allons, à présent, créer un fichier qui va vous permettre de configurer les paramètres nécessaires au déploiement. Facile, une ligne de commande:

N’oubliez pas le « . » 🙂 Voici ce que vous devriez voir:

[add] writing ‘./Capfile’
[add] making directory ‘./config’
[add] writing ‘./config/deploy.rb’
[done] capified!

En d’autres termes, cette commande va vous créer un fichier de config (config/deploy.rb) et un scénario de déploiement (Capfile). Pour cet article, nous nous intéresserons uniquement au fichier de configuration.

 

Configuration

Ouvrez le fichier config/deploy.rb avec votre éditeur de texte préféré (notepad par exemple). Effacez tout le contenu, nous allons procéder par étape.

Tout d’abord, ajoutons les informations pour la connexion au serveur distant:

Je pense que tout ca est très clair 🙂

Passons à la configuration de git:

Capistrano ne fonctionne pas qu’avec git. Vous pouvez aussi déployer avec SVN, mercurial, etc… ou même sans repository!

La variable « deploy_via » est interessante, car elle vous permet  de déployer votre application de plusieurs manières différentes:

  • :copy : Fait un checkout en local (donc sous Windows), et envoi les fichiers (tar et gzipé) sur votre serveur distant. Pratique si votre serveur n’a pas Git installé et que vous n’avez pas la main dessus 🙂
  • :checkout (pas défaut si vous n’indiquez rien): Fait un clone sur le serveur distant. Il faut donc git installer sur celui-ci.
  • :remote_cache : Fait un « fetch » plutot qu’un « clone » sur le serveur distant. C’est options est donc plus rapide que « :checkout »

Passons aux rôles du/des serveurs:

Je n’ai qu’un seul serveur, les 3 adresses sont donc les mêmes. Vous pouvez avoir autant de serveur que vous le souhaitez

  • web: Adresse du serveur web. Pour les sites à fort trafic, vous avez sans doute plusieurs serveurs derrière un load balancer
  • app: Adresse du/des serveur(s) d’application
  • db: Adresse du serveur DB. Comme je n’en ai qu’un c’est le master.

Ces 3 variables sont obligatoires. Dans mon cas je n’ai pas de serveur DB, mais j’indique quand même l’ip du serveur. Je n’ai pas de migration de DB, donc de toute manière, aucune action ne sera exécutée.

Le plus gros est fait. Mais il reste encore 2 variables que j’aimerais vous présenter. Capistrano utilise beaucoup la commande « sudo » pour se mettre en « root ». Dans mon cas, je n’ai pas accès à cette commande. Pas de soucis, ils ont prévu le coup, on rajoute une ligne:

Dernière petite touche. En temps normal, Capistrano va faire un « touch » des dossiers « images », « stylesheets » et « javascripts ». Comme je n’en ai pas dans mon exemple, j’ajoute cette ligne:

Et voilà! C’est tout, vous êtes prêt à faire du déploiement automatisé. Bien entendu, il existe d’autres variables, en voici la liste: https://github.com/capistrano/capistrano/wiki/2.x-Significant-Configuration-Variables

 

Place au déploiement!

La première fois que vous allez déployer votre application, il faut d’abord que Capistrano crée une arborescence sur votre serveur distant. Dans « Git Bash », vous allez donc exécuter ceci:

Ceci va créer cette structure de dossiers sur votre serveur:

  • releases
  • shared
    • log
    • pids
    • system

Le dossier « releases » contiendra toutes les versions de vos déploiements rangés par date/heure. Nous y reviendrons plus tard.
Le dossier « shared » contiendra tout ce qui peut être partagé entre version, comme les logs, les fichiers uploadés par vos utilisateurs, etc…

Nous allons à présent faire un petit « check » pour voir si Capistrano pense que notre déploiement va fonctionner. Il vérifie les droits, l’existence de dossier, etc…

Si vous avez le message « You appear to have all necessary dependencies installed », c’est que ca s’annonce pas mal 🙂

Essayons donc de déployer notre code, en n’oubliant pas de croiser les doigts.

Cette petite commande en appelle d’autres:

  • cap deploy:update_code :
    • En local (parce que je l’ai précisé avec la variable deploy_via), il clone votre repository dans un dossier temporaire.
    • Compresse le dossier temporaire
    • Upload le fichier compressé sur le serveur distant
    • Décompresse le fichier dans le dossier « releases/[YmdHis] » (ex: 20110926162059 pour un déploiement à 16:20:59 le 16/09/2011) qui sera la release en cours.
  • cap deploy:finalize_update
    • crée les dossiers « log », « public », « tmp » dans votre release actuelle.
    • C’est ici que Capistrano fait un touch des assets (css, js, images) si vous n’indiquez pas « set :normalize_asset_timestamps, false« .
  • cap deploy:create_symlink
    • Crée un lien symbolique »current », au même niveau que « releases » et « shared », vers la release en cours.
  • cap deploy:restart
    • Ne fait rien dans notre cas

Etant donné que c’est notre premier déploiement, il faut encore créer un lien symbolique du dossier de notre site internet (ex: /home/monsiteInternet.com) vers le dossier « current » (/home/loginssh/deploy/current).  Pour tous vos prochains « deploy » , le lien symbolique de « current » se positionnera sur la bonne release, ce qui mettra votre site à jour automatiquement. Elle est pas belle la vie?

Bien sûr, qui dit déploiement, dit « rollback ». En effet, tout ca c’est génial, mais si vous remarquez que rien ne fonctionne comme il faut, il est nécessaire de pouvoir (vite) faire marche arrière. Rien de plus simple avec Capi’, une ligne de code suffit:

Avec cette commande, Capistrano va simplement changer le lien symbolique de « current » pour le placer sur la release précédente. Cela parait magique, mais comme toutes vos releases sont dans le dossier « releases », et rangées par date, il peut facilement la retrouver.

 

Conclusion

Nous sommes en 2013 et mettre à jour un site n’a plus rien de sorcier. Vous remarquez que cela peut aussi être fait sur un serveur qui n’a ni Git, ni Ruby installé, et ce, sans les droits « root ». Tout cela en une ligne de commande avec un taux d’erreur franchement très bas. Sans compter, le « rollback », tout aussi simple, qui est un sacré filet de sécurité « au cas où ».

Ce que je vous ai présenté n’est que la partie émergée de l’iceberg. Il y a un nombre incroyable de possibilités. En effet, vous pouvez à tout moment, lancer des lignes de commandes pendant la migration grâce aux « hooks ».

Voici un petit exemple très basique: j’aimerais créer un dossier « sql » juste après les opérations de « deploy:setup », rien de plus simple, je rajoute ceci à mon fichier:

Cela peut aller très loin, surtout qu’il existe des extensions à la pelle.

Je conseillerai à tout le monde d’au moins commencer par le déploiement de code, même si vous n’utilisez pas de système de versioning . Une fois que vous vous sentez à l’aise, passez aux assets, à la migration de base de données, etc…

 

 

Les perles de code: volume 2

Aujourd’hui, nouvelle fournée de perles.

Au programme,du commentaire « utile », une existante native réécrite (et bugée), et une condition qui ne fait pas ce qu’elle devrait 🙂

Le « if » de trop…Ou le « else » de trop peu?

Review de code 1:  Pourquoi deux fois exactement le même if l’un à la suite de l’autre? Peut-être un copier coller.
Review de code 2: Si « flow_id » n’est pas setté, la méthode ne renvoit pas « 0 » (zéro), comme le laisse entre la seconde ligne. En effet, si l’on indente son code correctement, voici ce que cela donne:

En ajoutant les accolades, c’est encore plus clair: Si « flow_id » n’existe pas, la méthode ne renverra rien.

Le commentaire pour les aveugles

Juste une petite anecdote ici, rien de bien méchant.

Review de code: Merci pour ce commentaire qui m’a bien aidé à comprendre la condition.

L’ « array_unique » du pauvre

Petite explication:

  • La fonction reçoit un tableau de valeurs (toutes des chaines de caractères).
  • On prépare un autre tableau de backup ($valuesList), qui va sans doute servir à transvaser les valeurs une à une… Enfin c’est ce que l’on croit.
  • On boucle sur les valeurs,
    • Pour chaque valeur, si le tableau de backup n’est pas vide, on vérifie que la valeur en cours n’est pas déjà dedans. Si c’est le cas, on renvoi false
    • Si la valeur ne se trouve pas dans le tableau de backup, on l’insère dans lui même. Et ça c’est beau. On va donc avoir un tableau qui va contenir uniquement un autre tableau, et ainsi de suite… Et ce autant de fois qu’on a de valeurs.

La méthode renvoi donc TOUJOURS true. et « $valueList » vaut ceci après 2 itérations:

Review de code 1: Nom de la méthode pas très explicite
Review de code 2: Renvoi toujours true
Review de code 3: Idée d’amélioration en une ligne (au cas où): return array_unique($values) === $values
Review de code 4: Dommage que ton test unitaire soit commenté,… Mais je comprends mieux pourquoi 🙂

 

A bientôt pour de nouvelles aventures. J’arrive avec plein d’articles sur les patterns. « Y’a plus qu’à »

Les perles de code: volume 1

L’heure est venue de reprendre un peu ce blog en main. Je n’ai malheureusement pas eu le temps de le faire ces derniers mois, mais cette fois-ci, on ne rigole plus.

Afin de reprendre « mon travail » en douceur, je vous propose quelques perles trouvées ci et là dans le code lors des mes reviews de code. Je vous en propose 2 par article… Histoire d’en garder un peu sous le code, et aussi de vous donner une bonne raison de revenir 😉

Je rappelle que tous ces codes sont issus de projets réels, je n’ai rien inventé. Seuls les noms de variable ont été changées pour des raisons obscures.

Place aux gurus du dev…. N’oubliez pas la devise de cette rubrique: « On peut rire, mais pas se moquer »

1. Php: Le else mal aimé

Question de review: Quand arrive-t-on dans le « else » afin d’avoir le « template3.php » ? Probablement jamais 🙂

2. Mysql : To be or not to be

Question de review: Il faut donc compter les clients qui sont dans le groupe « 12345 » ou aucun… Mais il faut exclure ceux qui ne sont dans aucun groupe. N’est-ce pas plus simple, dès lors, de juste prendre ceux du groupe « 12345 »?

 

Si vous aussi vous découvrez des perles, n’hésitez pas à me les envoyer, je me ferai un plaisir de les diffuser sur le site.

 

PHP5 : Les objets ne sont pas des (détestables) références

Depuis PHP5, j’entends souvent dire que les objets utilisent les références lors d’une assignation ou lorsqu’on les passe en paramètre par exemple. Même la documentation officielle (http://php.net/manual/fr/language.operators.assignment.php) l’explique noir sur blanc:

« Une exception au comportement d’assignation par valeur en PHP est le type object, ceux-ci sont assignés par référence dans PHP 5. La copie d’objet doit être explicitement demandée grâce au mot-clé clone. ».

C’est ce que l’on dit pour simplifier les choses, mais nous verrons plus loin que ce n’est pas tout à fait vrai.

En effet, je me suis penché sur la chose lors d’une mission où j’ai rejoint une équipe qui a dû passer une grosse application de PHP4 vers PHP5.3. A l’époque de PHP4, les objets étaient passés en copie, ce qui pouvait poser deux gros problèmes:

  • Explosion de la mémoire
  • Mise à jour d’une copie d’un objet en pensant travailler sur l’original.

Le « & » (pour « référence ») était donc vu comme le messie pour pallier à ce problème.

En php4 :

Et en PHP5, le résultat est le même donc tout va bien ! Le « & » n’a donc, à priori, pas d’impact pour les objets.

 

1. Mon problème

Je vais rapidement illustrer un problème récurrent que j’ai pu observer lors de l’analyse du projet. J’ai synthétisé au maximum le code pour n’avoir que le strict minimum. Gardez également en tête que nous venions d’une application PHP4 qui nécessitait l’utilisation des références pour les objets.

Nous avons d’abord une classe « Tab », qui a un indice (int) :

Et un TabManager, qui gère des « Tab »:

Et maintenant l’exécution (simplifiée) en PHP5:

Comme vous pouvez le constater, le premier « Tab » s’est transformé en deuxième « Tab » sans que l’on ait demandé quoique ce soit : C’est mesquin ! La faute à qui ? Les objets ? Les références ? Pourquoi ? Comment ? Quel suspens. Je me vois tout à coup dans une série policière, mon enquête peut débuter. Mon premier témoin sera la doc officielle : Elle a toujours quelque chose d’intéressant à raconter.

 

2. Les objets démystifiés! On m’aurait menti ?

Après une rapide recherche, j’arrive sur une page de la doc officielle qui parle des références (http://www.php.net/manual/fr/language.oop5.references.php). On peut y lire ceci :

« Depuis PHP 5, un une variable objet ne contient plus l’objet en lui-même. Elle contient seulement identifiant d’objet, qui permet aux accesseurs d’objets de trouver cet objet. Lorsque l’objet est utilisé comme argument, retourné par une fonction, ou assigné à une autre variable, les différentes variables ne sont pas des alias (ndlr : références) : elles contiennent des copies de l’identifiant, qui pointent sur le même objet. »

Première information importante, on ne manipule pas directement un objet, mais un identifiant. Pour info, c’est de l’hexadécimal (ex : 0000000041ae805200000000642707c6). Pour le connaitre, vous pouvez utiliser la méthode « spl_object_hash ».

Deuxième chose intéressante, lorsque l’on passe un objet en paramètre d’une fonction (ou qu’on l’assigne à une autre variable), on ne lui donne, en fait, qu’une copie de son identifiant. Une copie ? Cela ressemble furieusement à ce qu’il se passe pour un tableau, un entier ou une chaine de caractères, non ? Hé bien oui, c’est pareil !

Pour en avoir le cœur net, lançons un petit test :


Il n’y a rien d’incroyable dans cet exemple, si ce n’est que cela confirme les dires de la doc :

  • « $o » a le même identifiant que « $monObjet ». Ce qui nous permet de modifier le contenu et d’appeler les méthodes du même objet.
  • La fonction « add » reçoit une copie de l’identifiant. En effet, la variable « $o », interne à la fonction, a beau être écrasée par « null », cela n’impacte pas l’objet extérieur. Pourquoi ? Tout simplement parce que ce n’est pas une référence. L’effet est donc le même que pour un paramètre de type entier, tableau ou autre.

Le mythe est tombé : En objet, on travaille donc aussi avec des « copies ». Certes, ce sont des copies d’identifiant et pas des copies de valeur (« clone ») comme pour tous les autres types, mais c’est la seule subtilité. Ce stratagème « made in PHP5 » nous permet de modifier le contenu d’un même objet, un peu partout dans le code, sans savoir si celui-ci a bien été donné/assigné en référence. Les objets n’ont donc plus rien à voir avec les références.

Il était important de connaitre ce principe pour attaquer la deuxième partie de l’enquête : Les références/alias.

 

3. Les références/alias… Aïe, j’ai mal

Pour commencer, revenons sur la doc officielle (http://www.php.net/manual/fr/language.oop5.references.php) qui nous donne un premier indice sur les références :

 « Une référence PHP est un alias, qui permet à deux variables différentes de représenter la même valeur »

On parle bien de variables. J’en conclu que, dans le monde des références, tout le monde est logé à la même enseigne, y compris les objets. On aura beau travailler avec une chaine de caractère, un entier ou un objet, le résultat sera le même. Tant mieux, s’il n’y a pas d’exception, ce sera plus facile à expliquer J Cependant, laissez-moi vous dire que ce ne sera pas, pour autant, une partie de plaisir.

Petit rappel important avant de commencer: Lorsqu’une méthode reçoit un paramètre en référence (&$monParam) pour l’assigner à une variable interne d’un objet, assurez-vous de bien assigner la variable par référence, sinon PHP vous en fera une copie. Un simple exemple :

Comme vous pouvez le voir, indiquer au paramètre que c’est une référence n’est pas toujours suffisant. Faites donc bien attention lorsque vous travaillez avec des références dans des objets. Un simple oubli et je vous garantis de longues journées « à la recherche du & perdu ».

Ceci étant dit, revenons-en à nos moutons ! Pour bien comprendre les références, commençons simplement par écrire une méthode qui recevra 2 paramètres :

  • Le premier est passé en référence car on veut que la variable donné en paramètre (l’originale, c’est-à-dire « $un ») soit aussi modifié.
  • Le deuxième est une simple valeur passée en copie.

A présent, faisons le même test, mais avec une assignation par référence (=&). Cet exemple n’est pas pris au hasard, puisque je suis souvent tombé dessus lors de la migration de notre application de PHP4 à PHP5.3.

Aïe, alors que nous avons utilisé les références, notre variable « $un », n’a pas été modifiée ! C’est comme si aucune référence n’avait existé. Trop de références, tuerait-elle la référence ? Afin d’en avoir le cœur net, j’ai décidé d’écrire tous les cas possibles pour, peut-être, avoir des indices sur leurs manières de fonctionner. Voici la liste des 8 cas :


Je me suis amusé à tester chacune d’entre elles de cette manière (exemple avec la première méthode de la liste) :


Le résultat final de la variable « $un » sera notre indicateur : Vaut-elle 1 ou 2 ? Une fois les exécutions terminées, j’ai pu regrouper les méthodes en deux ensembles :

  • « $un » vaut « 1 »:
    • paramAB-assign_ref
    • paramA-assign_ref
    • paramB-assign_ref
    • param-assign_ref
    • paramB-assign_copy ¹
    • param-assign_copy ¹
  • « $un » vaut « 2 »:
    • paramAB-assign_copy
    • paramA-assign_copy

¹: Attention toutefois, en Php 5.3, on peut encore (même si c’est déprécié) forcer un paramètre qui demande une copie (pas de & devant le nom de la variable du paramètre) à recevoir une référence, en l’indiquant explicitement lors de l’appel à la fonction (Ex : maFonction (&$a,&$b)). Si vous faites cela, alors « paramB-assign_copy » et « param-assign_copy » changeront aussi la valeur de « $un » et seront donc dans le deuxième ensemble.

Voici ce que l’’on peut en conclure :

  • Il est visiblement acquis que donner « $deux » en référence ou en copie, ne change strictement rien au résultat de « $un ». Ça se tient ! Au final que « $b » soit une référence ou non, c’est la valeur de « $un » qui nous intéresse.
  • Assigner par référence ne change pas « $un » non plus… Ça c’est étrange, mais nous y reviendrons !

Par contre, pour modifier la variable « $un », il faut deux conditions:

  • « $un » doit être passé en référence. En effet, passer la variable en copie, ne changera que la valeur de…sa copie.
  • L’assignation doit se faire via un copy (« = ») et non pas par référence (« = & »).

Tout est clair, sauf un cas: Pourquoi « $un » n’est pas modifié pour les fonctions qui assignent par référence (« paramA-assign_ref » et « paramAB-assign_ref ») ? Voici un exemple plus simple pour expliquer le problème :

« $a » vaut 2 car « $a1 » est une référence vers « $a ». Si par contre, j’assigne par référence…


« $a » vaut toujours 1 et n’a pas changé ! Mais pourquoi ? La réponse n’est pas évidente et n’est indiquée nulle part dans la documentation.

Heureusement la communauté veille, et visiblement, je ne suis pas le premier à m’être posé la question. Après avoir épluché les commentaires des développeurs, tous en sont arrivés à la même conclusion : Lorsque vous assignez par référence une variable à une autre, seule la variable concernée est modifiée (dans notre cas, la variable « $a1 »). Il n’y a donc pas de dommage collatéral pour les variables ayant la même référence, elles continuent de pointer vers la même valeur et ne sont pas impactées par l’opération. Fou, n’est-ce pas ?

Je vous laisse avec un dernier casse-tête.

A votre avis, que vaut « $a1 » et « $a2 » ? On pourrait se dire : « $a » est une référence vers « $b », et comme « $a1 » et « $a2 » sont des références vers « $a », alors « $a1 » et « $a2 » sont également modifiés… Bien essayé, mais ça n’est pas le cas J. En effet, « $b » est assigné à « $a » par référence, donc seul « $a » change. CQFD.

 

4. Conclusion

Au terme de cet article, que pouvons-nous retenir ?

Tout d’abord, ne dites plus qu’un objet est une référence : C’est un raccourci rapide pour dire que vous ne devez plus utiliser les références lorsque vous passez votre objet à une méthode. Un objet « est » un identifiant (voir «spl_object_hash ») qui est copié lorsqu’il est donné en paramètre ou assigné à une autre valeur, comme le sont les chaines de caractères, les tableaux ou les entiers.

Ensuite, on vous l’a sans doute déjà dit mais, n’utilisez jamais les références à moins de savoir vraiment ce que vous faites (et de l’indiquer pour ceux qui reprendront votre code)! Comme vous avez pu le constater, c’est un nid à problèmes. Selon moi, utiliser des références est souvent preuve d’une mauvaise architecture. Et contrairement à ce que l’on pense, les références n’améliorent pas les performances, pire, elles pourraient les dégrader dans pas mal de cas…

Welcome home

C’est parti pour un tour ! Un an après avoir acheté le(s) nom(s) de domaine, je me décide enfin à lancer mon blog. Pour la gloire, l’argent et les femmes bien sûr, mais aussi un peu pour moi-même. En effet, je compte parcourir un maximum de sujets qui touchent de près ou de loin à mon domaine de prédilection : le web. On passera surement du coq (javascript) à l’âne (PHP) en passant par tous les autres animaux de la ferme: css3, html5, tests unitaires, pattern, plugins et j’en passe… Le but étant d’apprendre en écrivant.

Je vais essayer de pondre au moins un article par mois. C’est un peu radin, je vous l’accorde, mais je me suis rendu compte que faire un article demandait pas mal d’investigation, et donc de temps, surtout si c’est un sujet que je ne connais pas sur le bout des doigts. Nous verrons à l’usage ce que ça donne…

Pour détendre l’atmosphère, je vous concocte également 2-3 « perles », fruit du travail de collègues/stagiaires qui ont laissé libre court à leur imagination… parfois débordante. « No offense » comme dirait l’autre, on pourra rire, mais pas se moquer. N’hésitez pas à partager les vôtres ! Vous aurez bien évidemment vos royalties et serez donc aussi plein aux as !

Bref, voilà pour l’intro. Je vous souhaite la bienvenue, mettez-vous à l’aise, le frigo est plein, servez-vous.