Mes collègues aiment dire que "la journalisation / la mise en cache / etc. Est une préoccupation transversale", puis continue en utilisant le singleton correspondant partout. Pourtant, ils aiment IoC et DI.
Est-ce vraiment une excuse valable pour casser le principe SOLI D ?
Réponses:
Non.
SOLID existe en tant que directives pour rendre compte de changements inévitables. N'allez-vous jamais vraiment changer votre bibliothèque de journalisation, votre cible, votre filtrage, votre mise en forme ou ...? N'allez-vous pas vraiment changer votre bibliothèque de cache, votre cible, votre stratégie, votre périmètre ou ...?
Bien sûr, vous êtes. À tout le moins , vous allez vouloir vous moquer de ces choses de manière saine pour les isoler aux fins de tests. Et si vous voulez les isoler pour les tester, vous allez probablement rencontrer une raison professionnelle qui les oblige à les isoler pour des raisons réelles.
Et vous obtiendrez alors l'argument que l'enregistreur lui - même gérera le changement. "Oh, si la cible / le filtrage / le formatage / la stratégie changent, alors nous allons simplement changer la configuration!" C'est des ordures. Non seulement vous avez maintenant un objet divin qui gère toutes ces choses, mais vous écrivez votre code au format XML (ou similaire) sans analyse statique, sans erreur de compilation, et sans erreur. t vraiment obtenir des tests unitaires efficaces.
Existe-t-il des cas de violation des directives SOLID? Absolument. Parfois, les choses ne changeront pas (sans exiger de toute façon une réécriture complète). Parfois, une légère violation de LSP est la solution la plus propre. Parfois, faire une interface isolée ne fournit aucune valeur.
Mais la journalisation et la mise en cache (et d’autres préoccupations transversales omniprésentes) ne sont pas ces cas. Ce sont généralement d'excellents exemples des problèmes de couplage et de conception que vous rencontrez lorsque vous ignorez les instructions.
la source
String
ouInt32
ou mêmeList
de votre module. Il y a juste une mesure pour laquelle il est raisonnable et sain d' esprit de planifier le changement. Et, au-delà des types de «noyau» les plus évidents, discerner ce que vous pourriez changer est vraiment une question d'expérience et de jugement.Oui
C’est l’essentiel de l’expression "préoccupation intersectorielle" - cela signifie quelque chose qui ne correspond pas parfaitement au principe SOLID.
C'est ici que l'idéalisme rencontre la réalité.
Les personnes semi-nouvelles dans SOLID et transversales se heurtent souvent à ce défi mental. C'est bon, ne panique pas. S'efforcer de tout mettre en termes de SOLID, mais il y a quelques endroits comme la journalisation et la mise en cache où SOLID n'a tout simplement pas de sens. Coupe transversale est le frère de SOLID, ils vont de pair.
la source
HttpContextBase
(introduite précisément pour cette raison). Je sais pour sûr que ma réalité serait vraiment mauvaise sans cette classe.Pour l'enregistrement, je pense que c'est. La journalisation est omniprésente et généralement sans rapport avec la fonctionnalité du service. Il est courant et bien compris d'utiliser les modèles de singleton du cadre de journalisation. Si vous ne le faites pas, vous créez et injectez des enregistreurs partout et vous ne le souhaitez pas.
Un problème de ce qui précède est que quelqu'un dira "mais comment puis-je tester la journalisation?" . Mes pensées sont que je ne teste pas normalement la journalisation, à part affirmer que je peux réellement lire les fichiers de log et les comprendre. Lorsque j'ai vu la journalisation testée, c'est généralement parce que quelqu'un a besoin d'affirmer qu'une classe a réellement fait quelque chose et qu'elle utilise les messages du journal pour obtenir ce retour. Je préférerais de loin inscrire un auditeur / observateur dans cette classe et affirmer dans mes tests que cela s'appelle. Vous pouvez ensuite placer la journalisation de vos événements dans cet observateur.
Je pense que la mise en cache est un scénario complètement différent, cependant.
la source
Mes 2 centimes ...
Oui et non.
Vous ne devriez jamais vraiment violer les principes que vous adoptez. mais vos principes doivent toujours être nuancés et adoptés au service d'un objectif plus élevé. Ainsi, avec une compréhension correctement conditionnée, certaines violations apparentes peuvent ne pas être de véritables violations de "l'esprit" ou du "corps de principes dans son ensemble".
Les principes SOLID en particulier, en plus de nécessiter beaucoup de nuances, sont finalement inféodés à l’objectif de «fournir un logiciel fonctionnel et maintenable». Ainsi, adhérer à un principe SOLID particulier est contre-productif et contradictoire lorsque cela entre en conflit avec les objectifs de SOLID. Et ici, je constate souvent que la prestation des atouts maintenabilité .
Alors, qu'en est-il du D dans SOLID ? Cela contribue à augmenter la maintenabilité en rendant votre module réutilisable relativement agnostique par rapport à son contexte. Et nous pouvons définir le "module réutilisable" comme "une collection de code que vous prévoyez d'utiliser dans un autre contexte distinct". Et cela s'applique à des fonctions, classes, ensembles de classes et programmes uniques.
Et oui, le fait de changer les implémentations de l'enregistreur place probablement votre module dans un "autre contexte distinct".
Alors, laissez-moi vous proposer mes deux grandes mises en garde :
Premièrement: tracer des lignes autour des blocs de code qui constituent "un module réutilisable" relève du jugement des professionnels. Et votre jugement est nécessairement limité à votre expérience.
Si vous n'envisagez pas actuellement d'utiliser un module dans un autre contexte, il est probablement acceptable qu'il en dépende impuissant. La mise en garde à la mise en garde: Vos plans sont probablement faux - mais c'est aussi OK. Plus vous écrivez, module après module, plus vous serez intuitif et précis: "J'aurai besoin de ça un jour de plus". Mais vous ne pourrez probablement jamais dire rétrospectivement: «J'ai tout modulé et tout découplé dans toute la mesure du possible, mais sans excès ».
Si vous vous sentez coupable de vos erreurs de jugement, allez à la confession et passez à autre chose ...
Deuxièmement: Inverser le contrôle n’équivaut pas à des dépendances d’injection .
Cela est particulièrement vrai lorsque vous commencez à injecter des dépendances ad nauseam . L'injection de dépendance est une tactique utile pour la stratégie globale d'IoC. Mais, je dirais que l' efficacité de l' ID est inférieure à celle d'autres tactiques - comme l'utilisation d'interfaces et d'adaptateurs - de points uniques d'exposition au contexte depuis le module.
Et concentrons-nous vraiment sur cela une seconde. Parce que, même si vous injectez une
Logger
annonce , vous devez écrire du code sur l'Logger
interface. Vous ne pouviez pas commencer à utiliser un nouveauLogger
fournisseur différent qui prend des paramètres dans un ordre différent. Cette capacité provient du codage, au sein du module, d’une interface existante dans le module et comportant un seul sous-module (adaptateur) permettant de gérer la dépendance.Et si vous codez sur un adaptateur, le fait qu’il
Logger
soit injecté dans cet adaptateur ou découvert par celui-ci est en général assez insignifiant pour l’objectif de maintenabilité global. Et plus important encore, si vous avez un adaptateur de niveau module, il est probablement absurde de l'injecter dans n'importe quoi. C'est écrit pour le module.tl; dr - Arrêtez de vous préoccuper des principes sans vous soucier de la raison pour laquelle vous les utilisez. Et, plus concrètement, construisez simplement un
Adapter
module pour chaque module. Utilisez votre jugement lorsque vous décidez où vous tracez les limites du "module". Dans chaque module, allez-y et consultez directement leAdapter
. Et bien sûr, injectez le vrai enregistreur dans leAdapter
- mais pas dans tout ce qui pourrait en avoir besoin.la source
L'idée que l'exploitation forestière doit toujours être implémentée en tant que singleton est un de ces mensonges qui a été dit si souvent qu'il a gagné du terrain.
Depuis que les systèmes d’exploitation modernes existent, il est reconnu que vous voudrez peut-être vous connecter à plusieurs endroits en fonction de la nature de la sortie .
Les concepteurs de systèmes doivent constamment s'interroger sur l'efficacité des solutions antérieures avant de les inclure aveuglément dans de nouvelles solutions. S'ils ne font pas preuve d'une telle diligence, ils ne font pas leur travail.
la source
La journalisation est vraiment un cas particulier.
@Telastyn écrit:
Si vous prévoyez que vous devrez peut-être modifier votre bibliothèque de journalisation, vous devriez utiliser une façade. Soit SLF4J si vous êtes dans le monde Java.
Pour le reste, une bonne bibliothèque de journalisation se charge de changer l’emplacement de la journalisation, les événements filtrés, leur formatage à l’aide des fichiers de configuration du consignateur et (si nécessaire) de classes de plug-in personnalisées. Il existe un certain nombre d'alternatives disponibles dans le commerce.
En bref, ce sont des problèmes résolus ... pour la journalisation ... et il n’est donc pas nécessaire d’utiliser Dependency Injection pour les résoudre.
Le seul cas où l'ID pourrait être bénéfique (par rapport aux approches de journalisation standard) est si vous souhaitez soumettre la journalisation de votre application à des tests unitaires. Cependant, j'imagine que la plupart des développeurs diraient que la journalisation ne fait pas partie des fonctionnalités d'une classe et ne doit pas être testée.
@Telastyn écrit:
Je crains que ce soit une riposte très théorique. En pratique, la plupart des développeurs et intégrateurs de systèmes aiment le fait que vous puissiez configurer la journalisation via un fichier de configuration. Et ils aiment le fait qu'ils ne sont pas censés tester à l'unité la journalisation d'un module.
Bien sûr, si vous compliquez la configuration de la journalisation, vous pouvez avoir des problèmes, mais ils se manifesteront par le fait que l'application échouera au démarrage ou trop ou trop peu de journalisation. 1) Ces problèmes sont facilement résolus en corrigeant l'erreur dans le fichier de configuration. 2) L'alternative consiste en un cycle complet de génération / analyse / test / déploiement à chaque modification des niveaux de journalisation. Ce n'est pas acceptable.
la source
Oui et non !
Oui: Je pense qu'il est raisonnable que différents sous-systèmes (ou couches sémantiques ou bibliothèques ou autres notions de groupement modulaire) acceptent chacun un enregistreur (identique ou potentiellement différent) lors de leur initialisation plutôt que tous les sous-systèmes utilisant le même singleton partagé commun .
cependant,
Non: il est en même temps déraisonnable de paramétrer la journalisation dans chaque petit objet (par constructeur ou méthode d'instance). Pour éviter des pertes de temps inutiles et inutiles, les petites entités doivent utiliser le consignateur unique du contexte qui les entoure.
C'est une raison parmi beaucoup d'autres de penser à la modularité en niveaux: les méthodes sont regroupées dans des classes, tandis que les classes sont regroupées dans des sous-systèmes et / ou des couches sémantiques. Ces paquets plus importants sont de précieux outils d’abstraction; nous devrions donner des considérations différentes dans les limites modulaires que lorsque nous les traversons.
la source
Tout d’abord, cela commence par un cache singleton fort, puis les objets singletons forts de la couche base de données qui introduisent l’état global, des API non descriptives d’
class
es et du code non testable.Si vous décidez de ne pas avoir un singleton pour une base de données, ce n'est probablement pas une bonne idée d'avoir un singleton pour un cache, après tout, ils représentent un concept très similaire, le stockage de données, utilisant uniquement des mécanismes différents.
L'utilisation d'un singleton dans une classe transforme une classe ayant un nombre spécifique de dépendances en une classe ayant théoriquement un nombre infini d'entre elles, car vous ne savez jamais ce qui est réellement caché derrière la méthode statique.
Au cours des dix dernières années, j’ai passé à la programmation. Dans un cas seulement, j’ai assisté à un effort visant à modifier la logique d’exploitation forestière (qui était alors écrit singleton). Donc, bien que j'aime les injections de dépendance, la journalisation n'est vraiment pas une préoccupation majeure. Cache, par contre, je le ferais toujours comme dépendance.
la source
Oui et non, mais surtout non
Je suppose que la majeure partie de la conversation est basée sur une instance statique vs une instance injectée. Personne ne propose que l'enregistrement coupe le SRP, je suppose? Nous parlons principalement du "principe d'inversion de dépendance". Je suis surtout d'accord avec la non-réponse de Telastyn.
Quand est-il possible d'utiliser la statique? Parce qu'il est clair que parfois ça va. Les réponses positives des avantages de l'abstraction et les réponses "non" indiquent qu'elles sont payantes. Une des raisons pour lesquelles votre travail est difficile est qu'il n'y a pas une seule réponse que vous puissiez écrire et appliquer à toutes les situations.
Prendre:
Convert.ToInt32("1")
Je préfère ceci à:
Pourquoi? J'accepte le fait que je devrai refactoriser le code si j'ai besoin de la souplesse nécessaire pour échanger le code de conversion. J'accepte le fait que je ne pourrai pas me moquer de ça. Je crois que la simplicité et la concision valent ce métier.
À l'autre extrémité du spectre, si
IConverter
c'était unSqlConnection
, je serais assez horrifié de voir cela comme un appel statique. Les raisons pour lesquelles l'échec est évident. Je ferais remarquer qu'unSQLConnection
mot peut être assez "transversal" dans une application, de sorte que je n'aurais pas utilisé ces mots exacts.La journalisation ressemble-t-elle davantage à un
SQLConnection
ouConvert.ToInt32
? Je dirais plus comme 'SQLConnection`.Vous devriez vous moquer de la journalisation . Il parle au monde extérieur. Lorsque j'écris une méthode en utilisant
Convert.ToIn32
, je l'utilise comme outil pour calculer une autre sortie de la classe pouvant être testée séparément. Je n'ai pas besoin de vérifier que l'Convert
appel a été correctement effectué en vérifiant que "1" + "2" == "3". La journalisation est différente, c'est une sortie totalement indépendante de la classe. Je suppose que c'est un résultat qui a de la valeur pour vous, l'équipe de support et l'entreprise. Votre classe ne fonctionne pas si la journalisation n'est pas correcte; les tests unitaires ne doivent donc pas réussir. Vous devriez tester ce que vos journaux de classe. Je pense que c'est l'argument tueur, je pourrais vraiment m'arrêter ici.Je pense aussi que c'est quelque chose qui va très probablement changer. Une bonne journalisation n'imprime pas que des chaînes, c'est une vue de ce que fait votre application (je suis un grand fan de la journalisation basée sur les événements). J'ai vu des opérations de journalisation de base se transformer en interfaces de création de rapports très élaborées. Évidemment, il est beaucoup plus facile d'aller dans cette direction si votre exploitation forestière ressemble
_logger.Log(new ApplicationStartingEvent())
et moinsLogger.Log("Application has started")
. On pourrait dire que cela crée un inventaire pour un avenir qui pourrait ne jamais se produire, ceci est un jugement et je pense que cela en vaut la peine.En fait, dans un de mes projets personnels, j’ai créé une interface utilisateur ne se connectant pas à l’enregistrement uniquement à l’aide de l’
_logger
affichage du contenu de l’application. Cela signifiait que je n'avais pas à écrire de code pour comprendre ce que faisait l'application, et je me suis retrouvé avec une journalisation solide. J'ai l'impression que si mon attitude vis-à-vis de l'exploitation forestière était que c'était simple et immuable, cette idée ne me serait pas venue à l'esprit.Je suis donc d'accord avec Telastyn pour le cas de la journalisation.
la source
Semantic Logging Application Block
. Ne l'utilisez pas, comme la plupart du code créé par l'équipe "patterns and practice" de MS, ironiquement, c'est un anti-modèle.Premièrement, les problèmes intersectoriels ne sont pas des éléments constitutifs majeurs et ne doivent pas être traités comme des dépendances dans un système. Un système devrait fonctionner si, par exemple, Logger n’est pas initialisé ou si le cache ne fonctionne pas. Comment allez-vous rendre le système moins couplé et cohésif? C’est là que SOLID entre en scène dans la conception du système OO.
Garder objet en tant que singleton n'a rien à voir avec SOLID. C'est le cycle de vie de votre objet pendant combien de temps vous voulez que l'objet vive en mémoire.
Une classe qui a besoin d'une dépendance pour s'initialiser ne doit pas savoir si l'instance de classe fournie est singleton ou transitoire. Mais tldr; si vous écrivez Logger.Instance.Log () dans chaque méthode ou classe, le code qui pose problème (odeur de code / couplage dur) est vraiment désordonné. C'est le moment où les gens commencent à abuser de SOLID. Et les autres développeurs comme OP commencent à poser de véritables questions comme celle-ci.
la source
J'ai résolu ce problème en utilisant une combinaison d'héritage et de traits (également appelée mixins dans certaines langues). Les traits sont très pratiques pour résoudre ce type de problème transversal. Il s’agit généralement d’une fonctionnalité linguistique, alors je pense que la vraie réponse est qu’elle dépend des fonctionnalités linguistiques.
la source