Comme certains ont pu s’en apercevoir, le look & feel de Dot.Blog a été modifié en septembre 2012. Malgré mes efforts cela occasionne quelques gênes notamment pour certains liens vers des ressources ou des publications. Voici comment retrouver ce qui pourrait manquer…More...
Les Parallel Extensions, connues jusqu’à lors sous le nom de Parallel Framework Extensions (ou PFX) forment une librairie permettant de faciliter la construction d’algorithmes parallèles (multi-thread) tirant partie des machines multi-cœur. Je vous en avais déjà parlé, ainsi que de P-Linq les extensions parallèles pour LINQ. Deux choses importantes à savoir aujourd’hui : les Parallel Extensions font partie de .NET 4 (VS 2010, Silverlight…) et une nouvelle librairie arrive, les Reactive Extensions !More...
Générer des nombres aléatoires avec un ordinateur est déjà en soit ambigu : un PC est une machine déterministe (heureusement pour les développeurs et les utilisateurs !) ce qui lui interdit l’accès à la génération de suites aléatoires aux sens mathématique et statistique. Toutefois il s’agit d’un besoin courant et .NET propose bien entendu une réponse avec la classe Random. More...
Voici un beau cocktail ! Pouvoir à la fois parler de C#, donc en plein dans le sujet de ce blog, et de Hadopi, un système liberticide non pas dans son fondement (le respect de la propriété intellectuel est un sujet sérieux) mais dans sa forme. Vous commencerez à recevoir vos premiers emails le 21 juin, c’est décidé et c’est en route ! More...
Le code spaghetti est de retour ! Fuyez braves gens !
Sous-titré : Du Rififi dans le Xaml.
Avertissement au lecteur : ce billet, bien que bâti sur un fond technique préoccupant et une expérience réelle, utilise un formalisme un peu romancé. Ne cherchez pas d’extraits de code ici. Mais si vous avez un peu de temps, laissez vous porter par l’histoire. Si vous êtes pressés, revenez plus tard lire ce billet !

Genèse d’un malaise
Comme vous le savez je suis un passionné de WPF et de Silverlight, la puissance de Xaml servi par un Framework aussi riche que .NET et des outils de qualité comme Blend ou Visual Studio ne peuvent laisser de marbre (comment peut-il y avoir encore des gens travaillant sous Java ou Delphi ?). J’en suis tellement passionné que j’évangélise à tour de bras, ici et ailleurs, et que mes compétences autant techniques que de gardien des brebis égarées me valent d’avoir l’honneur d’être Microsoft MVP, notamment en 2010 pour les technologies de développement d’applications clientes (Client Application Development, “CAD”" MVP).
Bref, si je dis tout cela c’est pour faire comprendre que bien que cultivant mon objectivité comme un joyau précieux garant de la liberté de mes neurones, je suis plutôt “pro” Microsoft et que, bien entendu, cela peut déplaire à certains comme en séduire d’autres… La diversité du monde en fait sa richesse, isn’t it.
Et un partisan de Microsoft, MVP de surcroît, fan de Silverlight, ne dira jamais le moindre mal de sa technologie fétiche… Et soyez-en convaincu je ne briserai pas cette loyauté, essentielle à mes yeux, mais il faut pourtant savoir tirer les sonnettes d’alarme de temps en temps. D’ailleurs c’est une question de crédibilité, que vaudrait un expert sans liberté de parole ni de pensée… Et puis au fond vous verrez que c’est bien plus, comme à chaque fois, l’humain qui en prend pour son grade dans ce récit que les outils, innocents, par nature.
Tout cela pour parler franchement d’un risque, d’une dérive, et surtout d’un grand danger : le retour du code spaghetti ! Et, comble de l’infamie, la peur de cette tare qu’on croyait du passé, je ne la brandis pas à propos de langages ésotériques comme F#, ou de solutions vieillissantes comme Java. Non. Le drame est bien là : c’est de WPF et de Silverlight dont je veux vous entretenir, et ce, au travers d’une anecdote récente.
J’ai fait il y a quelques temps un audit dont je tairais, vous le comprendrez aisément, le nom du client visité ainsi que la date exacte. Au demeurant une société ni trop jeune pour n’être pas assez structurée, ni trop vieille pour en être devenue ringarde. Une entreprise de bonne taille, assez dynamique et assez typique de ma clientèle, se targuant de posséder une équipe de développement à la pointe du progrès, la preuve, puisque maniant les balises Xaml avec la même dextérité que la truelle l’est par un vrai maçon diplômé.
Le trait n’est pas forcé, il n’y en a nul besoin. Ce fut au début un audit “classique” c’est à dire durant lequel j’ai vu du code “normal” donc assez médiocre. Je dis “normal” au sens de la loi normale statistique ce qui signifie que grâce à messieurs Laplace et Gauss il m’arrive de voir des choses épouvantables comme de pures merveilles, mais que ces deux cas représentent un pourcentage faible au deux bouts de la cloche… Je ne savais pas encore que j’allais atteindre le bout de la cloche. Le mauvais. Bien entendu.
Espèce de spaghetti !
Le code “normal” est médiocre généralement. C’est finalement une définition en soi. Le code exceptionnel, étant, par le même genre de raisonnement, plus rare. C’est finalement une lapalissade. Donc, en général, je vois du code médiocre, donc normal (dans l’autre sens c’est intéressant aussi, non ?).
Et j’appelle médiocre un code qui se caractérise par plusieurs aspects distinctifs très techniques que je détaille avec moult délicatesse dans mes rapports d’audit pour expliquer les choses en y mettant les formes mais qui se résument en général à quelques cas généraux qu’on pourrait caractériser très doctement. Je m’exprimerais ici de façon bien plus directe puisqu’on est “entre nous” :
-
- La “putain” c’est à dire le code sur lequel tout le monde est passé, sauf le train et vous, mais ça, c’est même pas sûr, puisque vous êtes là… C’est du code typique des boîtes où les gens sont mal payé et où les salariés défilent plus vite qu’une hirondelle dans un ciel de printemps…
- Les migrations de migrations de portage d’intégration (de milles sabords, version 24 bis modifiée E). En général du code qu’on trouve dans les administrations. Avec des documentations de plusieurs centaines de pages, que personne n’a jamais lu bien entendu.
- Le code mille-feuille. Savoureuse pâtisserie constituée de tant de couches qu’on ne peut les compter, comme les pattes du iule (autrement appelé mille-pattes). C’est un peu un mélange des deux précédents mais en plus structuré que le premier (beaucoup plus, et c’est la son problème) et en moins documenté que le second (beaucoup moins, et c’est aussi là son problème). C’est du code de SSII “in” avec de vrais morceaux de “nouvelles technos” dedans.
- Le code “start-up”, celui-là est bourré de technos innovantes, hélas non maîtrisées, peu documentées et en bêta ne tournant que sur les versions US de l’environnement. Un code d’essayiste pur travaillant pour la beauté du discours qui va autour plus que pour l’efficacité, des gens qui devraient être en agence de pub plutôt que devant un environnement de développement.
- Le code à papa, ou l’objet est utilisé comme du C procédural. C’est le code C# écrit par de vieux delphistes ou cobolistes reconvertis sur le tard par exemple.
- Le code d’ingénieur, un des pires. Sortant de l’école et voulant montrer ses muscles en complexifiant tout et l’enrobant dans une prose technique pleine de sigles bizarres et de référence à des bouquins lus de lui seul et de ses potes de promo. Quand il arrêtera de sucer son pouce, il deviendra un bon développeur sévèrement burné comme disait Tapie. Mais en attendant son code c’est l’enfer…
- Et enfin, le célèbre, le magnifique, le code spaghetti, marquant l’incompétence à maîtriser la complexité du sujet. Celui-là est typique des mauvaises équipes, tous contextes confondus.
Il y en a bien d’autres dans mon bestiaire, en 20 ans d’audit vous imaginez ce que j’ai pu voir (heureusement je rencontre aussi des équipes compétences et même parfois de l’excellent code, sinon ça serait désespérant !).
Et WPF et Silverlight dans tout ça ?
C’est là que je voulais en venir, mais il fallait bien passer par ce détour pour vous plonger dans l’ambiance trouble de cette descente aux enfers binaires. Sinon cela aurait été d’une platitude redoutable. Un peu de lecture ça change des extraits de code en Xaml. Justement. Parlons de lui et de ce dernier audit (qui n’est pas le dernier, confidentialité oblige comme je le disais plus haut). Disons que c’est assez récent pour être en Xaml mais pas suffisamment pour être en Silverlight 3.
Qu’avait ce code qui vaille ce billet un peu particulier ?
Le code qui rend fou
il m’a rendu fou. Tout simplement ! Et en plus il m’a filé les chocottes !
WPF et Silverlight sont des technologies merveilleuses, Xaml a un pouvoir descriptif exceptionnel à tel point qu’il permet d’économiser beaucoup de code behind.
Malgré le génie des équipes MS ayant travaillé sur le Framework j’aurais malgré tout préféré que cette révolution se fasse à 100% dans le respect du paradigme objet et du fortement typé. Or ce ne fut pas totalement le cas, et si je comprends bien les contraintes techniques sous-jacentes qui ont interdit cet idéal, certains choix sont hélas autant de portes ouvertes sur des risques que je viens de palper de près. Et ça fait peur.
Il y a en premier lieu l’architecture. Il ne suffit pas de prendre Prism et ses Dll pour avoir un bon logiciel. Il faut comprendre et maîtriser la chose. Ce qui ne s’improvise pas. Mais il y a pire, car plus lié à la technologie elle-même.
Par exemple, prenez la syntaxe du Binding, en Xaml donc. Vous avez une balise, vous la complétez d’une propriété et là, au lieu de mettre une valeur, vous ouvrez des accolades américaines suivies du mot Binding, le tout entre guillemets, une simple chaine de caractères. Quant à ce qu’il y a après le mot Binding, je suis convaincu qu’aucun aficionado de WPF ou de Silverlight n’est capable de me citer de tête toutes les combinaisons possibles et astuces disponibles. Parfois c’est {Binding} tout court, parfois c’est une longue phrase intégrant imbriquées d’autres accolades faisant référence à des ressources statiques ou dynamiques, des paramètres, des convertisseurs, etc… Une puissance énorme, un peu comme les expressions régulières : puissant mais pas très clair (et c’est un euphémisme). Pas clair, on peut s’y faire… mais pas clair et pas fortement typé ni même contrôlé, c’est là que la chute aux enfers commence…
Le code que j’ai audité était bourré d’element binding, de datacontext pointant des conteneurs de services avec des indirections dans tous les sens “pour la souplesse”. Quand MVC et MVVM ne sont pas compris, mieux vaut tout mettre en procédural dans le code-behind de chaque fiche, c’est plus simple à débugger ! Le pire c’est que chacun dans l’équipe y allait de sa petite couche, de sa petite modification. Et je fais du “refactoring” par là, et je refactore par ici… Oui mais voilà, dans les balises Xaml ça commence à danser le cha-cha-cha toutes ses chaînes de caractères non contrôlées, tous ces paramètres de convertisseurs qui ont évolués sans qu’on ait mis à jour les références dans le Xaml, ces ressources statiques et d’autre dynamiques dans des dictionnaires chargés dynamiquement !
Le soft ne tenait plus que par un fil qu’un joyeux drille a du couper juste avant que je n’intervienne. Dommage. Je n’arrivais même pas à dépasser un ou deux écrans avant que ça me pète à la figure. Même avec des outils très intelligents comme NDepend, comprendre le soft était virtuellement impossible.
Quant à savoir d’où vient “le” problème ! C’est le soft lui-même tout entier qui était “le” problème… Ainsi que ceux l’avaient écrit (et ceux qui les dirigent, car un mauvais soft est toujours la cause d’une mauvaise direction bien plus que de mauvais développeurs).
En fait, le Binding Xaml est une porte ouverte sur l’inconnu. Une feature d’une grande puissance, sans laquelle beaucoup du charme disparaîtrait, mais assez déraisonnable dans cette implémentation libre sous forme de chaînes de caractères non compilées. La porte sur le néant, l’incontrôlé, et pire : l’incontrôlable. Un trou noir syntaxique. La damnation du testeur envoyé moisir au purgatoire des pisseurs de ligne. Et l’enfer de l’auditeur.
le Binding au pays des X-Files
Le Data binding Xaml est une jungle syntaxique pas très bien … balisée et totalement déconnectée de toute forme d’analyse à la compilation. du “Late Bugging” comme je m’amuse à appeler cette stratégie de type “late binding”, principe de ligature tardive utilisé d’ailleurs en d’autres endroits et même sous d’autres frameworks. Pire que les Dynamic de C# 4 (pratiques mais dangereux), pire que F# (stimulant mais pas industrialisable), le Binding de Xaml est un gouffre à bugs, un chien fou s’il n’est pas tenu en laisse fermement.
En réalité un marteau ne pourra jamais être jeté aux fers (!) pour le meurtre de qui que ce soit. Un marteau est un outil, et même s’il a servi et servira encore dans de nombreux crimes, un outil est un objet sans âme, sans conscience et donc sans responsabilité. Même un fusil mitrailleur est un objet innocent, même un canon de 105 ou une bombe atomique sont plus innocents que l’agneau qui vient de naître et qui, comme tout être vivant, et à la mesure de son intelligence, portera le poids de la responsabilité de ses agissements. Un outil de développement restera donc à jamais hermétique à tout procès d’intention. A celui qui s’en sert de le faire correctement.
Il en va de même de Xaml, de son data Binding et de bien d’autres de ces facettes. La responsabilité incombera toujours à l’humain qui tient le marteau. A l’informaticien qui tient la souris. Au développeur qui tape un code affreux.
Mais certaines features de Xaml, certains choix conceptuels comme l’utilisation de chaînes de caractères non contrôlées et non parsées à la compilation sont à mon sens des erreurs. Si des projets comme celui que j’ai audité et dont je vous parle ici devenaient courants, nul doute que cela signerait l’arrêt de mort de WPF et de Silverlight. La faute aux mauvais développeurs ? Pas seulement. A ceux aussi qui ont décidé de programmer Xaml de cette façon trop ouverte, trop permissive. On voit bien comment Silverlight a été verrouillé dès le départ par Microsoft. Si le moindre virus, le moindre phishing avait été réalisé avec Silverlight 1 ou 2 c’en était fini des espoirs portés par cette technologie. Microsoft a été méfiant pour préserver l’avenir de la technologie et c’est une bonne chose. Ce que j’ai vu dans le projet WPF dont je parle ici, c’est un peu de la même nature, mais à l’inverse : Microsoft n’a pas verrouillé Xaml comme cela a été fait avec Silverlight. Et si de tels détournements se généralisent c’est toute la technologie qui trinquera. D’ici un an environ, lorsque les projets lancés ces derniers temps seront finalisés, et qu’il faudra compter ceux qui n’aboutiront pas ou qui ne marcheront jamais bien, la note peut être salée pour Xaml. Microsoft a pris un sacré risque en faisant des choix de conception comme celui des balises non compilées (et dans lesquelles même Intellisence se prend les pieds dans le tapis).
Déjà sous Delphi je voyais souvent ce genre de code spaghetti avec des variables globales référencées n’importe où, des fiches utilisant des variables d’autres fiches jusqu’à créer des chaines de dépendances ingérables. J’ai vu des codes de ce type ne pouvoir jamais aboutir. Il m’est même arrivé une fois de réécrire en 15 jours proprement à partir de zéro un soft développés en 1 an par deux personnes sans le dire au client histoire que les 15 jours d’expertise qu’il m’avait payés servent à quelque chose… Je tairais ici le nom du client (une administration). J’étais plus jeune et je voulais me prouver des choses certainement, mais personne ne l’a jamais su jusqu’à ce billet (et encore vous en savez peu!). En tout cas ce projet là je l’ai sauvé. Mais être un pompier de l’ombre n’est pas ma vocation. Les gars étaient malgré tout étonnés ne pas vraiment s’y retrouver dans “leur” code. Amusante situation. Je n’avais rien gardé de “leur” code :-) Mais le soft marchait…
Mais avec Xaml, et une puissance décuplée, je viens de voir des horreurs du même genre alors que depuis des années, je commençais à trouver que C# incitait plutôt à faire du code “correct”, le niveau étant globalement meilleur que celui que je voyais sur Delphi. Et patatras ! Xaml arrive avec ces chausses-trappes dans lesquels les développeurs s’engouffrent. La faute à Xaml ? Pas totalement, mais si tout était typé et contrôlé à la compilation certaines mauvaises utilisations ne seraient pas possibles. Après tout, le fortement typé en soi ça ne sert à rien si on suppose que tous les développeurs sont “bons” ! Mais tous les langages modernes tentent de l’être car on sait que l’humain est faillible. En créant une brèche incontrôlable dans un édifice aussi beau de Xaml, ses concepteurs ont fait un pari risqué. Celui que tous les développeurs sauraient maîtriser le potentiel sans tomber dans ses pièges. Un pari forcément perdu d’avance.
Vision d’horreur
Ce que j’ai vu est donc indescriptible. Un peu comme si j’essayais de décrire Cthulhu. Ceux qui ont essayés sont souvent morts avant de finir d’écrire son nom (ah Lovecraft…) !
Imaginez vous un logiciel de 250 fiches WPF environ utilisant de l’ADO.NET, des bouts de LINQ to SQL, et des nouveautés en Entity Framework, le tout à la sauce Prism / MVC (mais en ayant lu le manuel certainement à l’envers car Prism c’est très bien !) avec des tentatives d’inversion de contrôle (et des dérapages non contrôlés) farci d’un code Xaml bourré de Binding renvoyant vers on ne sait quoi (et hélas pas contrôlé à la compilation), le tout planqué dans 4 ou 5 couches DAL, BOL pré-BOL, post-BOL, et j’en passe, histoire de faire court. La note s’élève a 6000 jours/homme (5 ans/homme environ). Pas un truc gigantesque mais qui commence à faire mal au budget malgré tout.
Agrémentez le tout de deux systèmes de versionning dont aucun n’avait vraiment une version complète du soft, des essais épars de tests unitaires avec MbUnit et VSTS. Vous obtiendrez le tableau. Une œuvre qui dépasse le classicisme habituel de ce point de vue, plus proche du Cubisme que de l’Hyperréalisme tout de même, avec au moins la bonne volonté de faire des petites choses (du mauvais testing prouve au moins qu’on a essayé de tester, ce qui est encore bien rare…). Mais l’enfer est pavé de bonnes intentions, c’est un peu ça le problème.
Le code était impossible à maintenir, les problèmes de performances impossible à cerner sans y passer plus de temps qu’à toute refaire, le code Spaghetti, avec un grand S avait frappé de son coup de poignard vengeur et lâche dans le dos. Une complexité non maîtrisée qui tel l’horizon d’un trou noir aspirait irrémédiablement le bon code vers l’enfer central laissant le hasard faire le tri entre les chemins à prendre… Démêler l’écheveau n’était pas possible, pas plus que de formuler des guidelines sérieusement applicables dans un tel contexte ni aucun conseil pour se sortir d’une telle situation.
Vous imaginez peut-être l’horreur qu’a été l’écriture du rapport d’audit. Entre dire une vérité que personne n’était prêt à entendre et mentir sachant que peu importe ce que je dirais cela passerait mal et qu’au fond mieux valait ne pas trop déplaire… Mon éthique a tranché, j’ai dit la vérité. En l’enrobant. Des heures passées à réécrire deux fois, dix fois certains paragraphes. Ils ne s’en douteront jamais… Et au final un rapport qui sera peut-être classé au fond d’un tiroir car personne ne voulait vraiment savoir ce qui n’allait pas. La remise en question d’un tel échec dépasse de loin le cadre technique et peu de gens savent admettre leurs erreurs, surtout quand toute la chaîne hiérarchique de la base au sommet doit participer à ce questionnement.
Le salaire de la peur
La peur dont je parlais plus haut, les chocottes que cet audit m’a données, c’est d’être confronté au fleuron de la technologie informatique, à des choses en lesquelles je crois car elles marquent un réel progrès et que (bien utilisées) elles permettent justement un code limpide. Cet audit a brisé ce fantasme en me rappelant qu’un marteau pouvait servir au meilleur, comme au crime. Xaml, WPF et Silverlight n’échapperaient pas à la règle et il faudra être vigilant. Surtout que l’avalanche de technologies et de patterns (WCF Ria Services, Entity Framework, MVVM, Prism, …) rendent presque impossible la maîtrise de tous ces sujets. Je suis payé pour ça. Au minimum 50% de mon temps est passé en autoformation pour apporter un conseil éclairé sur les technos à venir. Ce sont les 50% vendus qui financent cette formation et cette recherche permanente. Comment un développeur qui fait ces 48h (35 de base + le dépassement obligatoire non payé car il est aux “cadres”) peut-il se former à tout cela en travaillant sur d’autres projets la journée ? C’est impossible. Et cela créé une situation très dangereuse. Technologie sophistiquée + manque de formation = code spaghetti !
David Vincent au pays des tags
Il était donc urgent de vous en parler, d’attirer votre attention sur certaines dérives, car dans une moindre mesure, je sais, je l’ai vu chez d’autres, ces chausses-trappes savent s’ouvrir sous les pieds des développeurs les mieux formés et les mieux intentionnés ! Je les ai vues, et je dois convaincre un monde incrédule…
Ma mission, que j’ai acceptée (forcément avant de voir, on a pas vu…), était de faire un audit de pré-release. C’est à dire de venir faire le point sur l’état de la situation, les derniers fameux “boulons à serrer” en quelque sorte, et surtout de venir signer un satisfecit au DI, très fier de son bébé (Rosemary’s baby plutôt que Dora de TF1 au final), afin qu’il puisse faire monter sa prime de fin d’année je suppose.
Hélas, déception et vilenie. Ce n’est pas avec le prix d’un audit (fort raisonnable, devis gratuit sur simple demande) qu’on achète ma conscience. Il m’a donc fallu trouver les mots et les formules diplomatiques pour rendre un audit policé et mesuré sur cet édifice in-maintenable, voué à l’échec et bon à mettre à la broyeuse. Ce n’est pas la partie la plus difficile, car tout rapport d’audit se doit d’être policé et tourné de façon neutre et technique. Même si parfois on a envie de crier “bande de nuls !”, non, ce n’est pas bien, on ne le fait pas…
Que les futurs clients ne s’affolent pas trop, ce que je raconte aujourd’hui est malgré tout à classer dans les exceptions, le fameux bout de la cloche de la courbe Normale. Même si je ne suis que rarement content du code que je vois, on est, heureusement en moyenne, assez loin de l’horreur qui justifie le présent billet !
O Tempora, O Mores !
Parfois je rêve, j’imagine un monde où l’un de ces clients m’appellerait en me proposant un pont d’or pour lui avoir ouvert les yeux et l’avoir empêché de plonger plus encore tant qu’il était encore temps… Mais ce n’est qu’un rêve, bien entendu. Je suppose que je n’entendrais plus parler de ce client comme de quelques autres dont, les années passant et les vœux annuels restant sans réponse m’ont forcé à une résignation réaliste. Comme le chantait l’artiste, “il a dit la vérité, il doit être exécuté” !
Hélas, les temps ne sont pas à la prise de conscience ni à la bravoure. Nous vivons une époque de lâches où chacun ouvre son parapluie pour que les gouttes en atteignent d’autres. Forcément celui qui est au plus bas de la pyramide se prend tout sur la figure. Souvent c’est le développeur qui trinque pour une chaîne hiérarchique défaillante qui n’a pas fait son métier et qui n’assume pas ses responsabilités. Un développeur n’est jamais coupable seul d’un mauvais code, c’est toute la chaine de commandement qui faillit.
Il y a encore un rêve que je fais et que j’aimerais voir se réaliser avant ma (encore lointaine) retraite : que les entreprises fassent intervenir un conseil, un auditeur avant que l’irréparable ne soit commis, pas après pour constater les dégâts ! … ça serait tellement mieux et plus gratifiant pour moi et mes confrères ! Conseiller, orienter, former, en un mot aider, c’est tout de même plus chouette que de passer pour le père fouettard et l’inspecteur des travaux finis !
Faut-il brûler Xaml ?
Certes non.
Mais ce que je veux qu’il vous reste de ce récit hallucinant autant que réel, c’est que Xaml est utilisé par certains comme un langage dans le langage. Un moyen de vider le code-behind de son C# pour remplir des balises ésotériques. Un langage dans le langage avec son Binding qui est, telles le sont les poupée Russes, encore un langage dans le langage.
A vouloir tout faire par binding, à tout dynamiser, à appliquer tout Prism et le chargement dynamique des modules, la découvertes des plugins avec MEF, le tout mis en forme façon MVVM, sans compter les ruses purement graphiques du templating sous Blend, des DataTemplate à double face pivotante en 2,5 D, à vouloir appliquer tout cela dans un seul soft sans avoir pris les 2 ou 3 ans nécessaires à acquérir la parfaite maîtrise méthodologique pour faire marcher tout cela ensemble, on peut facilement arriver à une situation désastreuse. Encore plus facilement que sous C, encore plus que sous Delphi, pire que sous Windows Forms. Tout cela n’était que de l’amateurisme face aux dérapages délirants que WPF et Silverlight peuvent permettre… Comment faire des tests unitaires parlants sur des balises de Binding imbriquées à un tel point que même Intellisense de VS s’y perd ?
L’extrémisme essayiste est un fléau dans notre métier. Il m’a toujours effrayé, mais il a toujours eu sa sanction “naturelle” : une technologie qui finit par être si mal utilisée créée une telle publicité négative qu’elle tend à disparaître. Je ne voudrais pas que cela arrive à WPF et à Silverlight qui apportent tant d’autres progrès !
Il faut sauver le soldat Xaml !
Pitié pour Xaml, un bon geste pour WPF et Silverlight : ne laissez pas des sagouins faire du mal à toutes ces belles technologies que Microsoft nous offre depuis quelques années. Laissez leur une chance, celle que les développeurs deviennent plus matures et mieux formés aux méthodologies qui vont avec. Ne commettez pas le crime d’être complice de ce genre de chose. Intégrer les technologies les unes après les autres, ne sautez pas d’une bêta à l’autre sans avoir l’assurance que vous maîtrisez la précédente !
Mieux vaut une application Silverlight fonctionnant avec un Service Web classique et dont les champs sont connectés par code, qui marche et qui est maintenable, qu’une élucubration technologique improbable et non maîtrisée qui s’écroulera comme un château de carte au premier éternuement du premier bug venu…
Un peu de sérieux…
Il ne s’agit pas d’une invitation à la méfiance, encore moins au passéisme. Non, c’est une invitation au professionnalisme : n’utilisez que ce que vous maîtrisez.
Si vous maîtriser le top de la techno, allez-ci, franchement. Si vous avez un petit doute : faites joujou dans une machine virtuelle et produisez du code que vous comprendrez à 100% et surtout que d’autres pourront comprendre pour le maintenir, le faire vivre dans les années à venir.
Le code Kleenex coûte cher, il dévalorise notre métier et les outils que nous utilisons.
Le code spaghetti peut assassiner une technologie.
Codez bien. Formez-vous bien.
Et faites auditer vos projets avant, pas après.
Et Stay Tuned, j’aurais des trucs plus légers à vous dire la prochaine fois !
Model-View-ViewModel, je vous en parlais il y a très peu de temps (MVVM, Unity, Prism, Inversion of Control…) et je vous avais promis un exemple pour rendre tout cela plus concret. C’est fait ! Et même mieux, un article de 70 pages l’accompagne !
Vous saurez tout (ou presque) sur cette design pattern absolument incontournable pour développer sérieusement sous Silverlight.
Après des explications sur la pattern elle-même l’article vous présente une application exemple entièrement réalisée avec ce qu’il y a “out of the box”. J’ai fait le choix de n’utiliser aucun Framework existant (Prism, Cinch, Silverlight.FX, MVVM Light…) pour vous montrer que M-V-VM peut entièrement être mis en œuvre “à la main” sans aide extérieure.
Cela ne veut pas dire que tous ces Frameworks (dont l’article parle aussi) ne sont pas intéressants, au contraire ! Mais comment choisir une librairie facilitant M-V-VM si vous ne savez pas comment mettre en œuvre cette pattern et si vous ne connaissez pas les difficultés qu’elle soulève autant que ses avantages ?
Cet article vous permettra de faire le point sur M-V-VM et de pouvoir ensuite choisir le Framework qu’il vous plaira en toute connaissance de cause ou bien cela vous aidera à développer votre propre solution. Après tout, l’application exemple fonctionne parfaitement sans aucun de ces Frameworks….
Le code source du projet est fourni. En raison de l’énorme avantage de la gestion des commandes introduites dans Silverlight 4 (toujours en beta) l’article utilise cette version qui sera bientôt disponible. Tout est expliqué pour savoir comment faire fonctionner le code exemple à l’aide de VS 2010 ou Blend 4 (en beta aussi).
L’article peut être lu sans faire tourner le code si vous ne souhaitez pas installer la beta de SL4, et la première partie théorique s’applique aussi bien à M-V-VM sous SL3.
Bonne lecture !
(PS: n'oubliez pas que depuiis août 2012 les articles sont regroupés sur la page publications).
Téléchargement ici : M-V-VM avec Silverlight, de la théorie à la pratique.
Visual Studio 2010 beta 2 est maintenant accessible au public et il devient donc possible de vous parler des nouveautés sans risque de violer le NDA qui courrait jusqu’à lors pour les MVP et autres early testers de ce produit.
Les évolutions du langage commencent à se tasser et la mouture 4.0 est assez loin des annonces fracassantes qu’on a pu connaître avec l’arrivée des génériques ou des classes statiques et autres nullables de C# 2.0, ni même avec LINQ ou les expressions Lambda de C# 3.0.
Pour la version 4 du langage on compte pour l’instant peu d’ajouts (le produit ne sortira qu’en 2010 et que d’autres features pourraient éventuellement apparaître). On peut regrouper les 3 principales nouveautés ainsi :
- Les type dynamiques (dynamic lookup)
- Les paramètres nommés et les paramètres optionnels
- Covariance et contravariance
Paramètres optionnels
Il est en réalité bien étrange qu’il ait fallu attendre 4 versions majeures de C# pour voir cette syntaxe de Delphi refaire surface tellement son utilité est évidente.
De quoi s’agit-il ? Vous avez tous écrits du code C# du genre :
1: MaMethode(typeA param1, typeB param2, typeC param3) …;
2: MaMethode(typeA param1, typeB param2) { MaMethode(param1, param2, null) }
3: MaMethode(typeA param1) { MaMethode(param1, null) }
4: MaMethode() { MaMethode(null) }
Et encore cela n’est qu’un exemple bien court. Des librairies entières ont été écrites en C# sur ce modèle afin de permettre l’appel à une même méthode avec un nombre de paramètres variable. Le Framework lui-même est écrit comme cela.
Bien sûr il existe “params” qui autorise dans une certaine mesure une écriture plus concise, mais dans une certaine mesure seulement. Dans l’exemple ci-dessus le remplacement des valeurs manquantes par des nulls est une simplification. Dans la réalité les paramètres ne sont pas tous des objets ou des nullables. Dans ces cas là il faut spécifier des valeurs bien précises aux différents paramètres omis. Chaque valeur par défaut se nichant dans le corps de chacune des versions de la méthode, pour retrouver l’ensemble de ceux-ci il faut donc lire toutes les variantes et reconstituer de tête la liste. Pas très pratique.
Avec C# 4.0 cette pratique verbeuse et inefficace prend fin. Ouf !
Il est donc possible d’écrire une seule version de la méthode comme cela :
1: MaMethode(bool param1=false, int param2=25, MonEnum param3 = MonEnum.ValeurA) …
Grâce à cette concision l’appel à “MaMethode(true)” sera équivalente à “MaMethode(true, 25, MonEnum.ValeurA)”. Le premier paramètre est fixé par l’appelant (c’est un exemple), mais les deux autres étant oubliés ils se voient attribuer automatiquement leur valeur par défaut.
Pas de surcharges inutiles de la méthode, toutes les valeurs par défaut sont accessibles dans une seule déclaration. Il reste encore quelques bonnes idées dans Delphi que Anders pourraient reprendre comme les indexeurs nommés ou les if sans nécessité de parenthèses systématiques. On a le droit de rêver :-)
Comme pour se faire pardonner d’avoir attendu 4 versions pour ressortir les paramètres par défaut de leur carton, C# 4.0 nous offre un petit supplément :
Les paramètres nommés
Les paramètres optionnels c’est sympa et pratique, mais il est vrai que même sous Delphi il restait impossible d’écrire du code tel quel “MaMethode(true,,MonEnum.ValeurA)”. En effet, tout paramètre doit recevoir une valeur et les paramètres “sautés” ne peuvent être remplacés par des virgules ce qui rendrait le code totalement illisible. C# 4.0 n’autorise pas plus ce genre de syntaxe, mais il offre la possibilité de ne préciser que quelques uns des paramètres optionnels en donnant leur nom.
La technique est proche de celle utilisée dans les initialiseurs de classe qui permettent d’appeler un constructeur éventuellement sans paramètre et d’initialiser certaines propriétés de l’instance en les nommant. Ici c’est entre les parenthèses de la méthode que cela se jouera. Pour suivre notre exemple précédent, si on veut ne fixer que la valeur de “param3” il suffit d’écrire :
1: MaMethode(param3 : MonEnum.ValeurZ);
de même ces syntaxes seront aussi valides :
1: MaMethode(true,param3:MonEnum.ValeurX);
2: MaMethode(param3:MonEnum.ValeurY,param1:false);
En effet, l’ordre n’est plus figé puisque les noms lèvent toute ambigüité. Quant aux paramètres omis, ils seront remplacés par leur valeur par défaut.
Voici donc une amélioration syntaxique qui devrait simplifier beaucoup le code de nombreuses librairies, à commencer par le Framework lui-même !
Dynamique rime avec Polémique
Autre nouveauté de C# 4.0, les types dynamiques. Aie aie aie…
Dynamique. C’est un mot qui fait jeune, sautillant, léger. Hélas. Car cela ne laisse pas présager du danger que représente cette extension syntaxique ! La polémique commence ici et, vous l’aurez compris, je ne suis pas un fan de cette nouveauté :-)
Techniquement et en deux mots cela permet d’écrire “MaVariable.MethodeMachin()” sans être sûr que l’instance pointée par MaVariable supporte la méthode MethodeMachin(). Et ça passe la compilation sans broncher. Si çà pète à l’exécution, il ne faudra pas venir se plaindre. Le danger du nouveau type “dynamic” est bien là. Raison de mes réticences…
Si on essaye d’être plus positif il y a bien sûr des motivations réelles à l’implémentation des dynamiques. Par exemple le support par .NET des langages totalement dynamiques comme Python et Ruby (les dynamique de C# 4 s’appuient d’ailleurs sur le DLR), même si ces langages sont plus des gadgets amusants que l’avenir du développement (avis personnel). Les dynamiques simplifient aussi l’accès aux objets COM depuis IDispatch, mais COM n’est pas forcément non plus l’avenir de .NET (autre avis personnel).
Les deux autres emplois des dynamiques qui peuvent justifier leur existence sont l’accès simplifié à des types .NET au travers de la réflexion (pratique mais pas indispensable) ou bien des objets possédant une structure non figée comme les DOM HTML (pratique mais à la base de pas mal de code spaghetti).
Bref, les dynamiques ça peut être utile dans la pratique, mais ce n’est pas vraiment une nouvelle feature améliorant C# (comme les autres ajouts jusqu’à maintenant). Le danger de supporter un tel type est-il compensé par les quelques avantages qu’il procure ? C’est là que dynamique rime avec polémique !
Pour moi la réponse est non, mais je suis certain que ceux qui doivent jongler avec du COM ou des DOM Html penseront le contraire.
J’arrête de faire le grognon pour vous montrer un peu mieux la syntaxe. Car malgré tout le dynamisme n’est pas une invitation au chaos. Enfin si. Mais un chaos localisé. C’est à dire que l’appel à une méthode non existante reste impossible partout, sauf pour un objet déclaré avec le nouveau type “dynamic” :
1: dynamic x;
2: x = Machin.ObtientObjetDynamique();
3: x.MethodeA(85); // compile dans tous les cas
4:
5: dynamic z = 6; // conversion implicite
6: int i = z; // sorte de unboxing automatique
7:
Bien entendu le “dynamisme” est total : cela fonctionne sur les appels de méthodes autant que sur les propriétés, les délégués, les indexeurs, etc.
Le compilateur va avoir pour charge de collecter le maximum d’information sur l’objet dynamique utilisé (comment il est utilisé, ses méthodes appelées…), charge au runtime du Framework de faire le lien avec la classe de l’instance qui se présentera à l’exécution. C’est du late binding avec tout ce qui va avec notamment l’impossibilité de contrôler le code à la compilation.
A vous de voir, mais personnellement je déconseille fortement l’utilisation des dynamiques qui sont comme un gros interrupteur ajouté en façade de C# “Langage Fortement Typé On/Off”. Restez dans le mode “On” et ne passez jamais en mode “Off” !
Covariance et Contravariance ou le retour de l’Octothorpe
J’adore le jargon de notre métier. “Comment passer pour un hasbeen en deux secondes à la machine à café” est une mise en situation comique que j’utilise souvent, certainement influencé par mon passé dans différentes grosses SSII parisiennes et par la série Caméra Café de M6…
Ici vous aurez l’air stupide lorsque quelqu’un lancera “Alors t’en penses quoi de la contravariance de C#4.0 ?”… L’ingé le plus brillant qui n’a pas lu les blogs intéressants la veille sera dans l’obligation de plonger le nez dans son café et de battre en retraire piteusement, prétextant un truc urgent à finir…
Covariance et contravariance sont des termes académiques intimidants. Un peu comme si on appelait C# “C Octothorpe”. On aurait le droit. Octothorpe est l’un des noms du symbole #. Mais franchement cela serait moins sympathique que “do dièse” (C# est la notation de do dièse en américain, à condition de prononcer le # comme “sharp” et non “square” ou “octothorpe”).
Un support presque parfait sous C# 1 à 3
Un peu comme monsieur Jourdain faisait de la prose sans le savoir, la plupart d’entre nous a utilisé au moins la covariance en C# car il s’agit de quelque chose d’assez naturel en programmation objet et que C# le supporte pour la majorité des types. D’ailleurs la covariance existe depuis le Framework 2.0 mais pour certains cas (couverts par C# 4.0) il aurait fallu émettre directement du code IL pour s’en servir.
C# 4.0 n’ajoute donc aucune nouvelle fonctionnalité ou concept à ce niveau, en revanche il comble une lacune des versions 1 à 3 qui ne supportaient pas la covariance et la contravariance pour les délégués et les interfaces dans le cadre de leur utilisation avec les génériques. Un cas bien particulier mais devant lequel on finissait pas tomber à un moment ou un autre.
Un besoin simple
C# 4.0 nous assure simplement que les choses vont fonctionner comme on pourrait s’y attendre, ce qui n’était donc pas toujours le cas jusqu’à lors.
Les occasions sont rares où interfaces et délégués ne se comportent pas comme prévu sous C#, très rares. Mais cela peut arriver. Avec C# 4.0 ce sont ces situations rares qui sont supprimées. De fait on pourrait se dire qu’il n’y a rien à dire sur cette nouveauté de C# 4.0 puisqu’on utilisait la covariance et la contravariance sans s’en soucier et que la bonne nouvelle c’est qu’on va pouvoir continuer à faire la même chose !
Mais s’arrêter là dans les explications serait un peu frustrant.
Un exemple pour mieux comprendre
Supposons les deux classes suivantes :
1: class Animal{ }
2: class Chien: Animal{ }
La seconde classe dérive de la première. Imaginons que nous écrivions maintenant un délégué définissant une méthode retournant une instance d’un type arbitraire :
1: delegate T MaFonction<T>();
Pour retourner une instance de la classe Chien nous pouvons écrire :
1: MaFonction<Chien> unChien = () => new Chien();
Vous noterez l’utilisation d’une expression Lambda pour définir le délégué. Il s’agit juste d’utiliser la syntaxe la plus concise. On pourrait tout aussi bien définir d’abord une fonction retournant un Chien, lui donner un nom, puis affecter ce dernier à la variable “unChien” comme dans le code ci-dessous :
1: public Chien GetChien()
2: {
3: return new Chien();
4: }
5:
6: MaFonction<Chien> unChien = GetChien; // sans les () bien sur !
7:
Partant de là, il est parfaitement naturel de se dire que le code suivant est valide :
1: MaFonction<Animal> animal = unChien;
En effet, la classe Chien dérivant de Animal, il semble légitime de vouloir utiliser le délégué de cette façon. Hélas, jusqu’à C# 3.0 le code ci-dessus ne compile pas.
La Covariance
La covariance n’est en fait que la possibilité de faire ce que montre le dernier exemple de code. C# 4.0 introduit les moyens d’y arriver en introduisant une nouvelle syntaxe. Cette dernière consiste tout simplement à utiliser le mot clé “out” dans la déclaration du délégué:
1: delegate T MaFonction<out T>();
Le mot clé “out” est déjà utilisé en C# pour marquer les paramètres de sortie dans les méthodes. Mais il s’agit bien ici d’une utilisation radicalement différente. Pourquoi “out” ? Pour marquer le fait que le paramètre sera utilisé en “sortie” de la méthode.
La covariance des délégués sous C# 4.0 permet ainsi de passer un sous-type du type attendu à tout délégué qui produit en sortie (out) le type en question.
Si vous pensez que tout cela est bien compliqué, alors attendez deux secondes que je vous parle de contravariance !
La Contravariance
Si la covariance concerne les délégués et les interfaces utilisés avec les types génériques dans le sens de la sortie (out), et s’il s’agit de pouvoir utiliser un sous-type du type déclaré, ce qui est très logique en POO, la contravariance règle un problème inverse : autoriser le passage d’un super-type non pas en sortie mais en entrée d’une méthode.
Un exemple de contravariance
Pas de panique ! un petit exemple va tenter de clarifier cette nuance :
1: delegate void Action1<in T>(T a);
2:
3: Action1<Animal> monAction = (animal) => { Console.WriteLine(animal); };
4: Action1<Chien> chien1 = monAction;
Bon, ok. Paniquez. !!!
Ici un délégué est défini comme une méthode ayant un paramètre de type arbitraire. Le mot clé “in” remplace “out” de la covariance car le paramètre concerné est fourni en entrée de la méthode (in).
La plupart des gens trouve que la contravariance est moins intuitive que la covariance, et une majorité de développeurs trouve tout cela bien complexe. Si c’est votre cas vous êtes juste dans la norme, donc pas de complexe :-)
La contravariance se définit avec le mot clé “in” simplement parce que le type concerné est utilisé comme paramètre d’entrée. Encore une fois cela n’a rien à voir avec le sens de “in” dans les paramètres d’entrée des méthodes. Tout comme “out” le mot clé “in” est utilisé ici dans un contexte particulier, au niveau de la déclaration d’un type générique dans un délégué.
Avec la contravariance il est donc possible de passer un super-type du type déclaré. Cela semble contraire aux habitudes de la POO (passer un sous-type d’un type attendu est naturel mais pas l’inverse). En réalité la contradiction n’est que superficielle. Dans le code ci-dessus on s’aperçoit qu’en réalité “monAction” fonctionne avec n’importe quelle instance de “Animal”, un Chien étant un Animal, l’assignation est parfaitement légitime !
M’sieur j’ai pas tout compris !
Tout cela n’est pas forcément limpide du premier coup, il faut l’avouer.
En réalité la nouvelle syntaxe a peu de chance de se retrouver dans du code “de tous les jours”. En revanche cela permet à C# de supporter des concepts de programmation fonctionnelle propres à F# qui, comme par hasard, est aussi fourni de base avec .NET 4.0 et Visual Studio 2010. Covariance et contravariance seront utilisées dans certaines librairies et certainement dans le Framework lui-même pour que, justement, les délégués et les interfaces ainsi définis puissent être utilisés comme on s’y attend. La plupart des développeurs ne s’en rendront donc jamais compte certainement… En revanche ceux qui doivent écrire des librairies réutilisables auront tout intérêt à coder en pensant à cette possibilité pour simplifier l’utilisation de leur code.
Et les interfaces ?
Le principe est le même. Et comme je le disais la plupart des utilisations se feront dans des librairies de code, comme le Framework l’est lui-même. Ainsi, le Framework 4.0 définit déjà de nombreuses interfaces supportant covariance et contravariance. IEnumerable<T> permet la covariance de T, IComparer<T> supporte la contravariance de T, etc. Dans la plupart des cas vous n’aurez donc pas à vous souciez de tout cela.
Lien
La documentation est pour l’instant assez peu fournie, et pour cause, tout cela est en bêta ne l’oublions pas. Toutefois la sortie de VS2010 et de .NET 4.0 est prévue pour Mars 2010 et le travail de documentation a déjà commencé sur MSDN. Vous pouvez ainsi vous référer à la série d’articles sur MSDN : Covariance and Contravariance.
Conclusion
Les nouveautés de C# 4.0, qui peuvent toujours changer dans l’absolu puisque le produit est encore en bêta, ne sont pas à proprement parler des évolutions fortes du langage. On voit bien que les 3 premières versions ont épuisé le stock de grandes nouveautés hyper sexy comme les génériques ou Linq qui ont modifié en profondeur le langage et décuplé ses possibilités.
C# 4.0 s’annonce comme une version mature et stable, un palier est atteint. les nouveautés apparaissent ainsi plus techniques, plus “internes” et concernent moins le développeur dans son travail quotidien.
Une certaine convergence avec F# et le DLR pousse le langage dans une direction qui ouvre la polémique. Je suis le premier a resté dubitatif sur l’utilité d’une telle évolution surtout que la sortie de F# accompagnera celle de C# 4.0 et que les passionnés qui veulent à tout prix coder dans ce style pourront le faire à l’aide d’un langage dédié. Mélanger les genre ne me semble pas un avantage pour C#.
C# est aujourd’hui mature et il est peut-être temps d’arrêter d’y toucher…
L’ensemble .NET est d’ailleurs lui-même arrivé à un état de complétude qu’aucun framework propriétaire et cohérent n’avait certainement jamais atteint.
.NET a tout remis à plat et à repousser les limites sur tous les fronts.
On peut presque affirmer que .NET est aujourd’hui “complet”. Même si la plateforme va encore évoluer dans l’avenir. Mais tous les grands blocs sont présent, des communications à la séparation code / IHM, des workflows aux interfaces graphiques et multitouch, de LINQ au Compact Framework.
Quand un système arrive à un haut niveau de stabilité, le prochain est déjà là, sous notre nez mais on le sait pas. Le palier atteint par .NET 4.0 marque une étape importante. Cet ensemble a coûté cher, très cher à développer. Il s’installe pour plusieurs années c’est une évidence (et une chance !). Mais on peut jouer aux devinettes : quelle sera la prochaine grande plateforme qui remplacera .NET, quel langage remplacera C# au firmament des langages stars pour les développeurs dans 10 ans ?
Bien malin celui qui le devinera, mais il est clair que tout palier de ce type marque le sommet d’une technologie. De quelle taille est le plateau à ce sommet ? Personne ne peut le prédire, mais avec assurance on peut affirmer qu’après avoir grimpé un sommet, il faut le redescendre. Quelle sera la prochaine montagne à conquérir ? Il y aura-t-il un jour un .NET 10 ou 22 ou bien quelque chose d’autre, de Microsoft ou d’un autre éditeur, l’aura-t-il supplanté ?
C’est en tout cas une réalité qui comme l’observation des espaces infinis qu’on devine dans les clichés de Hubble laisse songeur…
Rêver bien, mais surtout et pour les dix ans à venir : Stay Tuned !
L’une des avancées les plus intéressantes introduite dans Silverlight 2 (puis reprise sous WPF et naturellement sous Silverlight 3) est très certainement le Visual State Manager. Gestionnaire des états visuels simplifiant la conception visuelle des contrôles (UserControl). Bien utiliser le VSM, outre de rendre plus simple la représentation des états visuels d’un composant, apporte aussi une clarification essentielle à la gestion des transitions entre ces derniers.
Etats et transitions
Un contrôle peut être ou non visuel. Un Timer n’est pas visuel. Une Combobox l’est. Nous parlerons bien entendu ici uniquement des contrôles qui possèdent un visuel.
Un Etat visuel peut être compris comme une allégorie d’un ou plusieurs états logiques du contrôle. Les états logiques sont ceux définis dans le code fonctionnel, comme par exemple IsEnabled ou IsChecked. Il ne faut pas confondre état et propriété. Les états sont le plus souvent représentés par des propriétés (ils peuvent simplement être des champs internes ou le résultat d’un calcul) mais toute propriété ne représente pas un état (la couleur Foreground ou le type de curseur en sont des exemples).
je parle d’allégorie car comme je l’évoque dans le billet “Le défit des nouvelles interfaces Silverlight et WPF – La cassure conceptuelle”, la représentation visuelle d’un contrôle (et de ses états) est l’aboutissement d’une démarche intellectuelle et conceptuelle qui a justement pour but de créer un univers dans lequel les acteurs (les contrôles) doivent chacun avoir leur place et leur comportement. Entre visuel et fonctionnel il n’y a pas de relation d’identité (au sens d’un schéma conceptuel de données - relation de type 1-1). Le visuel créé une identité pour l’acteur, ce qui est différent et ne s’entend pas dans le même sens.
Je disais aussi qu’un état visuel représente un ou plusieurs états logiques. Dans certains cas deux (ou plus) états logiques peuvent être “résumés” par un seul état visuel. Cette combinaison prend son sens ponctuellement, au cas par cas, et ne peut pas être généralisée en une règle absolue. Néanmoins, lorsqu’on créé le visuel d’un contrôle, il faut garder à l’esprit cette possibilité. L’œil peut discerner de nombreuses subtilités dans les formes et les couleurs alors qu’il lui faut plus de temps pour interpréter des séries de données chiffrées ou textuelles…
Les transitions ont aussi leur importance. On ne passe pas d’un état visuel à un autre de façon abrupte, sauf si cela est volontairement assumé. Les transitions fluidifient l’interface, créent une continuité visuelle. Si les transitions ne portent pas de sens en elle-mêmes, à la différence des états, elles jouent un rôle important dans l’expérience utilisateur (UX).
Trois phases
La conception des états visuels et des transitions peut se découper en trois phases différentes.
Phase statique
Cette phase de la conception consiste à créer des états dans le VSM et à définir l’aspect du contrôle dans chacun d’eux. On en profite pour identifier les groupes d’états.
Au sein de chaque groupe les états sont mutuellement exclusifs, les états de différents groupes étant indépendants et simultanés (une Checkbox peut à la fois avoir le focus et être cochée ou décochée par exemple. IsChecked appartient à une groupe d’états différent de celui définissant l’aspect visuel pour le focus).
Lors de la phase de conception statique on se préoccupe de l’aspect que prend le contrôle dans chacun des états. On vérifie aussi la compatibilité visuelle entre les états des différents groupes d’états. Si cette étape est qualifiée de statique c’est pour souligner qu’on ne se soucie pas encore des transitions (la dynamique visuelle), mieux vaut rester concentré sur les groupes d’états et les états eux-mêmes.
On veillera à ne pas manipuler une même propriété du contrôle dans plusieurs groupes d’états pour des raisons de cohérence. D’ailleurs si on commet cette imprudence Blend le signalera par un petit symbole de danger (triangle assorti d’un message de type tooltip). On peut assumer une telle situation si on en mesure toutes les conséquences, c’est pourquoi Blend signale le problème mais n’interdit pas la situation. Mais en règle générale cette alerte trahit une mauvaise conception !
Etat “Base”
Vous l’avez peut-être remarqué, dès qu’un groupe d’états existe le VSM ajoute systématiquement à la liste un état spécial appelé Base. Cet état permet de visualiser le contrôle hors de tous les états visuels définis. Toute modification effectuée dans l’état Base se propage à tous les états définis et n’est enregistré dans aucune time-line.
Etat par défaut
Un contrôle bien conçu devrait posséder pour chaque groupe d’états un état par défaut. En effet l’état Base n’existe pas en tant qu’état visuel, il s’agit juste d’un mode d’édition spécial du VSM. Lorsqu’une instance du contrôle est créée il est forcément dans un état donné (par exemple IsChecked=False pour une case à cocher). Il se trouve même souvent dans plusieurs états précis qui seront représentés par plusieurs groupes d’états. De fait il ne faut pas se reposer sur l’état Base mais plutôt créer un état par défaut pour chaque groupe qui traduit l’aspect visuel du contrôle lorsqu’il vient d’être initialisé (ou “remis à zéro” si une telle fonction est disponible dans le contrôle). Dans les contrôles existants vous pouvez remarquer que de tels états par défaut existent. Par exemple dans le groupe CommonStates de la classe Button on trouve un état Normal, dans le groupe CheckedStates (d’une case à cocher) on trouve UnChecked, etc.
L’état par défaut de chaque groupe ne doit pas forcément porter un même nom (ce qui poserait un problème pour le VSM de toute façon, il n’autorise pas que deux états portent le même nom). Au contraire, cet état par défaut de chaque groupe doit, comme dans les exemples donnés ci-dessus, porter un nom ayant un sens en rapport avec le groupe.
Il est parfaitement valide de créer un état par défaut (ou non) qui ne fait que recopier la situation de l’état Base. Il suffit de créer l’état et de ne rien modifier… Cela permet souvent de clarifier les choses. Prenons l’exemple de la Checkbox, la croix est ajoutée dans l’état Base mais est cachée. En revanche cela ne lève pas l’obligation de créer un état Unchecked dans le groupe CheckedStates. Cet état est juste créé mais non modifié puisqu’il reprend l’aspect de l’état Base (croix cachée).
Validation des états
Arrivé ici il faut tester tous les états. Cela peut se faire sous Blend mais il ne faut pas hésiter à créer une fiche de test, y placer le contrôle, et ajouter des boutons, sliders, et autres éléments d’interface pour tester au runtime le changement de chaque état, la cohérence entre les groupes, etc.
Dans certains cas très simples la conception des états visuels peut s’arrêter là. On peut vouloir des transitions franches et immédiates et il n’y a alors plus rien à ajouter.
Dans d’autres cas il s’avère essentiel d’aborder la seconde phase.
Phase des transitions
La version simple consiste à utiliser le réglage par défaut que le VSM propose pour chaque groupe d’états. Dans de nombreux cas cela peut être suffisant. Il suffit alors de définir un temps pour l’ensemble des transitions. Le VSM calculera à l’exécution les animations correspondantes.
La version plus évoluée consiste à utiliser les options du VSM pour chaque état afin de régler les transitions d’entrées et de sorties de celui-ci. Il peut en effet s’avérer nécessaire d’avoir des timings différents selon le sens de la transition et l’état précédent. Par exemple il est courant que les états visuels de type clic souris soient instantanés pour ne pas donner l’impression à l’utilisateur que le logiciel est “mou”, lent à répondre, alors que le relâchement de la souris peut au contraire accepter un temps assez long, donnant une sensation de douceur, d’amorti “luxueux”.
Encore une fois les choses peuvent s’arrêter là. Bien entendu après avoir testé toutes les transitions. N’oubliez pas que Blend 3 propose désormais une option permettant de visualiser en conception l’effet des transitions lorsque le VSM est actif. Cela fonctionne très bien, sauf pour les transitions utilisant des storyboards.
Phase des transitions dynamiques
Justement le troisième niveaux de personnalisation consiste à créer des storyboards au lieu de se contenter des timings réglés dans le VSM. Dans ce cas on créé une animation complète pour la transition en utilisant les fonctions des storyboards (time line, boucle for-ever, autoreverse…).
Pouvant s’utiliser dans les storyboards ou sur les animations par défaut créées par le VSM, les fonctions de ease in et ease out permettent d’ajouter une touche “organique”, plus naturelle, aux animations. Silverlight 3 propose de nombreux modes (comme le rebondissement par exemple) qui, bien utilisés, finissent le visuel et le rende plus “pro”.
Relation avec le code
Il y a toujours eu deux aspects à la création d’un contrôle personnalisé, même sous Win32 avec les MFC ou sous Delphi avec la VCL. La création du visuel d’un côté et celle du code fonctionnel de l’autre. Tout a changé mais pas cette séparation qui s’est, au contraire, renforcée.
Le designer (l’infographiste) conçoit le visuel d’un contrôle, il prévoit comment le contrôle se comportera pour l’utilisateur, comment il s’animera, comment ses différents états seront visualisés.
Pour l’informaticien le point de vue est différent. Il ne doit pas se soucier de l’aspect visuel mais du comportement du contrôle, de sa logique sous-jacente.
Cela implique de prendre en compte tout ce qui peut modifier les états internes du contrôle, la cohérence de la machine logique lors de son fonctionnement. Que les changements d’état soient le fait de l’utilisateur, d’une animation créée par le designer ou de tout autre acteur, peu importe.
Il faut oublier le visuel qui d’ailleurs n’existe pas forcément (la séparation du travail design / codage est telle qu’on peut aujourd’hui commencer un projet par une réflexion purement conceptuelle et graphique avec un designer plutôt que d’écrire le cahier des charges avec un informaticien…).
Repérer les ilots d’états (qui deviendront ou non des groupes d’états dans la partie visuelle), le ou les graphes de changement d’état, vérifier quels sont les graphes d’états interconnectés (et qui forment un groupe ou des sous-groupes) de ceux qui sont totalement indépendants, tout cela est essentiel.
L’initialisation
On retrouve ici une préoccupation déjà évoquée quand nous parlions de la partie visuelle. Pour le designer il s’agissait de ne pas confondre l’état Base avec les états par défaut qu’il faut créer pour chaque groupe d’états.
Côté code il est indispensable que toute nouvelle instance d’un contrôle se “positionne” correctement. Normalement son initialisation comporte d’une façon ou d’une autre des valeurs par défaut. Sous C# il est possible d’atteindre cet objectif de multiples façons : soit par le biais de champs initialisés lors de leur déclaration, soit par le biais de valeurs par défaut mises en place dans les métadonnées des propriétés de dépendance, soit par code dans le constructeur de la classe, soit par code dans le gestionnaire de l’événement Loaded du contrôle (ou de l’une de ses sous-parties), soit encore par code XAML.
Le fait que ces différentes solutions puissent être utilisées simultanément dans un même contrôle n’aide pas forcément à rendre les choses claires… C#, comme tout langage, n’interdit pas le code spaghetti, hélas !
Mais ici en dehors de la pure stylistique, de l’académisme, voire même des bonnes pratiques, c’est aussi la stabilité visuelle qui risque d’être compromise si le contrôle ne s’initialise pas clairement dans une suite d’états bien déterminés (généralement les états par défaut).
Un contrôle a ainsi la charge lors de son initialisation de se positionner sur la case “départ” de son graphe d’états. Cela semble évident mais parfois les portes ouvertes sont celles qui méritent le plus d’être enfoncées. Tout le monde peut voir qu’une porte est fermée. Il faut être très perspicace pour s’apercevoir que l’absence d’un obstacle ne signifie pas qu’il n’y a rien à faire…
Car si rien n’est fait, le contrôle va être frappé d’une sorte de schizophrénie : il peut être doté d’un code qui s’initialise correctement et d’une interface qui en fait tout autant, hélas les deux personnalités existent simultanément et ne se connaissent pas forcément. Les cas de personnalités multiples sont passionnants en psychanalyse (je vous conseille d’ailleurs un excellent vieux livre écrit par une patiente atteinte de ce syndrome : Joan, autobiographie d’une personnalité multiple) et même s’il est plus facile de déboguer un programme qu’un cerveau humain, ce type de désordre du comportement ruine totalement les efforts pour créer une interface riche et cohérente… Alors autant y penser ! Cela signifie dans la pratique que vous devez centraliser l’initialisation logique de votre contrôle (l’ensemble de ses états) et l’initialisation visuelle. Cette dernière s’effectue en général en appelant GotoState avec le paramètre d’animation à false (il s’agit d’une bonne pratique. On évite d’animer les contrôles lorsqu’ils s’initialisent). Ainsi, code et visuel sont initialisés conjointement et sont en phase.
Encore une fois ne prenez pas Base pour un état. Notamment il n’existe aucune transition gérée par le VSM entre Base et les autres états. Si votre contrôle n’est pas volontairement initialisé dans ses états par défaut aucune animation ne sera jouée lors du premier changement car Base vers un état ne génère aucune transition.
Conclusion
La création d’un contrôle est une opération longue et minutieuse car elle doit être réfléchie. Et plutôt deux fois qu’une : à la fois sous l’angle visuel et sous l’angle du code. Ces deux aspects bien séparés aujourd’hui introduisent la nécessité de penser à leur indispensable synchronisation pour garantir la cohérence du contrôle.
Mais avec un peu d’habitude on s’aperçoit bien vite qu’il est mille fois plus facile de créer un nouveau contrôle visuel avec Blend (que cela soit pour Silverlight ou WPF) qu’avec les technologies précédentes. Alors cela vaut bien un peu d’attention au départ !
Bonne conception !
N’oubliez pas que seul Blend (qui hélas n’existe pas en version express gratuite) permet sérieusement de travailler sous Silverlight et WPF. N’hésitez pas à acheter ce complément indispensable ou à vous le faire offrir (après tout, un Blend 3 pour Noël c’est mieux qu’une cravate ou une bague – pas de sexisme sur Dot.Blog! – pensez-y ! )
Et Stay Tuned !
Silverlight, comme vous le savez, propose dans quelques méga octets un mini framework .NET qui est tellement bien "découpé" que la plupart du temps on ne se rend même pas compte qu'il manque des dizaines de méga de code binaire... Pourtant entre une installation complète du Framework 3.5 ou 4.0 à venir et l'installation du plugin Silverlight il y a comme une énorme différence ! De deux choses l'une, soit Silverlight ne sait rien faire tellement il est diminué par ce découpage, ce qui n'est pas le cas, soit le Framework complet est juste gonflé avec des fichiers inutiles pour "faire sérieux", ce qui n'est pas le cas non plus :-)
Un découpage savant
Donc ce n'est ni l'un ni l'autre. La troisième possibilité, qui est la bonne réponse, est que le Framework complet est d'une richesse infinie dans les moindres détails, et que le Framework Silverlight "ruse" en zappant beaucoup de détails mais sans perdre l'essentiel. Cela donne l'impression qu'on peut tout faire en Silverlight comme en WPF. C'est "presque" vrai. Assez rapidement on tombe sur les fameux petits détails qui manquent. Cela implique de les compenser par du code.
Cela dit loin d'être une critique négative de Silverlight s'en est au contraire une apologie ! Je trouve en effet le découpage savant qui a été effectué dans Silverlight particulièrement bien fait. L'approche est très sensée : il est rarissime (même impossible) qu'une application utilise et nécessite d'accéder à toutes les méthodes, toutes les propriétés de toutes les classes du Framework tellement celui-ci est riche. Du coup, en faisant des coupes bien choisies, on peut laisser les squelettes de presque tout le Framework ainsi que les principales méthodes et propriétés utilisées le plus souvent. On obtient le Framework Silverlight dans lequel un code standard trouvera 95% de tout ce qu'il lui faut pour tourner. Beaucoup d'applications simples ne verront même pas qu'il manque quelque chose. En revanche pour les autres cas, le développeur ajoutera les contournements nécessaires ce qui grossira un peu son code binaire Silverlight, mais d'une petite fraction très supportable. Rien à voir avec le coût d'une installation du Framework complet.
Il n'en reste pas moins vrai que parfois pour des choses très simples on se retrouve un peu le bec dans l'eau. "Tiens, c'est bizarre, j'aurais juré que la méthode xxx existait !" Et non, on n'a pas rêvé, elle existe bien, mais dans le Framework complet, pas dans celui de Silverlight. Un exemple tout simple : la méthode GetValues() de la classe Enum.
Enum.GetValues où es tu ?
Un cas d'utilisation très basique qui fait voir immédiatement qu'il manque quelque chose : essayez de faire un binding entre une combobox et une énumération. Truc classique par excellence.
Que le binding soit fait par Xaml ou bien par code, à un moment où un autre il faut que l'énumération sache retourner la liste de ses valeurs. C'est justement la fonction de la méthode Enum.GetValues().
Mais dans le Framework Silverlight cette méthode n'existe tout simplement pas. Victime de la cure d'amaigrissement évoquée plus haut. Il ne s'agit donc ni d'un oubli ni d'un dommage collatéral, c'est un parti pris, assumé.
Et alors on fait comment ?
Assumé, assumé... comme il y va ! Non, je vous l'assure, c'est assumé. Par l'équipe qui développe le Framework Silverlight en tout cas. Mais pas forcément par les développeurs qui l'utilisent ! Car pour eux c'est une autre histoire puisque, en effet, il va falloir réinventer cette méthode.
A l'aide d'un peu de Linq to Object et de Reflexion, on peut s'en sortir.
Linq et Reflexion
Il existe en effet un moyen d'obtenir les valeurs d'une Enum par la réflexion, en utilisant la méthode GetFields() sur le type de l'Enum dont on souhaite obtenir les valeurs.
GetFields() retourne un tableau de FieldInfo. Une Enum présente alors ses différentes valeurs comme un tableau de champs. En plus de ces champs, GetFields() retournera aussi des éléments qui ne sont pas des valeurs de l'énumération mais d'autres champs de la classe. Au sein de FieldInfo vous trouverez un ensemble de méthodes nommées Isxxx(), l'une d'entre elles nous intéresse plus particulièrement ici; c'est IsLiteral. Toutes les valeurs de l'énumération retournent True. La solution est alors simple en ajoutant à la Réflexion un peu de Linq to Object :
1: var enumType = typeof(monEnumeration);
2: var fields = from field in enumType.GetFields()
3: where field.IsLiteral
4: select field.GetValue(null).ToString();
5: LaCombobox.ItemsSource = fields;
A partir du type de l'énumération (ligne 1) on construit une requête Linq to Object qui filtre tous les champs ayant IsLiteral à vrai et qui sélectionne la valeur de ce champ sous la forme d'une string.
Ne reste plus qu'à faire le binding entre cette requête Linq et ItemsSource de la combo box.
Il faudra ajouter un peu de code pour transformer la chaîne sélectionnée dans la combo en une valeur de l'énumération grâce à un appel à Enum.Parse().
C'est la version simple et courte. Bien entendu dans le cas où on souhaite faire du binding plus automatisé, notamment directement en Xaml, la solution donnée ici est un peu trop simple. L'esprit est le bon mais il manque des petites choses comme un convertisseur de valeurs.
D'autres versions plus sophistiquées
Il est bien sûr possible d'aller plus loin et de formuler une solution plus sophistiquée qui permettent de faire du binding en Xaml notamment. Je vous laisse y réfléchir, ça fait un bon excercice C# et ce n'est pas un MVP C# qui vous dira que s'entraîner mentalement de la sorte sur le langage est inutile ! :-)
Mais sachez que d'autres y ont déjà pensé et ont proposé des solutions souvent assez proches (ce problème ne peut pas être résolu de dix milles façons). En voici quelques unes dans lesquelles vous pourrez piocher matière à aller plus loin :
Silverlight c'est sympa et en plus ça fait travailler les méninges, que du bon !
Stay Tuned !
Silverlight est conçu pour s'intégrer dans une chaîne de développement "sérieuse". Son but est principalement la création de RIA (Rich Internet Application) le plus souvent associées à des applications métier. Il s'agit en fait de porter WPF sur le Web et de faire ainsi converger applications "desktop" et applications Web de façon transparente. Mais Silverlight est aussi un merveilleux outil pour écrire des jeux en ligne (ou même desktop grâce à la fonction Out-Of-Browser). Plus loin, et j'en parle souvent, les techniques visuelles du jeu ne sont pas à négliger pour créer des applications professionnelles disposant d'interfaces innovantes.
Raison de plus pour se pencher sur la question. Dans les jours passés je vous ai parlé de la gestion du son (notamment en vous proposant un petit composant capable de rendre possible la synchronisation son/animation - AnimatableSoundPlayer. Ou comment synchroniser du son sur une animation Silverlight et Animations, Sons, Synchronisation, Ease in/out sous Blend), aujourd'hui je vais aborder la gestion du clavier. Pour ce qui est de la boucle de jeu, je vous renvoie à mon billet exposant l'exemple de la neige qui tombe (« Il neige » ou les boucles d’animation, les fps et les sprites sous Silverlight ).
D'abord un petit exemple live. Une fois que vous aurez cliqué sur le rectangle "click here to start" vous pourrez déplacer le carré rouge et changer sa taille. Les déplacements se font à l'aide des 4 flèches de direction. Pour agrandir l'objet on utilise la combinaison de touche Shift-KeyUp (et Shift-KeyDown pour diminuer la taille). La touche Ctrl est un accélérateur pour les mouvements. Ctrl-KeyUp/Down/Left/Right permet donc d'aller plus vite. Ce qui est intéressant dans cet exemple est bien entendu la détection des appuis simultanés sur les touches et la gestion de ceux-ci. Jouer deux secondes et on y revient :
Tout le problème ici est donc de détecter plusieurs touches appuyées en même temps. Silverlight propose une gestion de clavier très simple : les événements KeyDown et KeyUp. Avec ça il faut se débrouiller.
Pour une gestion de déplacement rudimentaire (les 4 directions par les 4 flèches) cela pourrait parfaitement convenir. Mais comment gérer les déplacements en diagonal (KeyUp+KeyLeft par exemple) ou pire des choses du genre Shift-Ctrl-KeyUp/Barre d'espace ? Il faudrait se rappeler dans l'événement KeyDown si l'une ou l'autre des touches a été enfoncée et si elle est toujours Down... De plus aucune assurance de l'ordre dans lequel les touches vont arriver (la simultanéité n'existe pas du point de vue du pilote clavier, une touche est forcément avant l'autre).
Bref cela semble d'un seul coup se compliquer énormément !
Oui et non. Oui puisque la solution n'est pas directe, non parceque celle-ci n'est pas si compliquée à trouver.
La classe KeyLogger
Ne cherchez pas dans l'aide du Framework Silverlight, cette classe n'existe pas :-) Il s'agit justement de la classe que je vous propose aujourd'hui de créer pour gérer le petit problème des touches simultanées.
Problématique
On doit pouvoir à tout moment dans la boucle principale du jeu tester si certaines combinaisons de touches sont actives ou non. On doit aussi pouvoir tester l'appui sur une seule touche.
Solution
Il n'y a pas de miracle, pour savoir si une ou dix touches sont appuyées il faut se souvenir de toutes les touches ayant reçu un KeyDown et pas encore de KeyUp. Se souvenir, en informatique, cela signifie stocker des données. C'est bien ce que nous allons faire en créant un tableau dont les éléments seront des booléens et dont l'index sera la valeur de l'énumération Key (classe du Framework listant les touches accessibles du clavier). Ne reste plus qu'à gérer nous-mêmes les KeyUp et KeyDown pour mettre à vrai/faux les cases correspondantes dans le tableau. Pour tester une combinaison de touches (ou une touche) il suffit alors d'interroger le tableau pour savoir si les touches que nous voulons tester sont toutes à true.
Le code
Une fois le problème et la solution posés, il ne reste plus qu'à coder. Plutôt que d'alourdir ce post, je vous renvoie au code source du projet téléchargeable en fin de billet. Ouvrez-le sous Blend ou Visual Studio, vous comprendrez rapidement comment il fonctionne.
Le UserControl principal de l'application exemple commence par créer une instance du KeyLogger et l'attache à lui-même. On pourrait fort bien attacher un KeyLogger différent à différentes zones écran, ici nous optons pour un seul gestionnaire centralisé.
La détection du clavier ne marche que si le contrôle a le focus. Donner par défaut celui-ci au UserControl n'est pas forcément évident, je n'ai pas trouvé de solution qui marche systématiquement. D'où la petite astuce du bouton de démarrage : en cliquant dessus je lance l'animation qui le fait disparaître et qui diminue l'opacité des instructions mais surtout j'en profite de réclamer le focus. Cette technique a l'avantage de fonctionner à tous les coups.
Conclusion
Remplacer le rectangle rouge par une petite fusée, ajoutez des blocs rocheux, détectez la barre d'espace pour tirer, utilisez la gestion de sprites pour animer les tirs de laser, agrémentez d'une gestion de collision, complétez par une gestion de l'inertie des mouvements... et vous aurez une réplique de Asteroid ! Yaka. :-)
Déplacer des objets ou contrôler des animations via le clavier est l'une des bases de la création de jeux. Avec l'exemple de la neige qui tombe nous avions déjà vu la gestion des sprites, vous disposez maintenant des briques pour commencer à réfléchir à des petits jeux sympas (éviter la cent-millième version de Asteroid ou de Space Invader bien entendu :-) )!
Amusez-vous bien, et .... Stay Tuned !
Code source du projet de la classe KeyLogger : KeyTrapper.zip (78,30 kb)