Meilleure pratique pour marquer une méthode appelée par réflexion?

11

Notre logiciel a plusieurs classes qui devraient être trouvées dynamiquement via la réflexion. Les classes ont toutes un constructeur avec une signature spécifique via laquelle le code de réflexion instancie les objets.
Cependant, lorsque quelqu'un vérifie si la méthode est référencée (par exemple via Visual studio Code Lens), la référence via réflexion n'est pas comptée. Les gens peuvent manquer leurs références et supprimer (ou modifier) ​​des méthodes apparemment inutilisées.

Comment marquer / documenter les méthodes destinées à être appelées par réflexion?

Idéalement, la méthode devrait être marquée de telle manière que les collègues et Visual Studio / Roslyn et d'autres outils automatisés «voient» que la méthode est destinée à être appelée via la réflexion.

Je connais deux options que nous pouvons utiliser, mais les deux ne sont pas tout à fait satisfaisantes. Étant donné que Visual Studio ne peut pas trouver les références:

  • Utilisez un attribut personnalisé et marquez le constructeur avec cet attribut.
    • Un problème est que les propriétés d'attribut ne peuvent pas être une référence de méthode, par conséquent le constructeur affichera toujours comme ayant 0 références.
    • Les collègues qui ne connaissent pas l'attribut personnalisé l'ignoreront probablement.
    • Un avantage de mon approche actuelle est que la partie réflexion peut utiliser l'attribut pour trouver le constructeur qu'elle doit appeler.
  • Utilisez des commentaires pour documenter qu'une méthode / constructeur doit être appelé via la réflexion.
    • Les outils automatisés ignorent les commentaires (et les collègues peuvent également le faire).
    • Les commentaires de documentation Xml peuvent être utilisés pour que Visual Studio compte une référence supplémentaire à la méthode / constructeur:
      Soit MyPluginla classe dont le constructeur à invoquer via la réflexion. Supposons que le code de réflexion appelant recherche les constructeurs qui prennent un intparamètre. La documentation suivante fait que la lentille de code montre le constructeur ayant 1 référence:
      /// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection

Quelles meilleures options existent?
Quelle est la meilleure pratique pour marquer une méthode / un constructeur qui doit être appelé via la réflexion?

Kasper van den Berg
la source
Juste pour être clair, c'est pour une sorte de système de plugin, non?
whatsisname
2
Vous supposez que vos collègues vont ignorer ou manquer tout ce que vous faites ... Vous ne pouvez pas empêcher le code d'une telle inefficacité au travail. Documenter me semble le moyen le plus simple, le plus propre, le moins cher et le plus conseillé. Sinon n'existerait pas la programmation déclarative.
Laiv
1
Resharper a l'attribut [UsedImplictly].
CodesInChaos
4
Je suppose que l'option de commentaire de doc Xml est probablement votre meilleure option. Il est court, auto-documenté et n'a pas besoin de "hacks" ou de définitions supplémentaires.
Doc Brown du
2
Un autre vote pour les commentaires de la documentation xml. Si vous créez quand même de la documentation, elle devrait ressortir dans la documentation générée.
Frank Hileman

Réponses:

12

Une combinaison des solutions suggérées:

  • Utilisez des balises de documentation XML pour documenter que le constructeur / la méthode est appelée via la réflexion.
    Cela devrait clarifier l'utilisation prévue pour les collègues (et mon futur moi-même).
  • Utilisez l'astuce via le <see>-tag pour augmenter le nombre de références pour le constructeur / méthode.
    Cela rend cette lentille de code et les références de recherche montrent que le constructeur / méthode est référencé.
  • Annoter avec Resharper's UsedImplicitlyAttribute
    • Resharper est un standard de facto et [UsedImplicitly]a précisément la sémantique voulue.
    • Ceux qui n'utilisent pas Resharper peuvent installer les annotations JetBrains ReSharper via NuGet:
      PM> Install-Package JetBrains.Annotations.
  • S'il s'agit d'une méthode privée et que vous utilisez l'analyse de code de Visual Studio, utilisez SupressMessageAttributepour le message CA1811: Avoid uncalled private code.

Par exemple:

class MyPlugin
{
    /// <remarks>
    /// <see cref="MyPlugin.MyPlugin(int)"/> is called via reflection.
    /// </remarks>
    [JetBrains.Annotations.UsedImplicitly]
    public MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }

    /// <remarks>
    /// <see cref="MyPlugin.MyPlugin(string)"/> is called via reflection.
    /// </remarks>
    [JetBrains.Annotations.UsedImplicitly]
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(string arg)
    {
        throw new NotImplementedException();
    }
}

La solution confère l'utilisation prévue du constructeur aux lecteurs humains et aux 3 systèmes d'analyse de code statique les plus utilisés avec C # et Visual Studio.
L'inconvénient est qu'un commentaire et une ou deux annotations peuvent sembler un peu redondants.

Kasper van den Berg
la source
Notez qu'il y a aussi MeansImplicitUseAttributequi peut être utilisé pour créer vos propres attributs qui ont un UsedImplicitlyeffet. Cela peut réduire beaucoup de bruit d'attribut dans les bonnes situations.
Dave Cousineau
Le lien vers JetBrains est rompu.
John Zabroski
5

Je n'ai jamais eu ce problème dans un projet .Net, mais j'ai régulièrement le même problème avec les projets Java. Mon approche habituelle consiste à utiliser l' @SuppressWarnings("unused")annotation en ajoutant un commentaire expliquant pourquoi aussi). Cela a l'avantage de garantir automatiquement que les outils d'analyse statique sont conscients que le code n'est pas censé avoir des références directes et de donner une raison détaillée aux lecteurs humains.

L'équivalent C # de Java @SuppressWarningsest SuppressMessageAttribute. Pour les méthodes privées , vous pouvez utiliser le message CA1811: Évitez le code privé non appelé ; par exemple:

class MyPlugin
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }
}
Jules
la source
(Je ne sais pas, mais présumez que la CLI prend en charge un attribut similaire à celui de Java que je mentionne; si quelqu'un sait de quoi il s'agit, veuillez modifier ma réponse comme référence ...)
Jules
stackoverflow.com/q/10926385/5934037 ressemble à ça.
Laiv
1
J'ai découvert récemment que réaliser des tests et mesurer la couverture (en java) est un bon moyen de savoir si un bloc de code est vraiment inutilisé. Ensuite, je peux le supprimer ou chercher pourquoi est inutilisé (si j'attends le contraire). Pendant la recherche, c'est quand je fais plus attention aux commentaires.
Laiv
Il existe le SuppressMessageAttribute( msdn.microsoft.com/en-us/library/... ). Le message le plus proche est CA1811: Avoid uncalled private code( msdn.microsoft.com/en-us/library/ms182264.aspx ); Je n'ai pas encore trouvé de message pour le code public.
Kasper van den Berg, le
2

Une alternative à la documentation serait d'avoir des tests unitaires pour s'assurer que les appels de réflexion s'exécutent correctement.

De cette façon, si quelqu'un modifie ou supprime les méthodes, votre processus de génération / test devrait vous alerter que vous avez cassé quelque chose.

Nick Williams
la source
0

Eh bien, sans voir votre code, cela ressemble à ceci serait un bon endroit pour introduire un héritage. Peut-être une méthode virtuelle ou abstraite que le constructeur de ces classes peut appeler? Si vous êtes la méthode que vous essayez de marquer est juste le constructeur, alors vous essayez vraiment de marquer une classe et non une méthode ouais? Quelque chose que j'ai fait dans le passé pour marquer les classes est de créer une interface vide. Ensuite, les outils d'inspection de code et la refactorisation peuvent rechercher des classes qui implémentent l'interface.

Jeff Sall
la source
Le véritable héritage serait normalement la voie à suivre. Et les classes sont en effet liées par héritage. Mais en créant une nouvelle instance au-delà de l'héritage. En outre, je compte sur «l'héritage» des méthodes statiques, et puisque C # ne prend pas en charge les emplacements de classe; il y a un moyen de contourner, que j'utilise, mais qui dépasse la portée de ce commentaire.
Kasper van den Berg le