Philosophie du keyword C# out
par sandreo
Il y a quelques semaines de cela j'ai fait tourner Reshaper 2.0 sur mon code afin de supprimer les namespaces non utilisés, variables initialisées inutilement et autres petites choses, résultant de refactorings successifs sans cleanup.
Tout se passe pour le mieux jusqu'à ce que mon collègue qui développe une partie de l'assembly me dit : "Ton outil c'est très bien mais il a changé la sémantique de ma fonction..."|-|
En effet Resharper avait changé ce type de code:
public enum BufferType { Stream, Buffer, Undefined } public void Read(out int length , out BufferType type) { length = 0; type = BufferType.Undefined; Byte[] buff = new Byte[256]; length = this.DoSomething(); if(...) type = BufferType.Stream; else type = BufferType.Buffer; }
en
public void Read(out int length , out BufferType type) { Byte[] buff = new Byte[256]; length = this.DoSomething(); if(...) type = BufferType.Stream; else type = BufferType.Buffer; }
Et oui resharper avait supprimé les 2 affectations en début de méthode. Et là commenca une longue et âpre discussion....
Son argumentation était basée sur le fait que l'utilisation du keyword out obligeait de fournir une valeur de retour correcte dans tous les cas. Donc pour le garantir ca il fallait qu'il initialise ces 2 paramètres dès le début de la méthode. Ceci n'est effectivement pas tout à fait faux.
Mais le compilateur C# vérifie que toutes les branches de code de la méthode vont garantir l'affectations des variables out, donc à priori pas besoin d'affecter d'entrée celles-ci.
Et là tombe l'argument massue! Et oui mais en cas d'erreur, plus précisement la méthode DoSomething lève une exception.... Si tu n'as pas affecté tes variables en début de méthode et bien au sortir tes variables ne le seront pas... Je suis resté dubitatif 88|
Comment peut on imaginer initialiser des variables (toutes out quelles soient) lorsqu'une erreur se produit. Lorsqu'une exception est levée (et qu'elle ne peut pas être traitée dans la méthode) c'est forcément une rupture de contrat. Vouloir transporter des informations grâce aux parametres out pour traiter l'erreur est forcément en opposition avec les bonnes pratiques de gestion d'erreur
Donc pour résumer on laisse le compilo faire son travail et en cas d'erreur c'est à l'appelant de traiter l'erreur et donc d'initialiser les valeurs comme son context le lui indique.
D'autres avis sur la question?
Adresse de trackback pour cet article
7 commentaires
Je suis surpris d'ailleurs que resharper ne rajoute pas des accolades
if(...)
{
type = BufferType.Stream;
}
else
{
type = BufferType.Buffer;
}
}
car ne pas mettre les accolades, ça peut mener à des risques d'erreurs plus tard lorsqu'on va maintenir le code.
concernant le out.
C'est vrai que cela me laisse moi aussi perplexe quand à son intérêt. Mais puisqu'il est là, autant effectivement essayer de dégager de bonnes pratiques d'utilisation.
Supposons que l'on renplace les out par un objet disons Toto contenant les champs type et length. Une exception survient dans DoSomething().
Si une exception survient lorsque l'on appelle une méthode, on ne s'attend pas à obtenir un type de retour valide. D'ailleurs l'exécution du code s'arrêtera là et .NET partira à la recherche d'un catch quand au type de retour il restera non définit.
Pour les même raison je ne vois pas pourquoi cela devrait être différent pour nos valeureux paramètres
s'il y a une exception, c'est que ça n'a pas fonctionné.
En effet, il n'est pas envisageable de lire les paramètres de retour si une exception a été levé. Je rejoins donc Amethyste : "S'il y a une exception, c'est que ça n'a pas fonctionné."
Enfin sur le fait que ces outils soient dangereux je suis mitige, on va dire 50/50 et j"appelle un ami! Sous certains aspects comme le cleanup de namespace, et les varaibles non utilisees c"est assez interessant je trouve. Il me semble que ca ameliore la lisibilite du code et donc la qualite. Mais cela ne veut pas dire qu'il faille prendre son resultat comme une verite ultime. Un code review est toujours le bien venu apres.
Mais bon apres comment fait on de bonnes code review.... ca c"est une autre affaire.
Pour les exceptions je suis bien d"accord avec moi meme et donc avec Amethyse, et heureusement que nous ne sommes pas seul au monde! Mais il reste encore bcp de monde a eduquer.....
- le refactoring (qu'il soit manuel ou automatisé) prend toute sa valeur lorsque le code est couvert par des tests unitaires (typiquement ici un test unitaire qui teste un cas de bordure, qui lève une exception)... Quand on a une bonne couverture de tests, on refactore sans pitié, et on est prévenu quand la sémantique change, car un test ou deux passent au rouge.
- pas de problème pour moi avec le refactoring (qu'il soit manuel ou automatisé, bis!), mais avant tout commit/checkin, un diff et une revue de code sont très précieux.
2 autres petites choses
-Concernant la mise en forme. Il est possible de configurer Resharper pour avoir le retour a la ligne et l'ajout d'accolade quand ca manque. Pour reformater du code qu'on a maintenir, c'est sans prix et ameliore la visibilite en 2 coups de cuillere a pot.
-Concernant le refactoring automatique; j'ai du mal a voir la dangerosite de l'outil (que ca soit couple ou non aux TU): auriez vous un exemple autre que celui presente par sandreo?
Ca fait tout de même plus pro de livrer un code impécable, personne n'accepterai que son plombier ne nettoie pas une fois le boulot terminé, pour les même raisons j'aime bien prendre le temps de nettoyer mon code.
25.11.06 00:33:09, 