La journalisation à côté d'une implémentation est-elle une violation SRP?


19

Lorsque la pensée du développement logiciel agile et tous les principes (SRP, OCP, ...) Je me demande comment l'exploitation forestière plaisir.

La journalisation à côté d'une implémentation est-elle une violation SRP?

yesJ'ai vérifié des motifs et est venu à la conclusion que la meilleure façon de ne pas violer les principes d'une manière définie par l' utilisateur, à utiliser toute forme qui est connu pour violer un principe est d'utiliser un modèle de décorateur.

Disons que nous avons un tas de composants complètement sans violation de SRP et nous voulons ajouter la journalisation.

  • le composant A
  • le composant B utilise un

Nous voulons que l'exploitation forestière A, donc nous créons un autre élément D ornée d'une fois mise en œuvre d'une interface I.

  • interface I
  • composante L (composante identification du système)
  • le composant A implémente I
  • Composant D instruments I, décore / A utilise, L utilise pour l'exploitation forestière
  • le composant B utilise un I

Avantages: - Je peux utiliser une sans vous connecter - test A moyen Je ne pas besoin de se moque d'exploitation - les tests sont plus simples

Inconvénient: - plus de composants et plus de tests

Je sais que cela semble être une autre question ouverte de discussion, mais je veux vraiment savoir si quelqu'un utilise de meilleures stratégies d'exploitation forestière que la violation décorateur ou SRP.



Je l' ai déjà lu et la réponse ne satisfait pas, désolé.


@MarkRogers Merci de partager cet article intéressant. Oncle Bob dit au « code propre », ce beau élément SRP traite avec d' autres éléments sur le même niveau d'abstraction. Pour moi , cette explication est plus facile à comprendre que le contexte peut aussi être trop grand. Mais je ne peux pas répondre à la question, parce que ce qui est le leve contexte ou l' abstraction d'un enregistreur?
Aitch

3
« ne constitue pas une réponse à moi » ou « la réponse ne satisfait pas » est un peu méprisant. Vous pourriez réfléchir à ce qui est spécifiquement insatisfaisant (quelle exigence avez-vous qui n'a pas été satisfaite par cette réponse? Qu'est-ce qui est spécifiquement unique dans votre question?), Puis modifiez votre question pour vous assurer que cette exigence / cet aspect unique est expliqué clairement. Le but est de vous permettre de modifier votre question pour l'améliorer afin de la rendre plus claire et plus ciblée, et non de demander un passe-partout affirmant que votre question est différente / ne devrait pas être fermée sans justification pourquoi. (Vous pouvez également commenter l'autre réponse.)
DW

Réponses:


-1

Oui, il s'agit d'une violation de SRP car la journalisation est une préoccupation transversale.

La bonne façon est de déléguer la journalisation à une classe de journalisation (Interception) dont le seul but est de se conformer au protocole SRP.

Voir ce lien pour un bon exemple: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Voici un petit exemple :

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

Les avantages comprennent

  • Vous pouvez tester cela sans vous connecter - de vrais tests unitaires
  • vous pouvez facilement activer / désactiver la connexion, même lors de l'exécution
  • vous pouvez remplacer la journalisation par d'autres formes de journalisation, sans jamais avoir à modifier le fichier TenantStore.

Merci pour le joli lien. Figure 1 sur cette page est en fait ce que je qualifierais ma solution préférée. La liste des préoccupations transversales (de l' exploitation forestière, la mise en cache, etc.) et un modèle de décorateur est la solution la plus générique et je suis heureux de ne pas être tout à fait mal à mes pensées, bien que la plus grande communauté voudrait abandonner cette abstraction et l' exploitation forestière en ligne .
Aitch

2
Je ne vois pas vous attribuer la part variable de _logger. Envisagiez-vous d'utiliser l'injection de constructeur et vous avez juste oublié? Si c'est le cas, vous obtiendrez probablement un avertissement du compilateur.
user2023861

27
Au lieu de TenantStore étant DIPed avec un enregistreur à usage général, qui nécessite N + 1 classes (lorsque vous ajoutez un LandlordStore, un FooStore, un BarStore, etc.), vous avez un TenantStoreLogger étant DIPed avec un TenantStore, un FooStoreLogger étant DIPed avec un FooStore , etc ... nécessitant des classes 2N. Pour autant que je peux dire, pour bénéficier zéro. Lorsque vous voulez faire des tests unitaires sans l' enregistrement, vous devrez rejigger N des classes, au lieu de simplement la configuration d' un NullLogger. OMI, c'est une très mauvaise approche.
user949300

6
Faire cela pour chaque classe nécessitant une journalisation augmente considérablement la complexité de votre base de code (à moins que si peu de classes aient une journalisation que vous ne pourriez même plus appeler cela une préoccupation transversale). En fin de compte, cela rend le code moins maintenable simplement en raison du grand nombre d'interfaces à maintenir, ce qui va à l'encontre de tout ce pour quoi le principe de responsabilité unique a été créé.
jpmc26

9
Voté. Vous avez supprimé le problème de l' exploitation forestière de la classe des locataires, mais maintenant votre TenantStoreLoggerchange à chaque TenantStorechangement. Vous n'êtes pas séparer les préoccupations plus que dans la solution initiale.
Laurent LA RIZZA

61

Je dirais que vous prenez SRP beaucoup trop au sérieux. Si votre code est suffisamment ordonné pour que la journalisation soit la seule "violation" de SRP, vous réussissez mieux que 99% de tous les autres programmeurs, et vous devriez vous féliciter.

Le point de SRP est d'éviter le code horrible spaghetti où le code qui fait des choses différentes sont brouillées ensemble. l'exploitation forestière de mélange avec le code fonctionnel ne sonne pas la sonnette d'alarme pour moi.


19
@Aitch: Vos choix sont de câbler la connexion à votre classe, de passer une poignée à un enregistreur ou de ne rien enregistrer du tout. Si vous allez être ultra-strict sur le SRP au détriment de tout le reste, je vous recommande de ne rien enregistrer, jamais. Tout ce que vous devez savoir sur ce que votre logiciel fait peut être noodled avec un débogueur. Le P SRP signifie « principe » et non « loi physique de la nature qui ne doit jamais être rompu. »
Blrfl

3
@Aitch: Vous devriez être en mesure de retracer l' exploitation forestière dans le dos de la classe à une certaine exigence, sinon vous violez YAGNI. Si l' enregistrement est sur la table, vous fournissez dans une poignée d'enregistreur valide comme vous le feriez pour tout autre chose les besoins de la classe, de préférence un d'une classe qui a déjà passé les tests. Que ce soit celui qui produit les entrées du journal réels ou les décharges dans le seau de bits est la préoccupation de ce instancié l'instance de votre classe; la classe elle - même ne devrait pas prendre soin.
Blrfl

3
@Aitch Pour répondre à votre question sur les tests unitaires:, c'est PRÉCISÉMENT Do you mock the logger?ce que vous faites. Vous devriez avoir une ILoggerinterface qui définit ce que l'enregistreur ne. Le code sous test s'injecté avec un ILoggerque vous spécifiez. Pour les tests, vous avez class TestLogger : ILogger. La grande chose à ce sujet est la TestLoggerpossibilité d'exposer des choses comme la dernière chaîne ou l'erreur enregistrée. Les tests peuvent vérifier que le code en cours de test est l' exploitation forestière correctement. Par exemple, un test pourrait être UserSignInTimeGetsLogged(), où le test vérifie TestLoggerle journal.
CurtisHx

5
99% semble un faible débit binaire. Vous êtes probablement mieux que 100% de tous les programmeurs.
Paul Draper

2
+1 pour raison. Nous avons besoin de plus de ce genre de réflexion: moins l' accent sur les mots et les principes abstraits et plus se concentrer sur d' avoir une base de code maintenable .
jpmc26

15

Non, ce n'est pas une violation de SRP.

Les messages que vous envoyez dans le journal devrait changer pour les mêmes raisons que le code environnant.

Ce qui est une violation du SRP utilise une bibliothèque spécifique pour se connecter directement dans le code. Si vous décidez de changer le mode de l'exploitation forestière, SRP affirme qu'il ne devrait pas influer sur votre code d'entreprise.

Une sorte de résumé Loggerdoit être accessible à votre code de mise en œuvre, et la seule chose que votre mise en œuvre devrait dire est « Envoyer ce message au journal », sans préoccupations wrt comment faire. Décider de la façon exacte de l' exploitation forestière (même timestamping) n'est pas la responsabilité de votre mise en œuvre.

Votre mise en œuvre doit alors également savoir pas si l'enregistreur , il envoie des messages à un NullLogger.

Cela dit.

Je n'abandonnerais pas trop vite l'abattage comme une préoccupation transversale . Journaux pour retrouver la trace émettant des événements spécifiques qui se produisent dans votre code de mise en œuvre appartient au code de mise en œuvre.

Ce qui est une préoccupation transversale, OTOH, est l' exécution de traçage : l' exploitation forestière entre et sort dans chaque méthode. L'AOP est le mieux placé pour le faire.


Disons que le message de l' enregistreur est « connexion utilisateur xyz », qui est envoyé à un enregistreur qui prepends un horodatage , etc. Savez - vous ce que « login » des moyens à la mise en œuvre? Commence-t-il une session avec un cookie ou tout autre mécanisme? Je pense qu'il existe de nombreuses façons d'implémenter une connexion, donc changer l'implémentation n'a logiquement rien à voir avec le fait qu'un utilisateur se connecte. C'est un autre excellent exemple de décoration de différents composants (par exemple OAuthLogin, SessionLogin, BasicAuthorizationLogin) faisant la même chose comme une Logininterface décorée avec le même enregistreur.
Aitch

Cela dépend de la signification du message "connexion utilisateur xyz". Si elle marque le fait qu'une connexion est réussie, l'envoi du message dans le journal appartient à l'utilisation de connexion cas. La façon spécifique pour représenter les informations de connexion en tant que chaîne (OAuth, session, LDAP, NTLM, empreintes digitales, roue de hamster) appartient à la classe spécifique représentant les informations d' identification ou la stratégie de connexion. Il n'y a aucun besoin impérieux de le supprimer. Ce cas spécifique n'est pas une préoccupation transversale. Il est spécifique au cas d'utilisation de la connexion.
Laurent LA RIZZA

7

Comme l'exploitation forestière est souvent considérée comme un problème transversal je suggère d'utiliser AOP pour séparer l'exploitation de la mise en œuvre.

Selon le langage, vous utiliseriez un intercepteur ou un cadre AOP (par exemple AspectJ en Java) pour effectuer cela.

La question est de savoir si cela en vaut vraiment la peine. Notez que cette séparation augmentera la complexité de votre projet tout en offrant très peu d'avantages.


2
La plupart du code AOP que j'ai vu concernait la journalisation de chaque étape d'entrée et de sortie de chaque méthode. Je souhaite uniquement enregistrer certaines parties de la logique métier. Il est donc peut-être possible de consigner uniquement des méthodes annotées, mais AOP ne peut exister que dans les langages de script et les environnements de machines virtuelles, non? En C ++ par exemple, c'est impossible. J'avoue que je ne suis pas très satisfait des approches AOP, mais il n'y a peut-être pas de solution plus propre.
Aitch

1
@Aitch. "C ++ c'est impossible." : Si vous recherchez "aop c ++" sur Google, vous trouverez des articles à ce sujet. "... le code AOP que j'ai vu concernait la journalisation de chaque étape d'entrée et de sortie de chaque méthode. Je veux seulement journaliser certaines parties de la logique métier." Aop vous permet de définir des modèles pour trouver les méthodes à modifier. c'est-à-dire toutes les méthodes de l'espace de noms "my.busininess. *"
k3b

1
La journalisation n'est souvent PAS une préoccupation transversale, en particulier lorsque vous souhaitez que votre journal contienne des informations intéressantes, c'est-à-dire plus d'informations que celles contenues dans une trace de pile d'exceptions.
Laurent LA RIZZA

5

Cette belle sons. Vous décrivez un décorateur d'exploitation forestière assez standard. Vous avez:

composante L (composante identification du système)

Cela a une responsabilité: consigner les informations qui lui sont transmises.

le composant A implémente I

Cela a une responsabilité: fournir une implémentation de l'interface I (en supposant que I soit correctement compatible SRP, c'est-à-dire).

Ceci est la partie cruciale:

Composant D instruments I, décore / A utilise, L utilise pour l'exploitation forestière

Lorsque cela est dit de cette façon, cela semble complexe, mais regardez-le de cette façon: le composant D fait une chose: rapprocher A et L.

  • Le composant D ne se connecte pas; il délègue à ce que L
  • Composante D ne met pas en œuvre elle-même je; il délègue cela à un

La seule responsabilité du composant D est de s'assurer que L est notifié lorsque A est utilisé. Les implémentations de A et L sont toutes deux ailleurs. Ceci est complètement conforme à SRP, en plus d'être un exemple soigné d'OCP et une utilisation assez courante des décorateurs.

Une mise en garde importante: lorsque D utilise votre composant de journalisation L, il doit le faire de manière à vous permettre de modifier la façon dont vous vous connectez. La façon la plus simple de le faire est d'avoir une interface IL implémentée par L. Ensuite:

  • Le composant D utilise un IL pour se connecter; une instance de L est fournie
  • Le composant D utilise un I pour fournir des fonctionnalités; une instance de A est fournie
  • Le composant B utilise un I; une instance de D est fournie

De cette façon, rien ne dépend directement d'autre chose, ce qui facilite leur échange. Il est ainsi facile de s'adapter aux changements et de se moquer facilement des parties du système afin que vous puissiez effectuer des tests unitaires.


En fait, je ne connais que C # qui prend en charge la délégation native. C'est pourquoi j'ai écrit D implements I. Merci pour votre réponse.
Aitch

1

Bien sûr, c'est une violation de SRP car vous avez une préoccupation transversale. Vous pouvez cependant créer une classe chargée de composer la journalisation avec l'exécution de toute action.

exemple:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}

2
Voté. L'exploitation forestière est en effet une préoccupation transversale. Il en va de même pour les appels de méthode de séquencement dans votre code. Ce n'est pas une raison suffisante pour prétendre à une violation de SRP. La consignation de l'occurrence d'un événement spécifique dans votre application n'est PAS une préoccupation transversale. La FAÇON dont ces messages sont transmis à tout utilisateur intéressé est en effet une préoccupation distincte, et la description de cela dans le code d'implémentation EST une violation de SRP.
Laurent LA RIZZA

les «appels de méthode de séquençage» ou la composition fonctionnelle n'est pas une préoccupation transversale mais plutôt un détail de mise en œuvre. La responsabilité de la fonction que j'ai créée est de composer une instruction de journal, avec une action. Je n'ai pas besoin d'utiliser le mot "et" pour décrire ce que fait cette fonction.
Paul Nikonowicz

Ce n'est pas un détail d'implémentation. Cela a un effet profond sur la forme de votre code.
Laurent LA RIZZA

Je pense que je regarde SRP du point de vue de "QUE FAIT cette fonction" où alors que vous regardez SRP du point de vue de "COMMENT cette fonction le fait-elle".
Paul Nikonowicz
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.