Category Archives: Outils

Tirer parti des annotations de ReSharper

Je ne pense pas qu’il soit vraiment nécessaire de présenter ReSharper (souvent abbrégé R#), mais au cas où vous ne connaitriez pas, il s’agit d’un outil créé par JetBrains qui analyse votre code C# ou VB.NET en temps réel pour vous avertir des bugs potentiels, mauvaises pratiques, non-respect des conventions, etc. Il propose aussi de nombreux refactorings et générateurs de code bien utiles. Je l’utilise depuis quelques années maintenant, et cela a beaucoup amélioré ma productivité et la qualité de mon code.

Entre autres choses, R# vous avertit sur l’utilisation incorrecte de méthodes du .NET Framework. Par exemple, si vous appelez Path.GetFullPath avec un chemin qui est peut-être null, il affiche un avertissement :

image

Comment R# sait-il que Path.GetFullPath n’accepte pas d’argument null ? Et d’ailleurs, comment sait-il que Console.ReadLine peut renvoyer null ? Je suppose que ça aurait pu être codé en dur dans l’outil, mais ce ce ne serait pas une approche très élégante, et ne permettrait pas l’extensibilité… En fait, ReSharper utilise des annotations externes. Ce sont des fichiers XML qui sont fournis avec R# et contiennent des métadonnées à propos des classes et méthodes du .NET Framework. Ces données sont utilisées par l’analyseur pour détecter les problèmes potentiels dans votre code.

Soit, mais alors qu’en est-il des bibliothèques tierces ? Evidemment, JetBrains ne peut pas créer les annotations pour celles-ci, il y en a trop. La bonne nouvelle, c’est que vous pouvez écrire vos propres fichiers d’annotations externes pour les bibliothèques que vous utilisez, ou pour votre propre code. Cependant, pour votre code, il y a un moyen beaucoup plus pratique : vous pouvez appliquer les annotations directement dans votre code à l’aide d’attributs. Il y a deux moyens d’obtenir ces attributs :

  • Référencer l’assembly où ils sont définis (JetBrains.Annotations.dll dans le répertoire d’installation de R#). C’est très bien si ça ne vous gêne pas d’avoir une référence vers quelque chose qui n’a rien à voir avec votre application, mais ce n’est probablement pas une bonne idée pour une librairie.
  • Déclarer les attributs dans votre propre code. En fait, il n’est pas nécessaire de les écrire soi-même, car R# a une option pour copier leur implémentation dans le presse-papier, comme illustré ci-dessous. Il suffit ensuite de les coller dans un fichier de code du projet.

image

Maintenant qu’on a les attributs, comment les utilise-t-on ? Je vais présenter quelques unes des annotations les plus fréquemment utilisées.

NotNull

Cette annotation indique que l’élément sur lequel il est appliqué ne peut pas, ou ne doit pas, être null.

Si elle est appliquée sur une méthode ou propriété, cela signifie que celle-ci ne renverra jamais null:

[NotNull]
public string GetString()
{
    return "Hello world!";
}

Quand une méthode a cet attribut, si vous testez que la valeur de retour est null (ou non null), R# vous avertira que la condition est toujours fausse (ou vraie) :

image

 

Si l’annotation est appliquée à un paramètre de méthode, cela signifie que null n’est pas une valeur d’argument valide :

public string Repeat([NotNull] string s)
{
    if (s == null)
        throw new ArgumentNullException("s");
    return s + s;
}

Si R# détermine que la valeur passée pour s peut être null, il vous en avertira, comme illustré dans le premier exemple de cet article.

Cette annotation peut être ajoutée automatiquement en utilisant le menu  “quick fix” de ReSharper. L’option “Not null” va juste ajouter l’annotation; l’option “Check parameter for null” va ajouter l’annotation ainsi qu’une vérification de la valeur:

image

image

 

CanBeNull

C’est le contraire de NotNull. Appliquée à une méthode ou propriété, cette annotation signifie que cette méthode ou propriété peut renvoyer une valeur null. Appliquée à un paramètre, elle signifie que null est une valeur d’argument valide.

Pure

Celle-ci est très utile. Appliquée à une méthode, elle signifie que la méthode est pure. Une méthode pure n’a pas d’effet de bord observable, donc si vous n’utilisez pas la valeur renvoyée par la méthode, l’appel n’a aucun effet utile, c’est donc probablement une erreur. Exemple typique avec String.Replace:

image

 

StringFormatMethod

Cette annotation indique qu’une méthode fonctionne sur le même principe que la méthode String.Format, c’est-à-dire qu’elle prend une chaine de format composite, suivie d’un nombre variable d’arguments qui remplaceront les “trous” dans la chaine de format.

[StringFormatMethod("format")]
public static void Log(string format, params object[] args)
{
    ...
}

Cela permet à R# de vous avertir si les trous et les arguments ne correspondent pas :

image

UsedImplicitly

Celle-ci indique à ReSharper qu’un élément de code est utilisé, même si R# ne peut pas le détecter statiquement. Cela a pour effet d’inhiber l’avertissement qui dit qu’un élément n’est pas utilisé. Cela est utile, par exemple, quand un type ou membre est utilisé uniquement via la réflexion.

NoEnumeration

Cette annotation est appliquée à un paramètre IEnumerable, et signifie que la méthode n’énumèrera pas la séquence. R# donne un avertissement quand un IEnumerable est énuméré plusieurs fois, donc utiliser cet attribut permet d’éviter les faux positifs pour cet avertissement :

public static IEnumerable<T> EmptyIfNull<T>([NoEnumeration] this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

InstantHandle

Celle-ci s’applique à un paramètre de type delegate, et signifie que le delegate sera exécuté uniquement pendant l’exécution de la méthode. Cela permet d’empêcher l’avertissement “Access to modified closure”, qui apparait quand une expression lambda capture une variable qui est modifiée ultérieurement.

ContractAnnotation

Cette annotation est un moyen puissant de décrire les dépendances entre les entrées et la sortie d’une méthode. Cela permet à R# de prédire la façon dont une méthode se comportera. Par exemple, cette méthode renverra null si son argument est null, et non null sinon :

[ContractAnnotation("null => null; notnull => notnull")]
public object Transform(object data)
{
    ...
}

Grâce à cette annotation, ReSharper saura que si l’argument était non null, le résultat ne sera pas null non plus.

La méthode suivante ne se termine pas normalement (elle lance une exception) si son argument est null:

[ContractAnnotation("value:null => halt")]
public static void CheckArgumentNull<T>(
    [NoEnumeration] this T value,
    [InvokerParameterName] string paramName)
    where T : class
{
    if (value == null)
        throw new ArgumentNullException(paramName);
}

Cela permet à R# de savoir que si vous passez null à cette méthode, le code qui suit l’appel ne sera jamais atteint; s’il est atteint, on peut supposer que la valeur n’est pas null.

LocalizationRequired

Cette annotation signifie qu’une propriété ou un paramètre de méthode devrait être localisé; si vous passez une valeur codée en dur, R# vous avertira et suggèrera de l’extraire vers un fichier de ressource.

image11

Conclusion

Maintenant, vous vous demandez peut-être “mais pourquoi est-ce que je me donnerais tant de mal pour ajouter toutes ces annotations à mon code?”. La raison est simple : cela aide ReSharper à vous aider ! En lui donnant plus d’informations sur votre code, vous permettez à R# de vous donner des recommandations plus pertinentes et de produire moins de faux positifs. De plus, si vous être un auteur de bibliothèque, cela améliore l’utilisation de votre bibliothèque pour les utilisateurs de ReSharper. J’utilise beaucoup les annotations R# dans ma bibliothèque Linq.Extras, c’est donc un bon endroit pour trouver d’autres exemples.

Remarquez que je n’ai décrit qu’une petite partie des annotations disponibles. Il y en a beaucoup d’autres, principalement destinées à des scénarios spécifiques à ASP.NET. Vous pouvez les voir dans le fichier d’annotations généré par ReSharper, ou dans la documentation (qui n’est pas tout à fait complète, mais quand même utile).

Intégration avec Visual Studio Online + Git dans Team Explorer

J’ai commencé récemment à utiliser Visual Studio Online pour des projets personnels, et je dois dire que c’est une très bonne plateforme, même si ce serait bien de pouvoir héberger des projets publics et non pas seulement privés. J’apprécie particulièrement l’intégration dans le Team Explorer de Visual Studio pour gérer les tâches et les builds.

Cependant j’ai remarqué un petit bug quand on utilise Git pour le contrôle de version : le remote pour VS Online doit s’appeler origin, sinon Team Explorer ne détecte pas qu’il s’agit d’un projet VS Online, et n’affiche pas les pages “Builds” et “Work Items”.

Quand le remote VSO s'appelle "origin"Quand le remote VSO s'appelle "vso"

C’est clairement un bug (quoique mineur), car le nom origin est juste une convention, et un remote Git peut s’appeler n’importe comment ; je l’ai signalé sur Connect. Si vous rencontrez ce problème, vous pouvez le contourner en renommant le remote en origin :

git remote rename vso origin

Exécuter un outil personnalisé automatiquement quand un fichier est modifié

Aussi loin que je me souvienne, il y a toujours eu dans Visual Studio quelque chose appelé “outils personnalisés” (custom tools), également connus sous le nom de single-file generators. Quand vous appliquez un tel outil à un fichier de votre projet, il génère quelque chose (généralement du code, mais pas forcément) en fonction du contenu du fichier. Par exemple, l’outil personnalisé par défaut pour les fichiers de ressource s’appelle ResXFileCodeGenerator, et génère une classe qui permet d’accéder facilement aux ressources définies dans le fichier resx.

image

Quand vous enregistrez un fichier qui a un outil personnalisé associé, Visual Studio réexécute automatiquement l’outil personnalisé pour regénérer sa sortie. Vous pouvez aussi le faire manuellement, en utilisant la commande “Exécuter l’outil personnalisé” depuis le menu contextuel du fichier dans l’explorateur de solution.

Habituellement, les outils personnalisés ne se basent que sur un fichier d’entrée pour générer leur sortie, mais parfois les choses sont un peu plus complexes. Par exemple, prenons les templates T4 : ils ont un outil personnalisé associé (TextTemplatingFileGenerator), donc cet outil est exécuté quand le template est sauvegardé, mais bien souvent, le template lui-même utilise d’autres fichiers d’entrée pour générer sa sortie. Donc l’outil personnalisé doit être exécuté non seulement quand le template est modifié, mais également quand les fichiers dont il dépend sont modifiés. Puisqu’il n’y a pas de moyen d’indiquer à Visual Studio l’existence de cette dépendance, il faut exécuter l’outil personnalisé manuellement, ce qui est assez agaçant…

Comme j’étais dans cette situation, et que j’en avais assez d’aller exécuter manuellement l’outil personnalisé sur mes templates T4, j’ai finalement créé une extension Visual Studio pour le faire automatiquement : AutoRunCustomTool. Le nom manque un peu d’imagination, mais au moins il est descriptif…

Cet outil est conçu pour être très simple et discret : il fait son travail en silence, sans vous gêner, et vous oubliez très vite qu’il est là. Il ajoute une nouvelle propriété à chaque élément du projet : “Run custom tool on”. Cette propriété est une collection de noms de fichiers pour lesquels l’outil personnalisé doit être exécuté à chaque fois que cet élément de projet est enregistré. Par exemple, si vous avez un template T4 (Template.tt) qui génère un fichier (Output.txt) en fonction du contenu d’un autre fichier (Input.txt), il suffit d’ajouter “Template.tt” à la propriété “Run custom tool on” de Input.txt. A chaque fois que vous enregistrerez Input.txt, l’outil personnalisé sera exécuté automatiquement sur Template.tt, ce qui regénèrera le contenu de Output.txt. Vous trouverez un exemple concret sur la page de l’outil dans la galerie Visual Studio.

J’ai créé AutoRunCustomTool il y a 6 mois environ, mais la version initiale n’était pas très bien dégrossie, donc je n’ai pas communiqué à son sujet. J’ai publié la deuxième version il y a quelques jours, et je pense qu’il est maintenant prêt à être utilisé par tout le monde. Si vous êtes intéressé par le code, vous pouvez le trouver sur GitHub, qui est aussi l’endroit où vous pouvez signaler les problèmes éventuels.