SonarQube : Clarifications sur S2971

S2971 « IEnumerable » LINQs should be simplified

LINQ a permis de grandement simplifier la recherche d’élément dans des structure complexes.
De nos jours, tout développeur .Net est supposé connaître LINQ car certaines des choses qu’il permet de faire sont couteuses à réaliser sans LINQ.

Par exemple, si j’ai une liste qui contient un ensemble d’objet et que je veux obtenir le premier élément dont la propriété Valeur1 vaut “a” et dont la propriété Valeur2 vaut “b”, je peux le réaliser de plusieurs manières sans LINQ :

private static ClassExemple ExempleBoucle_1(List<ClassExemple> liste)
{
foreach (var item in liste)
{
if (item.Valeur1 == "a" && item.Valeur2 == "b")
{
return item;
}
}
return null;
}
private static ClassExemple ExempleBoucle_2(List<ClassExemple> liste)
{
for (int i = 0; i < liste.Count; i++)
{
var item = liste[i];
if (item.Valeur1 == "a" && item.Valeur2 == "b")
{
return item;
}
}
return null;
}

Les requêtes LINQ pouvant être écrites au format en ligne (inline) ou au format méthode, je peux donc aussi réaliser mon exemple de plusieurs manières avec LINQ:

private static ClassExemple ExempleInline_a(List<ClassExemple> liste)
{
var listeItem = from x in liste
where x.Valeur1 == "a" && x.Valeur2 == "b"
select x;
return listeItem.FirstOrDefault();
}

private static ClassExemple ExempleMethode_a(List<ClassExemple> liste)
{
var item = liste.Where(x => x.Valeur1 == "a" && x.Valeur2 == "b").FirstOrDefault();
return item;
}

SonarQube m’indique que le code que j’ai créé dans la méthode ExempleMethode_a est un code smell :

image

Ce message indique 2 choses :

  • SonarQube permet de détecter les pistes d’améliorations des requêtes basée sur des méthodes
  • SonarQube NE permet PAS de détecter les pistes d’améliorations des requêtes inline

Si j’applique le conseil d’optimisation de SonarQube, je me retrouve avec la méthode suivante :

private static ClassExemple ExempleMethode_b(List<ClassExemple> liste)
{
var item = liste.FirstOrDefault(x => x.Valeur1 == "a" && x.Valeur2 == "b");
return item;
}

Certes, l’écriture en est simplifiée, et donc la maintenance devrait être plus aisée vu que cela fait baisser le nombre d’Halstead, mais quel est l’impact sur le code généré ?
Si on regarde avec IlSpy, on peut voir que la différence est subtile :

image

Le return de ExempleMethode_a fait liste.Where(arg_25_1).FirstOrDefault() alors que le return de ExempleMethode_b fait liste.FirstOrDefault(arg_21_1).
Juste un appel de méthode en plus, rien de bien méchant me direz-vous… sauf, que fait l’appel .Where ?

image

Chaque appel à cette méthode crée un itérateur Where pour le type de données associé et donc, vu que liste.Where(arg_25_1).FirstOrDefault() et liste.FirstOrDefault(arg_25_1) sont sémantiquement identiques, le programme va créer systématiquement créer des itérateurs Where pour rien.

Quoi Faire ?

Face à cette situation, vous avez 2 choix :

  • Désactiver l’avertissement dans SonarQube et accepter la situation (Fortement déconseillé)
  • Supprimer le Where et déplacer la condition dans le FirstOrDefault.