Les propriétés de dépendance et les propriétés jointes sous WPF (article à télécharger) 31. mars 2009 Olivier C#, Design, Silverlight, WPF (1) En voilà un beau sujet ! Vous allez me dire qui irait investir deux jours à taper 25 pages sur ce su [Plus]
Visual NDepend un outil d'analyse de code original et puissant 9. janvier 2009 Olivier C#, Méthodologie (0) A partir d'une certaine taille le code d'une application devient difficile à maintenir même s'il est bien conçu. De même, la maintenance évolutive oblige souvent à mettre les mains dans un code écrit par d'autres (si ce code est assez ancien, on peut s'y perdre même en en étant l'auteur d'ailleurs !). Auditer un code est ainsi un besoin fréquent dans le travail de tous les jours d'un développeur. Si on effectue des missions d'audit ce besoin devient alors impérieux car il est nécessaire d'entrer rapidement dans un code "étranger" et toujours complexe (je n'ai jamais été appelé en audit pour des logiciels de 20 lignes...). Les outils d'analyse de code sont ainsi des compagnons indispensables. Hélas ils ne sont pas si nombreux que ça et comme tout ce qui touche la qualité, ils sont un peu "oubliés". Je voulais ainsi vous présenter un excellent outil : Visual NDepend. NDepend est puissant, il permet de construire et visualiser des comparaisons, d'auditer la qualité du code, et surtout de mieux visualiser la structure d'une application. Le tout quantitavement (métriques), qualitativement (l'information est pertinente) et graphiquement (le logiciel propose de nombreux diagrammes et schémas permettant de visualiser ce qui est invisible en lisant le code source : structures, dépendances, ...). NDepend ne se limite pas seulement à l'audit de code, c'est aussi un excellent outil de refactoring. On notera aussi, côté puissance, l'intégration d'un langage d'interrogation, le CQL (Code Query Language), permettant à la façon de SQL de "questionner" un projet et son code comme s'il s'agissait d'une base de données. La comparaison s'arrête là avec SQL car CQL "comprend" le code alors que SQL ne comprend pas le sens des données qu'il manipule. Cette grande différence confère à NDepend une très grande souplesse et beaucoup d'intelligence. Par exemple il est très simple de faire des requêtes du type "montre moi toutes les méthodes privées dont le nombre de lignes de code est supérieur à 20", très utile pour repérer rapidement dans un projet les lourdeurs ou la nécessité de refactoring. Une telle interrogation se formule en la requête CQL suivante "SELECT METHODS WHERE NbLinesOfCode > 20 AND IsPrivate". On peut bien entendu créer des requêtes plus sophistiquées comme rechercher toutes les types qui sont une définition de classe et qui implémentent telle ou telle autre interface ! NDepend est ainsi un outil de grande qualité dont on ne peut que conseiller l'utilisation. Réalisé par Patrick Smacchia, un acteur connu de la scène .NET et Microsoft MVP C#, NDepend est vendu 299 euros en licence 1 utilisateur, c'est à dire pas grand chose au regard des immenses services qu'il peut rendre. Il y a beaucoup de choses à dire sur cet outil et surtout à voir. Le mieux si vous êtes intéressés est de vous rendre sur le site du logiciel www.ndepend.com où vous pourrez visionner de nombreuses petites vidéos qui vaudront mieux que de longs discours. Bon refactoring !
MEF - Managed Extensibility Framework - De la magie est des plugins ! 8. novembre 2008 Olivier C#, Framework .NET, Méthodologie (4) Tutor sur Managed Extensibility Framework qui permet de créer des plugins sous .NET [Plus]
Contourner le problème de l'appel d'une méthode virtuelle dans un constructeur 5. novembre 2008 Olivier C#, Méthodologie (2) Ce problème est source de bogues bien sournois. J'ai déjà eu l'occasion de vous en parler dans un billet cet été (Appel d'un membre virtuel dans le constructeur ou "quand C# devient vicieux". A lire absolument...), la conclusion était qu'il ne faut tout simplement pas utiliser cette construction dangereuse. Je proposais alors de déplacer toutes les initialisations dans le constructeur de la classe parent, mais bien entendu cela n'est pas applicable tout le temps (sinon à quoi servirait l'héritage). Dès lors comment proposer une méthode fiable et systématique pour contourner le problème proprement ? Rappel Je renvoie le lecteur au billet que j'évoque en introduction pour éviter de me répéter, le problème posé y est clairement démontré. Pour les paresseux du clic, voici en gros le résumé de la situation : L'appel d'une méthode virtuelle dans le constructeur d'une classe est fortement déconseillé. La raison : lorsque qu'une instance d'une classe dérivée est créée elle commence par appeler le constructeur de son parent (et ainsi de suite en cascade remontante). Si ce constructeur parent appelle une méthode virtuelle overridée dans la classe enfant, le problème est que l'instance enfant elle-même n'est pas encore initialisée, l'initialisation se trouvant encore dans le code du constructeur parent. Et cela, comme vous pouvez l'imaginer, ça sent le bug ! Une première solution La seule et unique solution propre est donc de s'interdire d'appeler des méthodes virtuelles dans un constructeur. Et je serai même plus extrémiste : il faut s'interdire d'appeler toute méthode dans le constructeur d'une classe, tout simplement parce qu'une méthode non virtuelle, allez savoir, peut, au gréé des changements d'un code, devenir virtuelle un jour. Ce n'est pas quelque chose de souhaitable d'un pur point de vue méthodologique, mais nous savons tous qu'entre la théorie et la pratique il y a un monde... Tout cela est bien joli mais s'il y a des appels à des méthodes c'est qu'elles servent à quelque chose, s'en passer totalement semble pour le coup tellement extrême qu'on se demande si ça vaut encore le coup de développper ! Bien entendu il existe une façon de contourner le problème : il suffit de créer une méthode publique "Init()" qui elle peut faire ce qu'elle veut. Charge à celui qui créé l'instance d'appeler dans la foulée cette dernière pour compléter l'initialisation de l'objet. Le code suivant montre une telle construction : // Classe Parent public class Parent2 { public Parent2(int valeur) { // MethodeVirtuelle(); } public virtual void Init() { MethodeVirtuelle(); } public virtual void MethodeVirtuelle() { } } // Classe dérivée public class Enfant2 : Parent2 { private int val; public Enfant2(int valeur) : base(valeur) { val = valeur; } public override void MethodeVirtuelle() { Console.WriteLine("Classe Enfant2. champ val = " + val); } } La méthode virtuelle est appelée dans Init() et le constructeur de la classe de base n'appelle plus aucune méthode. C'est bien. Mais cela complique un peu l'utilisation des classes. En effet, désormais, pour créer une instance de la classe Enfant2 il faut procéder comme suit : // Méthode 2 : avec init séparé var enfant2 = new Enfant2(10); enfant2.Init(); // affichera 10 Et là, même si nous avons réglé un problème de conception essentiel, côté pratique nous sommes loin du compte ! Le pire c'est bien entendu que nous obligeons les utilisateurs de la classe Enfant2 à "penser à appeler Init()". Ce n'est pas tant l'appel à Init() qui est gênant que le fait qu'il faut penser à le faire ... Et nous savons tous que plus il y a de détails de ce genre à se souvenir pour faire marcher un code, plus le risque de bug augmente. Conceptuellement, c'est propre, au niveau design c'est à fuir... Faut-il donc choisir entre peste et choléra sans aucun espoir de se sortir de cette triste alternative ? Non. Nous pouvons faire un peu mieux et rendre tout cela transparent notamment en transférant à la classe enfant la responsabilité de s'initialiser correctement sans que l'utilisateur de cette classe ne soit obligé de penser à quoi que ce soit. La méthode de la Factory Il faut absolument utiliser la méthode de l'init séparé, cela est incontournable. Mais il faut tout aussi fermement éviter de rendre l'utilisation de la classe source de bugs. Voici nos contraintes, il va falloir faire avec. La solution consiste à modifier légèrement l'approche. Nous allons fournir une méthode de classe (méthode statique) permettant de créer des instances de la classe Enfant2, charge à cette méthode appartenant à Enfant2 de faire l'intialisation correctement. Et pour éviter toute "bavure" nous allons cacher le constructeur de Enfant2. Dès lors nous aurons mis en place une Factory (très simple) capable de fabriquer des instances de Enfant2 correctement initialisées, en une seule opération et dans le respect du non appel des méthodes virtuelles dans les constructeurs... ouf ! C'est cette solution que montre le code qui suit (Parent3 et Enfant3 étant les nouvelles classes) : // Classe Parent public class Parent3 { public Parent3(int valeur) { // MethodeVirtuelle(); } public virtual void Init() { MethodeVirtuelle(); } public virtual void MethodeVirtuelle() { } } // Classe dérivée public class Enfant3 : Parent3 { private int val; public static Enfant3 CreerInstance(int valeur) { var enfant3 = new Enfant3(valeur); enfant3.Init(); return enfant3; } protected Enfant3(int valeur) : base(valeur) { val = valeur; } public override void MethodeVirtuelle() { Console.WriteLine("Classe Enfant3. champ val = " + val); } } La création d'une instance de Enfant3 s'effectue dès lors comme suit : var enfant3 = Enfant3.CreerInstance(10); C'est simple, sans risque d'erreur (impossible de créer une instance autrement), et nous respectons l'interdiction des appels virtuels dans le constructeur sans nous priver des méthodes virtuelles lors de l'initialisation d'un objet. De plus la responsabilité de la totalité de l'action est transférée à la classe enfant ce qui centralise toute la connaissance de cette dernière en un seul point. Dans une grosse librairie de code on peut se permettre de déconnecter la Factory des classes en proposant directement une ou plusieurs abstraites qui sont les seuls points d'accès pour créer des instances. Toutefois je vous conseille de laisser malgré tout les Factory "locales" dans chaque classe. Cela évite d'éparpiller le code et si un jour une classe enfant est modifiée au point que son initialisation le soit aussi, il n'y aura pas à penser à faire des vérifications dans le code de la Factory séparée. De fait une Factory centrale ne peut être vue que comme un moyen de regrouper les Factories locales, sans pour autant se passer de ces dernières ou en modifier le rôle. Conclusion Peut-on aller encore plus loin ? Peut-on procéder d'une autre façon pour satisfaire toutes les exigences de la situation ? Je ne doute pas qu'une autre voie puisse exister, pourquoi pas plus élégante. Encore faut-il la découvrir. C'est comme en montagne, une fois qu'une voie a été découverte pour atteindre le sommet plus facilement ça semble une évidence, mais combien ont du renoncer au sommet avant que le découvreur de la fameuse voie ne trace le chemin ? Saurez-vous être ce premier de cordée génial et découvrir une voie alternative à la solution de la Factory ? Si tel est le cas, n'oubliez pas de laisser un commentaire ! Dans tous les cas : Stay Tuned ! Et pour jouer avec le code, le projet de test VS 2008 : VirtualInCtor.zip (5,99 kb)
C# - Les optimisations du compilateur dans le cas du tail-calling 1. novembre 2008 Olivier Bug, C# (0) Les optimisations du compilateurs C# ne sont pas un sujet de discussion très courant, en tout cas on voit très nettement que le fait d'avoir quitté l'environnement Win32 pour l'environnement managé de .NET a fait changer certaines obsessions des codeurs... Ce n'est pas pour autant que le sujet a moins d'intérêt ! Nous allons voir cela au travers d'une optimisation particulière appelée "tail-calling" (appel de queue, mais je n'ai pas trouvé de traduction française, si quelqu'un la connaît, qu'il laisse un commentaire au billet). Principe On appelle "tail-calling" un mécanisme d'optimisation du compilateur qui permet d'économiser les instructions exécutées en même temps que des accès à la pile. Les circonstances dans lesquelles le compilateur peut utiliser cette optimisation sont celles où une méthode se termine par l'appel d'une autre, d'où le nom de tail-calling (appel de queue). Prenons l'exemple suivant : static public void Main() { Go(); } static public void Go() { Première(); Seconde(); Troisième(); } static public void Troisième() { } Dans cet exemple le compilateur peut transformer l'appel de Troisième() en un appel de queue (tail-calling). Pour mieux comprendre regardons l'état de la pile au moment de l'exécution de Seconde() : Seconde()-Go()-Main() Quand Troisième() est exécutée il devient possible, au lieu d'allouer un nouvel emplacement sur la pile pour cette méthode, de simplement remplacer l'entrée de Go() par Troisième(). La pile ressemble alors à Troisième()-Main(). Quand Troisième() se terminera elle passera l'exécution à Main() au lieu de transférer le trait à Seconde() qui immédiatement devra le transférer à Main(). C'est une optimisation assez simple qui, cumulée tout au long d'une application, et ajoutée aux autres optimisations, permet de rendre le code exécutable plus efficace. Quand l'optimisation est-elle effectuée ? La question est alors de savoir quand le compilateur décide d'appliquer l'optimisation de tail-calling. Mais dans un premier temps il faut se demander de quel compilateur nous parlons.... Il y existe en effet deux compilateurs dans .NET, le premier prend le code source pour le compiler en IL alors que le second, le JIT, utilisera ce code IL pour créer le code natif. La compilation en IL peut éventuellement placer certains indices qui permettront de mieux repérer les cas où le tail-calling est applicable mais c'est bien entendu dans le JIT que cette optimisation s'effectue. Il existe de nombreuses règles permettant au JIT de décider s'il peut ou non effectuer l'optimisation. Voici un exemple de règles qui font qu'il n'est pas possible d'utiliser le tail-calling (par force cette liste peut varier d'une implémentation à l'autre du JIT) : L'appelant ne retourne pas directement après l'appel; Il y a une incompatibilité des arguments passés sur la pile entre l'appelant et l'appelé ce qui imposerait une modification des arguments pour appliquer le tail-calling; L'appelant et l'appelé n'ont pas le même type de retour (données de type différents, void); L'appel est est transformé en inline, l'inlining étant plus efficace que le tail-calling et ouvrant la voie à d'autres optimisations; La sécurité interdit ponctuellement d'utiliser l'optimisation; Le compilateur, le profiler, la configuration ont coupé les optimisations du JIT. Pour voir la liste complète des règles, jetez un oeil à ce post. Intérêt de connaitre cette optimisation ? Normalement les optimisations du JIT ne sont pas un sujet intéressant au premier chef le développeur. D'abord parce qu'un environnement managé comme .NET fait qu'à la limite ce sont les optimisations du code IL qui regarde directement le développeur et beaucoup moins la compilation native qui peut varier d'une plateforme à l'autre pour une même application. Ensuite il n'est pas forcément judicieux de se reposer sur les optimisations du JIT puisque, justement, ce dernier peut être différent sans que l'application ne le sache. Qui s'intéresse à l'optimisation du tail-calling alors ? Si vous écrivez un profiler c'est une information intéressante, mais on n'écrit pas un tel outil tous les jours... Mais l'information est intéressante lorsque vous déboguez une application car vous pouvez vous trouver face à une pile d'appel qui vous semble "bizarre" ou "défaillante" car il lui manque l'une des méthodes appelées ! Et c'est là que savoir qu'il ne faut pas chercher dans cette direction pour trouver le bug peut vous faire gagner beaucoup de temps... Savoir reconnaître l'optimisation de tail-calling évite ainsi de s'arracher les cheveux dans une session de debug un peu compliquée si on tombe en plus sur un pile d'appel optimisée. Un bon debug consiste à ne pas chercher là où ça ne sert à rien (ou à chercher là où c'est utile, mais c'est parfois plus difficile à déterminer !), alors rappelez-vous du tail-calling ! Et stay tuned ! nota: le présent billet est basé sur un post de l'excellent blog ASP.NET Debugging