Reutilisabilite du code - callmematthi.eu · SOLID : Dependency Injection > « Un objet ne doit pas...
Transcript of Reutilisabilite du code - callmematthi.eu · SOLID : Dependency Injection > « Un objet ne doit pas...
Page 1Réutilisabilité du code
Réutilisabilité du codeIT&L@bsCO PMMVersion 1.01, le 29 novembre 2012 Nicolas Le Nardou
Architecte / Expert Technique PHP
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 2Ré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
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 3Réutilisabilité du code
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 4Réutilisabilité du code
> Objectifs :- Mutualiser la maintenance du code- Réduire les temps de développement
sommaire
> 1 – Rappel des principes de conception SOLID
> 2 – Etude de cas concrets
> 3 – Point sur les tests unitaires
Page 5Réutilisabilité du code
> 3 – Point sur les tests unitaires
Conception SOLID
Rappel
Réutilisabilité du code
conception SOLID
> Single Responsibility
> Open / Closed
> Liskov substitution
Page 7Réutilisabilité du code
> Interface segregation
> Dependency injection
SOLID vs STUPID
> Singleton
> Tight coupling
> Untestability
Page 8Réutilisabilité du code
> Premature Optimization
> Indescriptive Naming
> Duplication
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 9Ré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
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 10Réutilisabilité du code
> Une classe doit prévoir de pouvoir être étendue sans être réécrite.
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 11Réutilisabilité du code
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 12Réutilisabilité du code
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 13Réutilisabilité du code
> Pas d’utilisation du mot clé new dans une classe
> Injection par constructeur ou mutateur
Cas concret #1
Réutilisabilité du code
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 15Réutilisabilité du code
- Moteur de règles- Jeu de règles en configuration
> Contrainte : A déployer sur :1. Un site eZ Publish2. Un site Symfony 2.x
cas concret #1
> Les mauvaises solutions :
- Faire 2 développements distincts
- Dupliquer la classe et la modifier
Page 16Réutilisabilité du code
- Nombreux paramètres dans le constructeur
- Ou toute autre abomination …
cas concret #1 : implémentation eZ Publish
class TagServer
{
private
$rules ;
public function __construct()
{
Page 17Réutilisabilité du code
{
$this-> rules = array ();
$ini = eZINI:: instance( 'tagserver.ini' );
$ini->assign( 'Tags' , 'Rules' , $this-> rules );
}
// ...
}
cas concret #1 : problèmes
public function __construct()
{
$this-> rules = array ();
$ini = eZINI:: instance('tagserver.ini');
$ini->assign( 'Tags' , 'Rules' , $this-> rules );
Page 18Réutilisabilité du code
> Couplage fort : TagServer dépend de eZINI
� La classe n’est réutilisable que sur un autre site eZ Publish �
}
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 19Réutilisabilité du code
> On pourra ainsi substituer à une occurrence d’eZINI, un objet d’une sous classe d’eZINI
cas concret #1 : eZINI injecté
class TagServer
{
private
$rules ;
public function __construct(eZINI $ini )
{
Page 20Réutilisabilité du code
{
$this-> rules = array ();
$ini ->assign( 'Tags' , 'Rules' , $this-> rules );
}
// ...
}
cas concret #1 : eZINI injecté
$ini = eZINI:: instance( 'tagserver.ini' );
$server = new TagServer($ini);
> La construction du serveur :
Page 21Réutilisabilité du code
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 22Réutilisabilité du code
� Il nous faut une interface « Configuration »
Séparation d’interfaces (SOLID)
cas concret #1 : interface Configuration
interface Configuration
{
const SEPARATOR = '/' ;
/**
* Read configuration if exists. Returns default value
* otherwise .
Page 23Réutilisabilité du code
* otherwise .
*
* @param string $variableName fully qualified variable name
* @param mixed $defaultValue
*/
public function read( $variableName , $defaultValue );
}
cas concret #1 : interface Configuration
class TagServer
{
private
$rules ;
public function __construct(Configuration $config )
{
Page 24Réutilisabilité du code
{
$this-> rules = $configuration ->read(
'tagserver/Tags/Rules' ,
array ()
);
}
}
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 25Réutilisabilité du code
cas concret #1 : eZConfiguration
class eZConfiguration implements Configuration
{
public function read( $variableName , $defaultValue )
{
list ($file, $group, $variable) =
explode( self :: SEPARATOR, $variableName );
Page 26Réutilisabilité du code
$ini = eZINI:: instance($file . '.ini' );
$ini->assign($group, $variable, $defaultValue );
return $defaultValue ;
}
}
cas concret #1 : eZConfiguration
> Appel
$configuration = new eZConfiguration();
$server = new TagServer($configuration);
> Fonctionne à nouveau pour eZ Publish
Page 27Réutilisabilité du code
> Fonctionne à nouveau pour eZ Publish- Sans modification de la classe TagServer
Open / Closed (SOLID)
cas concret #1 : site Symfony
> Etape suivante : réutiliser notre classe TagServer sur un site reposant sur Symfony
Page 28Réutilisabilité du code
cas concret #1 : site Symfony
> Bien sûr, la classe eZConfiguration ne fonctionnera pas
> Il nous faut une classe YamlConfiguration
Page 29Réutilisabilité du code
cas concret #1 : site Symfony
class YamlConfiguration implements Configuration{
public function read( $variableName , $defaultValue ){
list ($file, $group, $variable) = explode( self :: SEPARATOR, $variableName );
Page 30Réutilisabilité du code
$loader = Yaml:: parse($file);if (array_key_exists($loader[$group][$variable])){
return $loader[$group][$variable];}
return $defaultValue ;}
}
cas concret #1 : site Symfony
> Construction du serveur :
> Et …. c’est tout !
$configuration = new YamlConfiguration();$server = new TagServer($configuration);
Page 31Réutilisabilité du code
> Et …. c’est tout !
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 32Ré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
Cas concret #2
Réutilisabilité du code
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 34Réutilisabilité du code
cas concret #2
class TagServer
{
private
$logger ;
public function __construct(Logger $logger )
{
Page 35Réutilisabilité du code
{
$this-> logger = $logger ;
}
}
cas concret #2 : empilement de paramètres
class TagServer
{
private
$rules ,
$logger ;
public function __construct(Configuration $configuration , Logger $logger )
{
Page 36Réutilisabilité du code
{
$this-> logger = $logger ;
$this-> rules = $configuration ->read(
'tagserver/Tags/Rules' ,
array ()
);
}
}
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 37Réutilisabilité du code
� Injection par mutateur
cas concret #2 : injection par mutateur
class TagServer
{
private
$logger ;
public function __construct(Configuration $configuration )
{
$this - >logger = null ;
Page 38Réutilisabilité du code
$this - >logger = null ;
/* ... */
}
public function setLogger(Logger $logger )
{
$this-> logger = $logger ;
return $this;
}
}
cas concret #2 : injection par mutateur
private function writeLog( $message )
{
if ($this-> logger !== null )
{
$this-> logger ->write( $message );
}
}
Page 39Réutilisabilité du code
> Et l’appel :
$server = new TagServer( new eZConfiguration());
$server->setLogger( new eZLogger());
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 40Réutilisabilité du code
> Par exemple, il a fort à parier que le logger soit nécessaire sur de nombreuses classes.
cas concret #2 : conteneur d’injection
> Solution : recours à un conteneur d’injection
> Pimple (Sensio Labs)> DI Component de Symfony (Sensio Labs)
Page 41Réutilisabilité du code
> Objet en charge de l’instanciation des autres objets
cas concret #2 : conteneur commun
abstract class Container extends Pimple{
public function __construct(){
$this[ 'tagServer' ] = function ($container){
$server = new TagServer($container[ 'configuration' ]);
Page 42Réutilisabilité du code
$server->setLogger($container[ 'logger' ]);
return $server;};
}}
cas concret #2 : conteneur d’injection
Conteneur commun
Page 43Réutilisabilité du code
Conteneur communà tous les socles techniques
Conteneurs spécifiques
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 44Réutilisabilité du code
return new eZConfiguration();};
$this[ 'logger' ] = $this->share( function ($container){return new eZLogger();
});
}}
cas concret #2 : conteneur d’injection
> Et la construction de notre classe :
$container = new eZContainer();$server = $container[ ' tagServer ' ];
Page 45Réutilisabilité du code
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 46Réutilisabilité du code
- Dépendances masquées : quid des outils d’analyse ?
Et si on testait ?
Réutilisabilité du code
testabilité : souvenez-vous
class TagServer
{
private
$rules ;
public function __construct()
{
Page 48Réutilisabilité du code
{
$this-> rules = array ();
$ini = eZINI:: instance( 'tagserver.ini' );
$ini->assign( 'Tags' , 'Rules' , $this-> rules );
}
// ...
}
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 49Réutilisabilité du code
> Et si eZINI était un service à bouchonner ? (comme la db, un webservice ou le filesystem)
testabilité : ArrayConfiguration
> Il faut mocker la configuration ���� ArrayConfiguration !
Page 50Réutilisabilité du code
testabilité : ArrayConfiguration
class ArrayConfiguration implements Configuration
{
private $values ;
public function __construct( array $values )
{
$this-> values = $values ;
}
Page 51Réutilisabilité du code
public function read( $variableName , $defaultValue )
{
if (array_key_exists( $variableName , $this-> values ))
{
return $this-> values [ $variableName ];
}
return $defaultValue ;
}
}
testabilité : le test unitaire
class TagServerTest extends PHPUnit_Framework_TestCase
{
private
$tagServer ;
public function setUp()
{
Page 52Réutilisabilité du code
{
$configuration = new ArrayConfiguration( array (
'tagserver/Tags/Rules' => array ( /* ... */ )
));
$this-> tagServer = new TagServer($configuration);
}
}
testabilité : bilan
> C’est testable !
> C’est performant !
> Le test est facile à maintenir !
Page 53Réutilisabilité du code
> Possibilité de tester aussi les cas à la marge : - Configuration manquante- Configuration erronée- Configuration non consistante- …
merci!
Page 54Réutilisabilité du code
si vous avez des questions ?
Page 55Réutilisabilité du code
Nicolas Le NardouArchitecte / Expert Technique PHP