Qualité de code — Nullable, Analyseurs, StyleCop
Notions théoriques
Nullable Reference Types (C# 8+)
Avant C# 8, toute variable de type référence (string, classe...) pouvait implicitement valoir null, sans que le compilateur ne vous avertisse. Le résultat : des NullReferenceException en production.
Depuis C# 8, les Nullable Reference Types permettent de distinguer :
string: ne peut jamais être null (le compilateur le garantit)string?: peut être null (vous devez le vérifier explicitement)
Pour activer cette fonctionnalité dans tout le projet :
<!-- MonProjet.csproj -->
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
Ou fichier par fichier :
#nullable enable
public class Personnage
{
public string Nom { get; set; } = ""; // jamais null
public string? Description { get; set; } // peut être null
}
Opérateurs liés aux nulls
| Opérateur | Nom | Exemple |
|---|---|---|
?. | Déréférencement conditionnel | personnage?.Nom (retourne null si personnage est null) |
?? | Coalescence nulle | personnage?.Nom ?? "Inconnu" (valeur par défaut si null) |
??= | Assignation si null | description ??= "Aucune description"; |
! | Null-forgiving | string nom = personnage!.Nom; (supprime le warning) |
L'opérateur ! (null-forgiving) doit être utilisé avec parcimonie : il dit au compilateur "je garantis que ce n'est pas null", mais si vous avez tort, vous aurez quand même une NullReferenceException à l'exécution.
Roslyn Analyzers
Les Roslyn Analyzers sont des outils d'analyse statique intégrés à la chaîne de compilation .NET. En .NET 8, un ensemble d'analyseurs est activé par défaut et signale des problèmes de :
- Style : nommage des variables, propriétés, méthodes
- Performance :
stringconcaténation dans une boucle,StringBuilderrecommandé - Fiabilité :
awaitoublié,usingmanquant sur lesIDisposable
Ces warnings apparaissent dans Visual Studio et dans la sortie de dotnet build.
StyleCop
StyleCop.Analyzers applique un ensemble de règles de style de code (nommage, espaces, documentation XML). Il se configure via un fichier .editorconfig.
Installation :
dotnet add package StyleCop.Analyzers
Exemple de règles StyleCop courantes :
- Toutes les méthodes publiques doivent avoir un commentaire XML (
///) - Les
usingdoivent être à l'intérieur du namespace - Les accolades ouvrantes doivent être sur une nouvelle ligne
SonarAnalyzer.CSharp
SonarAnalyzer.CSharp détecte les bugs potentiels, les vulnérabilités de sécurité et les "code smells" (mauvaises pratiques). Il est distribué via NuGet.
dotnet add package SonarAnalyzer.CSharp
dotnet format
La commande dotnet format applique automatiquement les règles de formatage définies dans .editorconfig :
dotnet format # Formater tout le projet
dotnet format --verify-no-changes # Vérifier sans modifier (pour la CI)
.editorconfig
Le fichier .editorconfig à la racine du projet définit les règles de style partagées par toute l'équipe :
[*.cs]
# Indentation
indent_style = space
indent_size = 4
# Nommage
dotnet_naming_rule.private_fields_should_be_camel_case.severity = warning
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
# Nullable
dotnet_diagnostic.CS8600.severity = error # Conversion possible de null
dotnet_diagnostic.CS8602.severity = error # Déréférencement possible d'une référence null
Exemple pratique
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
public class Personnage
{
public int Id { get; set; }
public string Nom { get; set; } = ""; // jamais null
public string? Description { get; set; } // peut être null
public int PointsDeVie { get; private set; } = 100;
// Méthode qui retourne null si le personnage est mort
public string? ObtenirStatut()
{
if (PointsDeVie <= 0)
return null; // null explicitement autorisé par string?
return $"{Nom} est vivant ({PointsDeVie} PDV)";
}
}
class Program
{
static void Main()
{
var personnages = new List<Personnage>
{
new Personnage { Id = 1, Nom = "Aragorn", Description = "Roi du Gondor" },
new Personnage { Id = 2, Nom = "Gandalf" }, // Description = null
};
foreach (var p in personnages)
{
// ?. : évite NullReferenceException si Description est null
int longueur = p.Description?.Length ?? 0;
Console.WriteLine($"{p.Nom} — Description : {longueur} caractères");
// ?? : valeur par défaut si null
string statut = p.ObtenirStatut() ?? "Décédé";
Console.WriteLine($" Statut : {statut}");
}
// ??= : initialisation paresseuse
var premierPersonnage = personnages.FirstOrDefault();
premierPersonnage!.Description ??= "Aucune description disponible";
Console.WriteLine($"\n{premierPersonnage.Nom} : {premierPersonnage.Description}");
}
}
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Dans ce TP, vous allez activer les Nullable Reference Types sur le projet RPG et corriger les avertissements du compilateur.
Étape 1 — Activer Nullable dans le fichier de projet
Activez la fonctionnalité Nullable Reference Types en modifiant le fichier .csproj.
Activer <Nullable>enable</Nullable> sur un projet existant fait souvent apparaître des dizaines de warnings. Il est beaucoup plus simple de l'activer dès la création du projet et de traiter les cas un par un au fur et à mesure. Sur un projet existant, activez-le progressivement fichier par fichier avec #nullable enable.
Étape 2 — Corriger les propriétés non nullables
Après activation, certaines propriétés string sans valeur par défaut génèrent un warning. Corrigez la classe Personnage.
Chaque propriété doit signaler clairement son comportement : string Nom = l'appelant peut compter sur une valeur non-null ; string? Description = l'appelant doit vérifier avant d'utiliser. Cette distinction rend les contrats de vos classes explicites et réduit les bugs.
Étape 3 — Utiliser l'opérateur ?? pour une valeur par défaut
Dans une méthode d'affichage, utilisez ?? pour gérer le cas où Description est null.
Dès qu'une propriété est déclarée string?, son utilisation doit être encadrée par ?., ?? ou un test explicite if (Description != null). Ne supprimez pas le warning avec ! sauf si vous êtes certain à 100% que la valeur n'est pas null à cet endroit du code.