15

Tous,Est-ce que les méthodes d'extension masquent les dépendances?

Je voulais avoir quelques réflexions à ce sujet. Dernièrement, je suis de plus en plus un abonné de principes «puristes» DI/IOC lors de la conception/développement. Une partie de ceci (une grande partie) consiste à s'assurer qu'il y a peu de couplage entre mes classes, et que leurs dépendances sont résolues via le constructeur (il y a certainement d'autres façons de gérer cela, mais vous avez l'idée). Mon principe de base est que les méthodes d'extension violent les principes de DI/IOC.

J'ai créé la méthode d'extension suivante que j'utilise pour faire en sorte que les chaînes insérées dans les tables de base de données sont tronquées à la bonne taille:

public static class StringExtensions 
{ 
    public static string TruncateToSize(this string input, int maxLength) 
    { 
     int lengthToUse = maxLength; 
     if (input.Length < maxLength) 
     { 
      lengthToUse = input.Length; 
     } 

     return input.Substring(0, lengthToUse); 
    } 
} 

Je peux alors appeler ma chaîne à partir d'une autre classe comme ceci:

string myString = "myValue.TruncateThisPartPlease."; 
myString.TruncateToSize(8); 

une traduction fidèle de ce sans utiliser une méthode d'extension serait:

string myString = "myValue.TruncateThisPartPlease."; 
StaticStringUtil.TruncateToSize(myString, 8); 

Toute classe qui utilise l'un des exemples ci-dessus n'a pas pu être testée indépendamment de la classe qui contient la méthode TruncateToSize (TypeMock de côté). Si je n'utilise une méthode d'extension, et je ne voulais pas créer une dépendance statique, il ressemblerait plus:

string myString = "myValue.TruncateThisPartPlease."; 
_stringUtil.TruncateToSize(myString, 8); 

Dans le dernier exemple, la dépendance _stringUtil serait résolu par le constructeur et la classe pourrait être testé sans dépendance sur la classe de la méthode TruncateToSize (il pourrait être facilement moqué). De mon point de vue, les deux premiers exemples reposent sur des dépendances statiques (une explicite, une cachée), tandis que la seconde inverse la dépendance et fournit un couplage réduit et une meilleure testabilité.

L'utilisation de méthodes d'extension est-elle en conflit avec les principes DI/IOC? Si vous êtes abonné à la méthodologie du CIO, évitez-vous d'utiliser des méthodes d'extension?

+0

Votre méthode d'extension ne vérifie pas l'entrée nulle! – canon

+0

@antisanity: AFAIK il est logiquement impossible pour le paramètre d'instance d'une méthode d'extension (* input * dans ce cas) d'être nul. –

+3

Oui, Phil ... ça peut être nul. – canon

Répondre

15

Je vois d'où vous venez, cependant, si vous essayez d'esquiver la fonctionnalité d'une méthode d'extension, je crois que vous les utilisez de manière incorrecte. Les méthodes d'extension devraient être utilisées pour effectuer une tâche qui serait simplement gênante syntaxiquement sans eux. Votre TruncateToLength est un bon exemple. Le test de TruncateToLength n'impliquerait pas de se moquer, il impliquerait simplement la création de quelques chaînes et testerait que la méthode renvoyait effectivement la bonne valeur. D'un autre côté, si vous avez du code dans votre couche de données contenue dans les méthodes d'extension qui accèdent à votre magasin de données, alors oui, vous avez un problème et le test va devenir un problème.

Je n'utilise généralement que des méthodes d'extension afin de fournir du sucre syntaxique pour de petites opérations simples.

18

Je pense que c'est bien - parce que ce n'est pas comme TruncateToSize est un composant réaliste remplaçable. C'est une méthode qui n'aura jamais besoin de faire une seule chose.

Vous n'avez pas besoin de pouvoir créer une simulation - uniquement des services qui perturbent les tests unitaires (accès aux fichiers, etc.) ou ceux que vous souhaitez tester en termes de dépendances réelles. Si vous l'utilisiez pour effectuer une authentification ou quelque chose comme ça, ce serait une question très différente ... mais juste une opération de chaîne droite qui n'a absolument aucune configurabilité, différentes options d'implémentation etc - cela ne sert à rien de voir cela comme une dépendance dans le sens normal.

En d'autres termes: si TruncateToSize était un véritable membre de String, y réfléchiriez-vous à deux fois avant de l'utiliser? Essayez-vous de simuler l'arithmétique des nombres entiers, en introduisant IInt32Adder etc? Bien sûr que non. C'est juste la même chose, c'est seulement que vous fournissez la mise en œuvre. Unité tester le diable sur TruncateToSize et ne vous inquiétez pas à ce sujet.

+0

Même idée que mon message, d'accord – LorenVS

+0

Donc, je ne suis pas en désaccord avec votre réponse - c'est très pragmatique. Vous pouvez certainement prendre le CIO (ou n'importe quelle théorie de conception) trop loin. Par souci d'argument: en supposant que vous êtes engagé dans IOC, vous n'auriez aucun problème avec le deuxième exemple d'implémentation (la dépendance explicite sur une classe statique)? Je n'ai jamais lu un article d'un défenseur acharné de la COI disant que les dépendances statiques sont acceptables. –

+0

Et s'il y avait deux algorithmes pour TruncateToSize? Un qui échange de la vitesse pour la mémoire, et un qui échange de la mémoire pour la vitesse. Ne serait-il pas mieux dans ce cas s'il avait une interface? Et puisque vous ne pouvez pas prédire le besoin de ceux-ci, ne devriez-vous pas simplement en faire une interface en premier lieu? –

0

C'est une douleur parce qu'ils sont difficiles à simuler. Je l'habitude d'utiliser une de ces stratégies

  1. Eh oui, la ferraille l'extension est un PITA pour se moquer des
  2. Utilisez l'extension et l'essai juste qu'il a fait la bonne chose. c.-à-d. passer les données dans la troncature et vérifier qu'elle a été tronquée
  3. Si ce n'est pas quelque chose de trivial, et je dois me moquer, je vais faire en sorte que ma classe d'extension ait un setter pour le service qu'elle utilise, et régler cela dans le test code.

-à-dire

static class TruncateExtensions{ 

    public ITruncateService Service {private get;set;} 
    public string TruncateToSize(string s, int size) 
    { 
     return (Service ?? Service = new MyDefaultTranslationServiceImpl()). TruncateToSize(s, size); 
    } 
} 

Ceci est un peu effrayant parce que quelqu'un pourrait définir le service quand ils ne devraient pas, mais je suis parfois un peu cavalière, et s'il était vraiment important, que je pouvais faire quelque chose d'intelligent avec les indicateurs #if TEST, ou le modèle ServiceLocator pour éviter que le setter soit utilisé en production.

0

Méthodes d'extension, classes partielles et objets dynamiques. Je les aime vraiment, cependant vous devez marcher prudemment, il y a des monstres ici.

Je voudrais jeter un coup d'oeil aux langages dynamiques et voir comment ils font face à ces sortes de problèmes au jour le jour, c'est vraiment instructif. Surtout quand ils n'ont rien pour les empêcher de faire des choses stupides en dehors du bon design et de la discipline. Tout est dynamique au moment de l'exécution, la seule chose qui puisse les arrêter est que l'ordinateur lance une erreur d'exécution majeure. "Duck Typing" est la chose la plus folle que j'ai jamais vue, le bon code est la conception d'un bon programme, le respect des autres membres de votre équipe et la confiance que chaque membre a la possibilité de faire. la conception conduit à de meilleurs résultats. En ce qui concerne votre scénario de test avec des objets factices/ICO/DI, pourriez-vous vraiment mettre un travail intensif dans une méthode d'extension ou juste quelques trucs statiques simples qui fonctionnent de manière fonctionnelle? J'ai tendance à les utiliser comme vous le feriez dans un style de programmation fonctionnel, les entrées entrent, les résultats sortent sans magie au milieu, juste des classes de cadre droites que vous connaissez les gars de MS ont conçu et testé: P que vous pouvez compter sur. Si vous êtes en train de faire du gros travail en utilisant des méthodes d'extension, je regarderais à nouveau la conception de votre programme, vérifier vos conceptions CRC, maquettes, cas d'utilisation, DFD, schémas d'action ou tout autre chose que vous souhaitez utiliser. Dans cette conception, vous avez prévu de mettre cette substance dans une méthode d'extension au lieu d'une classe appropriée. À la fin de la journée, vous pouvez uniquement tester par rapport à la conception de votre système et ne pas coder en dehors de votre champ d'application.Si vous allez utiliser des classes d'extension, mon conseil serait de regarder les modèles de composition d'objet à la place et d'utiliser l'héritage seulement quand il y a une très bonne raison.

La composition d'objets gagne toujours avec moi car ils produisent du code solide. Vous pouvez les brancher, les sortir et faire ce que vous aimez avec eux. Attention, tout dépend si vous utilisez des interfaces ou non dans le cadre de votre conception. De plus, si vous utilisez des classes de composition, l'arborescence de la classe est aplatie en classes discrètes et il y a moins d'endroits où votre méthode d'extension sera récupérée dans les classes héritées.

Si vous devez utiliser une classe qui agit sur une autre classe comme c'est le cas avec les méthodes d'extension, examinez d'abord le modèle de visiteur et déterminez s'il s'agit d'une meilleure route.

Questions connexes