Contexte

Une des multiples fonctionnalités de l’extension Resharper pour Visual Studio est une petite fenêtre anodine qui permet d’afficher la liste des TODO d’une solution.

public class Class1 {
    // TODO : Implémenter la méthode...
    public void Test() {
        throw new NotImplementedException();
    }
}

Grâce à cette fenêtre on peux retrouver facilement de la dette technique laissée ça et là.

On observe sur la capture d’écran que Resharper, en plus d’afficher mon fameux TODO, affiche la levée de la NotImplementedException. En effet, par défaut Resharper considère qu’il s’agit là d’une tâche à terminer…

Récemment, alors qu’il était en train de traiter la dette technique sur un projet en utilisant cette fenêtre, un développeur avec lequel je travaille revient vers moi en me disant :

C’est assez ennuyeux car la fenêtre des TODO me retourne une trentaine de messages de type NotImplementedException.

Bryan

En effet, on a pu constater que tous les symptômes présentés dans cet article se trouvaient dans le code.

NotImplementedException

Le seul et unique rôle de cette exception est de permettre à un générateur de code de générer un code compilable et d’indiquer la non-implémentation de la méthode.

OK, un second intérêt est que l’on peut bien sûr aussi l’utiliser pour créer soit même une méthode en attente d’implémentation et compilable sans effort.

Symptômes

Mauvais type d’exception

public int Map(Direction direction) {
	switch (direction) {
		case Direction.Haut:
			return 33;
		case Direction.Droite:
			return 68;
		case Direction.Bas:
			return 12;
		case Direction.Gauche:
			return 8;
		default:
			throw new NotImplementedException();
	}
}

Dans cet exemple la méthode Map est bien implémentée par contre, si l’on se trouvait dans le cas par défaut alors cela voudrait dire que la valeur de l’énumération n’est pas dans la plage de valeurs attendues..

throw new ArgumentOutOfRangeException(nameof(direction), direction, null);

L’exception ci-dessus est bien plus pertinente et c’est d’ailleurs celle-ci qui est proposée par Resharper lorsqu’on lui demande de générer le code du swicth pour nous.

Inadvertance

public class Class1 {

	public void Save(object obj) {
		throw new NotImplementedException();
	}

// ...

Dans cet exemple on a oublié d’implémenter la méthode Save. Avec un peu de chance elle n’est pas utilisée, mais…

Implémentation forcée

public class Talker: ITalkToWorld {

    public void SayHelloWorld() {
        Console.WriteLine("Hello world !");
    }

    public void SayGoodByeWorld() {
        throw new NotImplementedException();
    }

}

Dans cet exemple, nous devons implémenter une classe qui hérite de l’interface ITalkToWorld qui est définie dans un assembly sur lequel nous n’avons pas la main : une librairie système ou librairie tierce.

Par défaut, à la génération automatique du code par Visual Studio, les méthodes SayHelloWorld et SayGoodByeWorld lèvent une NotImplementedException. On implémente alors la première méthode alors que l’on sait pertinemment que la seconde ne sera jamais appelée.

Nous avons alors 2 choix :

  • on implémente la méthode attendue tout en sachant qu’elle ne sera jamais utilisée (mais qui sait)
  • on lève une exception… mais pas celle là
throw new InvalidOperationException($"Call to {nameof(SayGoodByeWorld)} is not valid."); // déjà mieux...

Conséquences

  • Il est difficile de voir la différence entre du code en attente d’implémentation et du code de production
  • Des outils comme celui présenté ci-dessus est pollué par du code non pertinent

Traitement

  • changer le type de l’exception par un type adapté
  • supprimer le code mort
  • implémenter la méthode ou le bloc de code

Bénéfices

  • obtenir des exceptions pertinentes
  • simplifier le code
  • rendre plus lisibles les actions de maintenance du code