Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Réutilisabilité du code au sein d'un contexte multi-technos

Réutilisabilité du code au sein d'un contexte multi-technos

PHP Tour Nantes 2012

Nicolas Le Nardou

November 29, 2012
Tweet

More Decks by Nicolas Le Nardou

Other Decks in Programming

Transcript

  1. Page 1 Réutilisabilité du code Réutilisabilité du code IT&L@bs CO

    PMM Version 1.01, le 29 novembre 2012 Nicolas Le Nardou Architecte / Expert Technique PHP [email protected]
  2. Introduction : contexte projet > Centre de services d’un grand

    groupe de presse > 20-30 sites en PHP - eZ Publish, Symfony 2, WordPress, from scratch, … > Forte audience : environ 10M de pages vues / jour Page 2 Réutilisabilité du code > Forte audience : environ 10M de pages vues / jour > Périmètres fonctionnels très proches > Pression sur les coûts de développement
  3. Introduction : contexte projet > A son écriture, le partage

    du code entre plusieurs sites est : - Soit déjà acté - Soit déjà en cours de discussion > Le partage avec les autres sites du CDS est toujours de l’ordre du possible Page 3 Réutilisabilité du code
  4. Introduction : problématique > Comment se construire un référentiel de

    code commun à tous nos sites ? - Quel que soit le socle technique - Sans renoncer aux apports de ces différents socles (pas de politique du plus petit dénominateur commun) > Objectifs : Page 4 Réutilisabilité du code > Objectifs : - Mutualiser la maintenance du code - Réduire les temps de développement
  5. sommaire > 1 – Rappel des principes de conception SOLID

    > 2 – Etude de cas concrets > 3 – Point sur les tests unitaires Page 5 Réutilisabilité du code > 3 – Point sur les tests unitaires
  6. conception SOLID > Single Responsibility > Open / Closed >

    Liskov substitution Page 7 Réutilisabilité du code > Interface segregation > Dependency injection
  7. SOLID vs STUPID > Singleton > Tight coupling > Untestability

    Page 8 Réutilisabilité du code > Premature Optimization > Indescriptive Naming > Duplication
  8. SOLID : single responsibility > Principe de responsabilité unique :

    > « Une classe ne fait qu’une et une seule chose » > Envie de rajouter une fonctionnalité ? Il est temps de créer une nouvelle classe. Page 9 Réutilisabilité du code > Une classe au fonctionnement clairement défini et borné sera plus facilement réutilisable > Une classe aux multiples responsabilités sera fatalement dupliquée pour être adaptée au nouveau besoin
  9. SOLID : open / closed > « Une classe doit

    être fermée à la modification et ouverte à l’extension » > Une évolution ne devrait pas vous faire casser du code, juste en ajouter ! Page 10 Réutilisabilité du code > Une classe doit prévoir de pouvoir être étendue sans être réécrite.
  10. SOLID : Liskov substitution > « On doit pouvoir substituer

    à un objet d’une classe X, tout objet d’une sous classe de X » > Corolaire : Une classe utilisant un objet de classe X ne doit pas avoir connaissance des sous classes de X (sous peine de violer le principe open/closed) Page 11 Réutilisabilité du code
  11. SOLID : Interface segregation > « Un objet ne devra

    pas dépendre d’un autre objet mais de son interface » > Il faut expliciter la dépendance réelle au travers d’une interface Page 12 Réutilisabilité du code
  12. SOLID : Dependency Injection > « Un objet ne doit

    pas instancier un autre objet, il doit le recevoir de l’extérieur » > Inversion de contrôle > Pas d’utilisation du mot clé new dans une classe Page 13 Réutilisabilité du code > Pas d’utilisation du mot clé new dans une classe > Injection par constructeur ou mutateur
  13. cas concret #1 > Besoin : Injecter dans nos pages

    des tags javascript (tracking, pub, …) > Implémentation : Une classe TagServer qui calcule la valeur d’un tag en fonction d’un contexte en entrée (url, contenu, …) et d’un jeu de règles Page 15 Réutilisabilité du code - Moteur de règles - Jeu de règles en configuration > Contrainte : A déployer sur : 1. Un site eZ Publish 2. Un site Symfony 2.x
  14. cas concret #1 > Les mauvaises solutions : - Faire

    2 développements distincts - Dupliquer la classe et la modifier Page 16 Réutilisabilité du code - Nombreux paramètres dans le constructeur - Ou toute autre abomination …
  15. cas concret #1 : implémentation eZ Publish class TagServer {

    private $rules; public function __construct() { Page 17 Réutilisabilité du code { $this->rules = array(); $ini = eZINI::instance('tagserver.ini'); $ini->assign('Tags', 'Rules', $this->rules); } // ... }
  16. cas concret #1 : problèmes public function __construct() { $this->rules

    = array(); $ini = eZINI::instance('tagserver.ini'); $ini->assign('Tags', 'Rules', $this->rules); Page 18 Réutilisabilité du code > Couplage fort : TagServer dépend de eZINI La classe n’est réutilisable que sur un autre site eZ Publish }
  17. cas concret #1 : problèmes > Solution : injecter l’objet

    eZINI dans le constructeur > On pourra ainsi substituer à une occurrence d’eZINI, un objet d’une Injection de dépendances (SOLID) Page 19 Réutilisabilité du code > On pourra ainsi substituer à une occurrence d’eZINI, un objet d’une sous classe d’eZINI
  18. cas concret #1 : eZINI injecté class TagServer { private

    $rules; public function __construct(eZINI $ini) { Page 20 Réutilisabilité du code { $this->rules = array(); $ini->assign('Tags', 'Rules', $this->rules); } // ... }
  19. cas concret #1 : eZINI injecté $ini = eZINI::instance('tagserver.ini'); $server

    = new TagServer($ini); > La construction du serveur : Page 21 Réutilisabilité du code
  20. cas concret #1 : eZINI injecté > Couplage désormais faible

    > Mais problème de sémantique : conceptuellement nous n’avons pas besoin d’un eZINI, nous avons plutôt besoin de la configuration. Il nous faut une interface « Configuration » Page 22 Réutilisabilité du code Il nous faut une interface « Configuration » Séparation d’interfaces (SOLID)
  21. cas concret #1 : interface Configuration interface Configuration { const

    SEPARATOR = '/'; /** * Read configuration if exists. Returns default value * otherwise. Page 23 Réutilisabilité du code * otherwise. * * @param string $variableName fully qualified variable name * @param mixed $defaultValue */ public function read($variableName, $defaultValue); }
  22. cas concret #1 : interface Configuration class TagServer { private

    $rules; public function __construct(Configuration $config) { Page 24 Réutilisabilité du code { $this->rules = $configuration->read( 'tagserver/Tags/Rules', array() ); } }
  23. cas concret #1 : interface Configuration > La dépendance avec

    le framework d’eZ Publish est rompue … > … mais notre code ne fonctionne plus pour eZ Publish > Il nous faut une implémentation de Configuration reposant sur eZINI Substitution de Liskov (SOLID) Page 25 Réutilisabilité du code
  24. cas concret #1 : eZConfiguration class eZConfiguration implements Configuration {

    public function read($variableName, $defaultValue) { list($file, $group, $variable) = explode(self::SEPARATOR, $variableName); Page 26 Réutilisabilité du code $ini = eZINI::instance($file . '.ini'); $ini->assign($group, $variable, $defaultValue); return $defaultValue; } }
  25. cas concret #1 : eZConfiguration > Appel $configuration = new

    eZConfiguration(); $server = new TagServer($configuration); > Fonctionne à nouveau pour eZ Publish Page 27 Réutilisabilité du code > Fonctionne à nouveau pour eZ Publish - Sans modification de la classe TagServer Open / Closed (SOLID)
  26. cas concret #1 : site Symfony > Etape suivante :

    réutiliser notre classe TagServer sur un site reposant sur Symfony Page 28 Réutilisabilité du code
  27. cas concret #1 : site Symfony > Bien sûr, la

    classe eZConfiguration ne fonctionnera pas > Il nous faut une classe YamlConfiguration Page 29 Réutilisabilité du code
  28. cas concret #1 : site Symfony class YamlConfiguration implements Configuration

    { public function read($variableName, $defaultValue) { list($file, $group, $variable) = explode(self::SEPARATOR, $variableName); Page 30 Réutilisabilité du code $loader = Yaml::parse($file); if(array_key_exists($loader[$group][$variable])) { return $loader[$group][$variable]; } return $defaultValue; } }
  29. cas concret #1 : site Symfony > Construction du serveur

    : > Et …. c’est tout ! $configuration = new YamlConfiguration(); $server = new TagServer($configuration); Page 31 Réutilisabilité du code > Et …. c’est tout !
  30. cas concret #1 : bilan > Coût du déploiement de

    notre classe TagServer sur un autre framework PHP ≈ Coût de développement d’une classe d’adaptation pour accéder à la configuration Page 32 Réutilisabilité du code > Aucune modification de notre classe TagServer n’a été nécessaire > Les classes de la couche d’adaptation sont elles-mêmes réutilisables constitution d’une boîte à outils très rapidement
  31. cas concret #2 > Nous voulons ajouter des logs à

    notre classe TagServer > Contraintes : - Possibilité de les activer / désactiver - Possibilité de se reposer sur le système de log du socle technique utilisé Page 34 Réutilisabilité du code
  32. cas concret #2 class TagServer { private $logger; public function

    __construct(Logger $logger) { Page 35 Réutilisabilité du code { $this->logger = $logger; } }
  33. cas concret #2 : empilement de paramètres class TagServer {

    private $rules, $logger; public function __construct(Configuration $configuration, Logger $logger) { Page 36 Réutilisabilité du code { $this->logger = $logger; $this->rules = $configuration->read( 'tagserver/Tags/Rules', array() ); } }
  34. cas concret #2 : dépendance faible > Contrairement à la

    configuration, le logger est une dépendance faible > Un logger n’est pas requis pour le fonctionnement de notre classe Page 37 Réutilisabilité du code Injection par mutateur
  35. cas concret #2 : injection par mutateur class TagServer {

    private $logger; public function __construct(Configuration $configuration) { $this->logger = null; Page 38 Réutilisabilité du code $this->logger = null; /* ... */ } public function setLogger(Logger $logger) { $this->logger = $logger; return $this; } }
  36. cas concret #2 : injection par mutateur private function writeLog($message)

    { if($this->logger !== null) { $this->logger->write($message); } } Page 39 Réutilisabilité du code > Et l’appel : $server = new TagServer(new eZConfiguration()); $server->setLogger(new eZLogger());
  37. cas concret #2 : overhead > Les cas présentés sont

    simples et petits > A dimension d’un projet réel, les overheads de code pour construire les objets peuvent devenir pénibles à gérer. > Par exemple, il a fort à parier que le logger soit nécessaire sur de Page 40 Réutilisabilité du code > Par exemple, il a fort à parier que le logger soit nécessaire sur de nombreuses classes.
  38. cas concret #2 : conteneur d’injection > Solution : recours

    à un conteneur d’injection > Pimple (Sensio Labs) > DI Component de Symfony (Sensio Labs) Page 41 Réutilisabilité du code > Objet en charge de l’instanciation des autres objets
  39. cas concret #2 : conteneur commun abstract class Container extends

    Pimple { public function __construct() { $this['tagServer'] = function ($container){ $server = new TagServer($container['configuration']); Page 42 Réutilisabilité du code $server->setLogger($container['logger']); return $server; }; } }
  40. cas concret #2 : conteneur d’injection Conteneur commun Page 43

    Réutilisabilité du code Conteneur commun à tous les socles techniques Conteneurs spécifiques
  41. cas concret #2 : conteneur spécifique (eZ Publish) class eZContainer

    extends Container { public function __construct() { parent::__construct(); $this['configuration'] = function ($container){ return new eZConfiguration(); Page 44 Réutilisabilité du code return new eZConfiguration(); }; $this['logger'] = $this->share(function ($container){ return new eZLogger(); }); } }
  42. cas concret #2 : conteneur d’injection > Et la construction

    de notre classe : $container = new eZContainer(); $server = $container['tagServer']; Page 45 Réutilisabilité du code
  43. cas concret #2 : conteneur d’injection > Quelques remarques :

    - Le conteneur peut s’appuyer sur de la configuration (ex: Symfony) - Risque de dépendance au conteneur + global state Page 46 Réutilisabilité du code - Dépendances masquées : quid des outils d’analyse ?
  44. testabilité : souvenez-vous class TagServer { private $rules; public function

    __construct() { Page 48 Réutilisabilité du code { $this->rules = array(); $ini = eZINI::instance('tagserver.ini'); $ini->assign('Tags', 'Rules', $this->rules); } // ... }
  45. testabilité : problématique > Une instance eZ Publish est nécessaire

    Problème de performances des tests > Un fichier eZINI est également nécessaire Eparpillement du code de test Maintenabilité affaiblie Page 49 Réutilisabilité du code > Et si eZINI était un service à bouchonner ? (comme la db, un webservice ou le filesystem)
  46. testabilité : ArrayConfiguration class ArrayConfiguration implements Configuration { private $values;

    public function __construct(array $values) { $this->values = $values; } Page 51 Réutilisabilité du code public function read($variableName, $defaultValue) { if(array_key_exists($variableName, $this->values)) { return $this->values[$variableName]; } return $defaultValue; } }
  47. testabilité : le test unitaire class TagServerTest extends PHPUnit_Framework_TestCase {

    private $tagServer; public function setUp() { Page 52 Réutilisabilité du code { $configuration = new ArrayConfiguration(array( 'tagserver/Tags/Rules' => array(/* ... */) )); $this->tagServer = new TagServer($configuration); } }
  48. testabilité : bilan > C’est testable ! > C’est performant

    ! > Le test est facile à maintenir ! Page 53 Réutilisabilité du code > Possibilité de tester aussi les cas à la marge : - Configuration manquante - Configuration erronée - Configuration non consistante - …
  49. si vous avez des questions ? Page 55 Réutilisabilité du

    code Nicolas Le Nardou Architecte / Expert Technique PHP [email protected]