Loupe

C# - comment nommer une variable, un champ privé ou une propriété ?

Mes différentes relectures de PullRequest m'ont fait remarquer qu'il existe beaucoup de manières différentes de nommer des variables. Je suis capable en un seul coup d'oeil de reconnaître une variable créée par Arezki par exemple (mais ma pudeur m'empêche de donner des exemples). Cet article court aura pour vocation de décrire ma façon de faire afin d'échanger ensemble !

Pourquoi c'est important ?

J'ai tendance à voir le code comme une langue très vivante. En écrivant du code, vous écrivez ainsi une lettre à deux personnes : votre compilateur et le prochain développeur qui passera sur votre code (peut être bien même votre moi du futur). Le premier destinataire se moque un peu du nom donné à vos variables (les limitations des premiers temps sont passées et il est beaucoup plus laxiste maintenant) mais le deuxième, humain, compte beaucoup sur vous !

En adoptant les bonnes pratiques, vous gagnerez du temps ! Votre code sera, dans la plupart des cas, relu, manipulé, modifié par d'autres développeurs. En choisissant des noms de variables appropriés on facilite leur travail et on leur fait gagner du temps ! 

Il n'est pas rare de partir dans l'état d'esprit ci-dessous avant de se rendre compte qu'on est soi-même l'auteur du code... autant éviter cette situation...

36hfmx.jpg

Avec de bonnes conventions, il est possible d'exposer au travers du simple nom d'une variable :

  • Ce que stocke une variable : quelle donnée technique est dedans ?
  • Son rôle au sein de l'algorithme : que fait cette variable ici ?
  • La portée de la variable : quel est son cycle de vie ?

Qu'est ce qu'on stocke ?

La première des règles, la plus importante est : nommer votre variable pour exprimer ce qu'elle stocke. On lui donne le nom le plus explicite possible, un point c'est tout :

  • Peu importe la longueur nécessaire, l'espace de stockage pour stocker votre code n'est plus un problème depuis longtemps (on n'écrit plus le code de la NASA pour envoyer des fusées dans l'espace pardi !).  
  • Peu importe si c'est évident, cela ne l'est jamais forcément pour quelqu'un d'autre.
  • Peu importe si cela ne sera jamais modifié / relu : jamais n'existe pas dans le monde du software.
  • Peu importe si c'est ultra technique : et alors ?
  • Peu importe....

Je vais placer une petite citation, étrangement pas toute récente pour notre contexte, mais qui résume extrêmement bien mon idée :
Capture d’écran 2019-07-25 à 14.16.11.png

Aussi, il est intéressant de se rapprocher de l'équipe fonctionnelle pour trouver le nom le plus adapté et proche du fonctionnel. Cela permet de faciliter le dialogue avec le PO et l'équipe fonctionnelle : une fois un terme technique introduit dans une équipe, il n'est pas rare de l'utiliser à tire larigot pour parler de l'objet qu'il représente. Si tout le monde (technique + fonctionnel) utilise le même terme cela nous évite de périlleuses jongleries dans nos cerveaux.

Si on prend comme exemple de code la comparaison de deux tableaux, on évitera au maximum ce type de nommage : 

public static bool IsSameAs(this char[] a, char[] b)
{
    if (a == null || b == null)
    {
        return a == b;
    }
    var l = a.Length;
    if (l != b.Length)
    {
        return false;
    }
    for (int i = 0; i < l; i++)
    {
        var c = a[i];
        var d = b[i];
        if (c != d)
        {
            return false;
        }
    }
    return true;
}

Pour préférer un code avec les conditions de nommage appliquées pour permettre d'identifier rapidement le rôle de chaque variable :

public static bool IsSameAs(this char[] firstArray, char[] secondArray)
{
    if (firstArray == null || secondArray == null)
    {
        return firstArray == secondArray;
    }
    var firstArrayLength = firstArray.Length;
    if (firstArrayLength != secondArray.Length)
    {
        return false;
    }
    for (int i = 0; i < firstArrayLength; i++)
    {
        var firstArrayCurrentItem = firstArray[i];
        var secondArrayCurrentItem = secondArray[i];
        if (firstArrayCurrentItem != secondArrayCurrentItem)
        {
            return false;
        }
    }
    return true;
}

Une exception : les variables itératives des boucles for sont souvent appelées "i" ou "j" et sont souvent laissées telles quelles mais c'est surtout car nous avons l'habitude de les voir sous cette forme. Dans le cas d'algorithmes un peu plus complexes où i a un sens fonctionnel, il ne faut pas hésiter à les renommer elles aussi.

for (int i = 0; i < 10; i++)
{
    // Coucou i
}

facebook_1564180042126.jpg

Pas de précipitation

Nommer une variable n'est pas du tout anodin : il faut bien prendre le temps de choisir correctement le bon nom. Cela peu paraître sur le moment une grosse perte de temps mais au final cela évite bien souvent de mauvaises surprises et incompréhensions bien plus chronophages. 

N'hésitez pas non plus à en parler aux autres développeurs. Je me souviens de débats (endiablés) avec Arnaud pouvant durer plus d'une heure sur le bon choix du nom d'une variable ou de tel ou tel composant. Quelle perte de temps me direz vous ? Et bien non car au final, cela nous a forcés à bien repenser aux spécifications fonctionnelles et à les comprendre beaucoup mieux, beaucoup plus complètement. Ne bâclez pas cet exercice !

meaningful_variable_name.jpg

Pas de précipitation !

Non mais vraiment, prenez votre temps je vous dis !!

Périmètre de la variable et niveau d'accessibilité 

J'ai pour habitude d'indiquer le périmètre d'accès dans mes noms de variables à l'aide du casing et de suffixes très proches des conventions Microsoft :

  • Je préfixe par un underscore (puis camelCasing) si c'est un champ privé : _unChampPrive. Cela me permet aussi de me débarrasser au maximum de l'utilisation du mot clef this que je n'apprécie que peu....
  • Je préfixe par un underscore (puis PascalCasing) si c'est un champ privé static : _UnChampPriveStatic.
  • camelCasing tout bête pour les variables locales : maVariableLocale.
  • Les constantes en publiques en majuscules + underscores : MA_CONSTANTE.
  • Les propriétés en PascalCasing sans préfixe : CeciEstUnePropriete.

Avec ces règles simples, on peut faciliter grandement la lecture du code. Voici un exemple complet appliquant ces conventions : 

public class ArrayComparer
{
    private readonly int _maxLengthConsidered;
    /// <summary>
    /// Do we use IGNORED_CHARACTER as a skipper ?
    /// </summary>
    public bool IgnoreSpecialCharacters { get; set; }
    /// <summary>
    /// Characted to ignore in the comparaison.
    /// </summary>
    public const char IGNORED_CHARACTER = '$';
    public ArrayComparer(int maxLengthConsidered)
    {
        // pas de this. ici \o/
        _maxLengthConsidered = maxLengthConsidered;
    }
    public bool IsSameAs(char[] firstArray, char[] secondArray)
    {
        if (firstArray == null || secondArray == null)
        {
            return firstArray == secondArray;
        }
        var firstArrayLength = firstArray.Length;
        if (firstArrayLength != secondArray.Length)
        {
            return false;
        }
        for (int i = 0; i < firstArrayLength; i++)
        {
            var firstArrayCurrentItem = firstArray[i];
            var secondArrayCurrentItem = secondArray[i];
            if (!IgnoreSpecialCharacters 
           && firstArrayCurrentItem == IGNORED_CHARACTER)
            {
                continue;
            }
            if (firstArrayCurrentItem != secondArrayCurrentItem)
            {
                return false;
            }
        }
        return true;
    }
}

On est pas aidé par les outils ?

Mais bien sûr que si ! Depuis longtemps maintenant, Visual Studio et la plupart des IDEs modernes supportent le format .editorconfig qui permet justement de configurer ces conventions de nommage au niveau d'un projet ou d'une solution. 

Il s'agit de créer un fichier .editorconfig à côté de la solution (ou du projet) et d'insérer les règles souhaitées à l'intérieur. Un avantage de cette méthode est que ces conventions seront présentes sur le contrôleur de code source et donc partagées par tous les membres de l'équipe de développement. On sait à quel point on aime avoir du code propre :).

Pour savoir quoi mettre dans ce fichier, je vous invite à lire la documentation très complète de Microsoft sur le sujet ! En voici un extrait qui permet de demander une capitalization de tous les champs publiques des fichiers C# :

# Public members must be capitalized (public_members_must_be_capitalized)
[*.{cs,vb}]
dotnet_naming_rule.public_members_must_be_capitalized.symbols   = public_symbols
dotnet_naming_symbols.public_symbols.applicable_kinds           = property,method,field,event,delegate
dotnet_naming_symbols.public_symbols.applicable_accessibilities = public
dotnet_naming_symbols.public_symbols.required_modifiers         = readonly

dotnet_naming_rule.public_members_must_be_capitalized.style    = first_word_upper_case_style
dotnet_naming_style.first_word_upper_case_style.capitalization = first_word_upper

dotnet_naming_rule.public_members_must_be_capitalized.severity = suggestion

Avec son exploitation par l'IDE :

editorconfig-naming-rule-suggestion.png

Conclusion 

L'important n'est pas forcément de respecter une seule et unique façon de nommer les différents éléments mais bien de voir en un seul coup d’œil ce que stockent vos variables et la portée de vos éléments de code.

Happy coding !

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus