Code obsolète - Code tiers

image

Il existe une astuce dans la communauté TDD qui dit que nous ne devons pas utiliser des objets fictifs pour les types que nous ne possédons pas . Je pense que c'est un bon conseil, et j'essaie de le suivre. Bien sûr, il y a des gens qui disent que nous ne devons pas du tout utiliser de faux objets. Quelle que soit votre opinion, le conseil «ne pas imiter ce qui n'est pas le vôtre» contient également un sens caché. Les gens le dépassent souvent, voyant le mot "faux" et tombant en colère.

Cette signification cachée est que vous devez créer des interfaces, des clients, des ponts, des adaptateurs entre notre application et le code tiers que nous utilisons. La question de savoir si nous allons créer des objets fictifs de ces interfaces dans nos tests n'est même pas si importante. L'important est que nous créons et utilisons des interfaces qui séparent mieux notre code du code tiers. Un exemple classique de cela dans le monde PHP est la création et l'utilisation d'un client HTTP dans notre application qui utilise le client HTTP Guzzle , au lieu d'utiliser directement Guzzle.

Pourquoi? Eh bien, pour commencer, Guzzle possède une API beaucoup plus puissante que celle dont votre application a besoin (dans la plupart des cas). La création de votre propre client HTTP, qui ne fournit que l'ensemble nécessaire des API Guzzle, limitera les développeurs d'applications à ce qu'ils peuvent faire avec ce client. Si l'API Guzzle change à l'avenir, nous devrons apporter des modifications au même endroit, au lieu de corriger ses appels tout au long de l'application dans l'espoir que rien ne se cassera. Deux très bonnes raisons, et je n'ai même pas mentionné de faux objets!

Je ne pense pas que ce soit difficile à réaliser. Le code tiers se trouve généralement dans un dossier séparé de notre application, il s'agit souvent d'un vendor/ ou d'une library/ . Il réside également dans un espace de noms différent et a une convention de dénomination différente de celle utilisée dans notre application. Le code tiers est assez facile à identifier et, avec un peu de discipline, nous pouvons rendre notre code d'application moins dépendant de composants tiers.

Et si nous appliquons les mêmes règles au code hérité?


Que se passe-t-il si nous examinons notre code hérité ainsi que le tiers? Cela peut être difficile à faire, voire contre-productif si du code obsolète est utilisé exclusivement en mode support, lorsque nous corrigeons uniquement les bogues et en ajustons un peu les petites parties. Mais si nous écrivons un nouveau code qui (ré) utilise du code obsolète, je pense qu'il vaut la peine de le considérer de la même manière que le code tiers. Du moins du point de vue du nouveau code.

Si possible, le code obsolète et nouveau doit se trouver dans différents dossiers et espaces de noms. Cela fait longtemps depuis la dernière fois que j'ai vu le système sans démarrage, c'est donc tout à fait faisable. Mais au lieu d'utiliser aveuglément le code hérité dans le nouveau code, que se passe-t-il si nous créons des interfaces pour lui et les utilisons?

Le code obsolète est souvent plein d'objets «divins» qui font trop de choses. Ils utilisent un état global, ont des propriétés publiques ou des méthodes magiques qui donnent accès à des propriétés privées comme s'ils étaient publics, ont des méthodes statiques qui sont tout simplement très pratiques pour appeler n'importe qui et n'importe où. Cette commodité nous a donc conduits à la situation dans laquelle nous nous trouvons.

Un autre problème, peut-être encore plus grave, avec le code obsolète est que nous sommes prêts à le changer, à le corriger, à le casser parce que nous ne le considérons pas comme du code tiers. Que faisons-nous lorsque nous voyons un bogue ou que nous voulons ajouter une nouvelle fonctionnalité au code tiers? Nous décrivons le problème et / ou créons une pull request. Ce que nous ne faisons pas, c'est de ne pas aller au vendor/ dossier et de ne pas y éditer le code. Pourquoi faisons-nous cela avec du code hérité? Et puis nous croisons les doigts et espérons que rien ne s'est cassé.

Au lieu d'utiliser aveuglément du code obsolète dans le nouveau code, essayons d'écrire des interfaces qui n'incluront que le sous-ensemble requis de l'API de l'ancien objet «divin». Supposons que nous ayons un objet User dans le code hérité qui sait tout sur tout. Il sait comment changer l'email et le mot de passe, comment mettre à niveau les utilisateurs du forum en modérateurs, comment mettre à jour les profils d'utilisateurs publics, définir les préférences de notification, se sauver dans la base de données et bien plus encore.

src / Legacy / User.php
 <?php namespace Legacy; class User { public $email; public $password; public $role; public $name; public function promote($newRole) { $this->role = $newRole; } public function save() { db_layer::save($this); } } 

Ceci est un exemple grossier, mais reflète le problème: chaque propriété est publique et peut facilement être modifiée en n'importe quelle valeur, nous devons nous rappeler d'appeler explicitement la méthode save après toute modification à enregistrer, etc.

Limitons-nous et interdisons l'accès à ces propriétés publiques et essayons de deviner comment fonctionne un système obsolète tout en augmentant les droits des utilisateurs:

src / LegacyBridge / Promoter.php
 <?php namespace LegacyBridge; interface Promoter { public function promoteTo(Role $role); } 

src / LegacyBridge / LegacyUserPromoter.php
 <?php namespace LegacyBridge; class LegacyUserPromoter implements Promoter { private $legacyUser; public function __construct(Legacy\User $user) { $this->legacyUser = $user; } public function promoteTo(Role $newRole) { $newRole = (string) $newRole; //  ,  $role     ?  ! $legacyRoles = [ Role::MODERATOR => 1, Role::MEMBER => 2, ]; $newLegacyRole = $legacyRoles[$newRole]; $this->legacyUser->promote($newLegacyRole); $this->legacyUser->save(); } } 

Maintenant, lorsque nous voulons augmenter User droits des User dans le nouveau code, nous utilisons l'interface LegacyBridge\Promoter , qui traite de toutes les subtilités de l'augmentation d'un utilisateur dans un système obsolète.

Changer la langue d'origine


L'interface pour le code obsolète nous donne la possibilité d'améliorer la conception du système et peut nous sauver d'éventuelles erreurs de nommage qui ont été faites il y a longtemps. Le processus de changement du rôle d'un utilisateur d'un modérateur à un participant n'est pas une «promotion» (augmentation), mais plutôt une «rétrogradation» (diminution). Personne ne nous empêche de créer deux interfaces pour ces différentes choses, même si le code hérité est identique.

src / LegacyBridge / Promoter.php
 <?php namespace LegacyBridge; interface Promoter { public function promoteTo(Role $role); } 

src / LegacyBridge / LegacyUserPromoter.php
 <?php namespace LegacyBridge; class LegacyUserPromoter implements Promoter { private $legacyUser; public function __construct(Legacy\User $user) { $this->legacyUser = $user; } public function promoteTo(Role $newRole) { if ($newRole->isMember()) { throw new \Exception("Can't promote to a member."); } $legacyMemberRole = 2; $this->legacyUser->promote($legacyMemberRole); $this->legacyUser->save(); } } 

src / LegacyBridge / Demoter.php
 <?php namespace LegacyBridge; interface Demoter { public function demoteTo(Role $role); } 

src / LegacyBridge / LegacyUserDemoter.php
 <?php namespace LegacyBridge; class LegacyUserDemoter implements Demoter { private $legacyUser; public function __construct(Legacy\User $user) { $this->legacyUser = $user; } public function demoteTo(Role $newRole) { if ($newRole->isModerator()) { throw new \Exception("Can't demote to a moderator."); } $legacyModeratorRole = 1; $this->legacyUser->promote($legacyModeratorRole); $this->legacyUser->save(); } } 


Pas un grand changement, mais le but du code est devenu beaucoup plus clair.

Maintenant, la prochaine fois que vous devrez appeler certaines méthodes à partir de code obsolète, essayez de créer une interface pour elles. Cela peut être impossible, cela peut être trop cher. Je sais que la méthode statique de cet objet "divin" est vraiment très simple à utiliser et avec son aide, vous pouvez faire le travail beaucoup plus rapidement, mais considérez au moins cette option. Vous pouvez simplement améliorer légèrement la conception du nouveau système que vous créez.

Source: https://habr.com/ru/post/fr417683/


All Articles