Stop aux excès! S'il vous plaît, mettez fin à l'utilisation abusive des exceptions de type NotImplementedException.

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 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 ensemble que tous les symptômes présentés dans cet article se trouvaient dans le code!

NotImplementedException

Bon usages

L'origine 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.

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.

Mauvais usages

Utilisation inappropriée

Le premier exemple de mauvais usage est l'emploi de l'exception NotImplementedException en remplacement d'une autre exception qui serait plus appropriée dans le contexte.

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. La fenêtre Todo de Resharper est là heureusement pour nous le rappeler (ainsi qu'un petit test unitaire?).

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 - désolé Barbara.

Cela convient lors de la création du code, mais nécessite une modification avant le déploiement en production !

Nous avons alors 2 choix :

  • on implémente la méthode attendue tout en sachant qu'elle ne sera jamais utilisée ; dans l'exemple précédent cela fonctionne, mais dans le cas où la méthode doit retourner une valeur cela peut prêter à confusion de retourner une valeur quelconque.
  • on lève une exception... mais pas celle là!
throw new InvalidOperationException($"Call to {nameof(SayGoodByeWorld)} is not expected."); // 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

References