Styles

lundi 28 septembre 2009

Efficacité des tests unitaires

Les tests unitaires ne sont pas une solution à un problème, mais plutôt le symptôme de notre incapacité à produire du code correct dès le premier jet. Quand la même personne réalise le test unitaire et le code à tester, elle a toujours du mal à s'auto-critiquer, même en voulant honnêtement produire le code le plus propre possible. Un code est beaucoup plus critiqué, explicité, quand ce n'est pas la même personne qui code et qui teste. Cela me rappelle une étude citée par Steve McConnell dans "Code Complete 2nd Edition" qui m'apparut alors comme une révélation :
"Le test logiciel a une efficacité limité quand elle est utilisée seule - le taux moyen de détection d'erreur est seulement de 30 pourcent pour les tests unitaires, 35 pourcents pour les tests d'intégration et 35 pourcents pour des beta-tests de petits volumes. Par contre, l'efficacité des inspections de la conception et du code est de 55 et 60 pourcents. (Jones, Capers. 1996. "Software Defect-Removal Efficiency," IEEE Computer, April 1996)." -- Code Complete, 21.1. Overview of Collaborative Development Practices
L'inspection de code, formelle ou pas, est plus efficace que les tests unitaires et d'intégration. Avec de bonnes checklists, il est très intéressant de provoquer des critiques constructives sur chaque aspect d'une architecture et du code correspondant, en commençant par les plus sensibles. De plus, une personne porte beaucoup plus de soins à son code quand il sait que celui-ci va être inspecté par une autre personne. Une fois le squelette d'une architecture revue, il devient moins critique de relire tout le code développé sur celle-ci. Et on peut accélérer le développement comme nous le rappelle Jamie Zawinski :
"Les tests unitaires semblent biens en principe. Avec un rythme de développement tranquille, c'est certainement la voie à prendre. Mais quand on regarde de plus près, "nous devons aller de zéro à terminé en six semaines", et bien, je ne peux pas le faire à moins d'enlever quelque chose. Et ce que je vais enlever sont tous les trucs qui ne sont pas absolument critiques. Et les tests unitaires ne sont pas critiques. S'il n'y a pas de tests unitaires, le client ne s'en plaindra pas". -- Coders at Work
Quand les délais de livraison sont très courts par rapport à l'ampleur du code à développer, le ratio nombre de fonctionnalités sur le temps devient tel que le respect des délais est entièrement déterminé par la rapidité de frappe des développeurs sur leur clavier.

Et dans ce cas là, s'il n'y a pas de temps pour faire des tests unitaires, autant avoir des codeurs expérimentés codant juste du premier coup, ou sachant trouver la source de n'importe quel problème en un temps fini. Mais quand on en est à ce niveau, c'est qu'on n'est pas sure d'avoir du boulot si le logiciel à produire ne sort pas à la date cible.

Il peut être raisonnable de choisir de coder des solutions "Quick & Dirty" dans une première phase pour pouvoir respecter les délais tout en sachant qu'on contracte une dette technique importante qu'il faudra un jour payer dans une prochaine release sous peine de voir le logiciel se scléroser complètement.

Mais quel projet informatique, une fois parti sur le chemin séduisant du "quick & dirty" est prêt à payer sa dette pour revenir à un système "sain" de corps (le code) et d'esprit (la conception) ? Tant que le paiement des intérêts de la dette technique permette de continuer à développer de nouvelles fonctionnalités dans les temps impartis, qui se soucie de rembourser la dette ? s'il n'y a aucun intérêt business de nettoyer le code et l'architecture, mieux vaux continuer à coder comme avant.

J'ai rarement vu une maintenance d'un logiciel prenant régulièrement du temps sur chaque release pour corriger/mettre au propre des parties du code. J'ai toujours vu des applications devenir de grosses machines à gaz et être à terme refondue entièrement (la dette technique de l'application disparaît alors complètement). C'est peut-être malheureux à dire, mais c'est la sombre réalité du monde de l'informatique : la plupart des applications sur lesquelles nous sommes amenés à intervenir ont une énorme dette technique, et on a rarement l'espoir de voir s'améliorer une application sans que celle-ci soit entièrement refondue.

Il faut s'y faire, et autant apprendre à coder juste et de façon défensive dès le premier coup.

jeudi 17 septembre 2009

Inadaptation d’impédance objet-relationel : plus qu'une inadaptation, une incompréhension

Cet été 2009, Thomas Kyte, expert développement sous Oracle bien connu, s’est largement étendu sur son site AskTom de questions/réponses, sur l’inadaptation d’impédance objet-relationel (object-relational impedance mismatch). Sur un ton direct et tranchant, il nous délivre son point de vue sur la place des « applications » notamment Java EE et de leurs rôles dans la gestion des données. Il fini par énoncer cette règle à suivre absolument :
Ne laissez aucun développeur qui code en Java, Visual Basic, C et autres langages de ce genre, écrire du code contenant SELECT, INSERT, UPDATE, DELETE ou MERGE dedans. Donnez leur accès à une série de procédures stockées qui retournent les données dont ils ont besoin, ou qui traitent les transactions nécessaires au système lui-même.
Assez radical il est vrai, Tom ne considère les applications au dessus des bases de données que comme de simples interfaces homme-machine (en gros, de simples JSP ou pages PHP suffisent pour faire interagir un client avec le serveur de données). Pour lui, autant ne pas donner aux applications un accès direct aux tables, au modèle de données parce que les applications vont et viennent rapidement mais les données,… les données vivent pour toujours ! :

… je peux à la rigueur mettre tout le SQL dans la base de données au lieu que dans du XML, ce n’est ni un gros problème pour moi ni un gros gain d’un côté technique…

Cela m’indique (« ni un gros gain ») que vous n’avez pas fait de bases de données depuis longtemps. Votre application – la chose pour laquelle vous vous concentrez aujourd’hui en 2009 – elle n’existera plus vers 2013. Mais la donnée, elle, sera toujours là. Et si vous avez caché tout ce qui concerne les données dans votre application obsolète écrite dans un langage mort, avec des frameworks que personne ne considère plus utiliser (rappelez-vous, c’est 2013, plus 2009 maintenant) – vous nous forcerez à l’en arracher entièrement et à le recommencer.
Le reste des réponses de Tom est du même acabit. C’est comme si le monde était séparé en deux : les programmeurs sur framework et les programmeurs sur base de données. En entreprise, la plupart des applications existent pour gérer l’information, gérer des données. Les traitements associés sont rarement compliqués et ne font jamais appel à des experts en algorithmie.

Dans quel cas a-t-on réellement besoin d'une machine à gaz ORM ? Quels sont les clients qui analysent leur métier en termes d’objets ? Parmi ces clients, quels sont ceux qui comprennent toutes les notions, aspects et pièges de l’orienté-objet ?

J’ai rencontré il y a quelques temps, en 2007, un client pour lequel j’ai du réaliser un audit de sécurité sur son application Java. Quand arrive le moment de lire le code source de l’application (qui date de 2002), un effroi (les englishs diraient, « WTF » à ce moment là) m’envahi quand je réalisai que ce qui avait été codé était un véritable serveur d’application complet codé à la main, spécifiquement pour ce client !

Ah, les développeurs s’en étaient donné à cœur joie pour créer cette majestueuse machine à gaz, capable de gérer des composants aussi complexes que des EJB 1.0, inmaintenables et inmaintenu actuellement, puisque plus personne n’a plus le temps de se pencher sur son fonctionnement, les développeurs initiaux ayant changés au moins 3 fois de société de service entre temps. Et puis, ce n’est pas un simple serveur d’application minimaliste qui a été codé, mais un énorme tas de technologies du moment comme par exemple, CORBA et IIOP qui étaient en vogue au moment du développement de cette application.

J’aurais pu me dire que tout cela était nécessaire à l’époque, notamment pour supporter une charge importante, des changements de configuration fréquents, un clustering, de la réplication de sessions, de l’intégration d’autres systèmes… Que nenni ! Toute cette machinerie ne sert qu’à gérer une cinquantaine de tables dans une base oracle et ne fait qu’1 ou 2 appels vers des WebServices, 1 intégration avec un LDAP… et puis c’est tout !


Autant dire que tout ce code Java ne sert strictement à rien ! Et coûte énormément à maintenir… Une simple application générée par « Oracle Application Express » aurait suffit. Je vous passe les autres détails de l’architecture de cette application, qui révèle bien que le client n’a pas du tout investi sur sa base de données mais uniquement sur des programmes en Java… Pour quelle raison ? J’ai beau tourner maintes et maintes fois la question dans ma tête je ne vois pas de raison logique à tout ça. C’est certainement une question de mode du moment.

jeudi 10 septembre 2009

Template, Modèle de Construction, Code Snippet : une réutilisation nécessaire

Quelle est la plus importante habitude à prendre dans sa carrière d’informaticien ? On passe souvent des heures voire des jours à faire fonctionner un morceau de code, et on apprend toujours beaucoup de choses pendant ce processus de conception. La conception a cela de pervers qu’on ne connaît la bonne solution qu’une fois qu’on la conçue.

Ainsi, la conception est toujours un processus assez long et périlleux, il est donc plus que nécessaire de capitaliser sur ses expériences et les expériences des autres. La meilleure application étant celle pour laquelle on n’a à développer aucune ligne de code, la capitalisation qu’on entend ici est donc la constitution d’un ensemble de « modèles » (templates) ou « d’extrait de code » (code snippet) configurables, personnalisables et extensibles à souhait.

Quand on y pense, même pour des applications simples à concevoir comme des sites Web au dessus d’une base de données, il y a tellement d’exigences et de contraintes à prendre en compte (comme par exemple, la disponibilité, la sécurité, les performances…) qu’un cerveau humain n’est pas la plus fiable des machines pour les concevoir. Ne serait-ce que du simple fait qu’un être humain n’est pas constant et oublie vite les détails.

Ce qui motive le plus un informaticien à faire de la conception c’est l’objectif de ne jamais avoir à recoder deux fois la même chose, ou, mieux encore, ne jamais recoder deux choses similaires. Le copier/coller de code à la main (pratique la plus répandue dans notre profession, bien plus que l’exercice de réflexion) est à généraliser dans le cadre de la construction. Les « templates » permettent de générer des milliers de lignes de code et ce, sans l’introduction de bug si votre modèle est parfait.

Les templates : bien mieux qu'une photocopieuse...

La programmation orienté modèle (Template-oriented programming) devient de plus en plus pertinente du fait de la standardisation des solutions et des conceptions sous-jacentes, notamment par l’application des « modèles de conception ». A l’instar des modèles de conception, les templates de code pourraient s’appeler des « modèles de construction », puisqu’il s’agit de standardiser/automatiser la construction d’application. Autrement dit, il devient facile de récupérer des applications correctement codées comme, par exemple, les exemples d’application fournis par Spring (situées dans le bundle Spring) ou dans le Java EE SDK, d’en créer des modèles de code et d’automatiser la création d’application standard en fonction d’un modèle conceptuel des données et des traitements.

La meilleure pratique à s’imposer dès son plus jeune âge dans le métier est donc la transformation de code s’exécutant (« running code ») en code réutilisable pour d’autres projets. Personnellement, je me suis construit une petite application Web pour recueillir tous mes templates, et me permettre de générer du code en fonctions de configurations et de modélisation des informations à gérer. Mais ce genre d’outil se retrouve dans pratiquement tous les IDE dignes de ce nom par exemple Visual Studio, Eclipse, Emacs (avec aussi les abbrevs par exemple) ou dans d’autres outils innombrables (par exemple CodeSmith) …

J’essaie de passer du temps en fin de projet pour réutiliser de tout code qui a été programmé à la main. Comparez le temps nécessaire pour transformer du code en template et le temps que l’on peut gagner quand on va générer des applications avec ces nouveaux templates, et vis-à-vis du nombre de fois qu’on va générer ce code. On y gagne dans la plupart des cas. Il faut bien sur, faire attention à la licence associée au code source.

Qu’est-ce qui sépare un développeur d’un développeur expérimenté ? Le nombre de « modèles de construction » à sa disposition et sa capacité à programmer automatiquement. Faire soi-même un système de génération de code source ou d’application complète permet de maîtriser ce que l’on génère pour mieux en connaître les limites.

Qu’est-ce qui sépare un développeur expérimenté d’un guru respectable et respectée ? Le guru fait de la génération de code qui génère du code qui génère du code… Il fait de la production de modèle de construction, un processus récursif.

On peut extraire des modèles à partir de ses expériences sur des projets, mais on peut tout autant tirer parti des expériences des autres, à partir des exemples donnés dans les livres techniques. Toute lecture d’un livre doit s’accompagner d’un enrichissement de son système de génération de code. A chaque fois que je crée un modèle, je me demande « qu’est-ce qui peut être différencié dans ce code : qu’est-ce qui peut être demandé par un client, qu’est-ce qui est du ressort de l’implémentation uniquement ».

Les templates peuvent être utilisés pour autre chose que la génération de code comme par exemple :
  • générer automatiquement de la documentation (avec des formats de fichier sous format texte comme le RTF, XML par exemple)
  • générer automatiquement de la configuration de middleware comme la configuration d’Apache httpd ou tomcat, ou la configuration de progiciels…
Le domaine d’application de la génération de fichier est sans limites… Un système de gestion de templates peut servir aussi à se souvenir de l’ensemble des paramètres à saisir, l’ensemble de questions à se poser, comme une checklist ce qui permet de ne rien oublier.

A chaque fois que je code quelque chose j’essaie de me poser la question : est-ce que je serai capable de reproduire ce code dans 5 ans sans réfléchir ? Pour être sur de pouvoir ressortir ce code plusieurs années après, je le transforme en template et mon système doit permettre de générer du code qui s’exécute sans problème ou doit fournir des instructions suffisamment claires pour l’intégrer ou l’exécuter.

Et après ça, je peux oublier ce bout de code et dormir sur mes deux oreilles…

jeudi 3 septembre 2009

L'orienté objet : plus qu'un cache misère

Quels sont les caractéristiques principales d'un programme écrit par un néophyte ? des noms de variables incompréhensibles ? aucune organisation du code ? La pire, et malheureusement la plus fréquente, car inhérente à l'incompréhension de la notion d'abstraction, c'est l'utilisation généralisée de variables globales.

L'emploie de variables globales, faut-il le rappeler, est considéré comme une mauvaise pratique, du fait même de sa "non-localité" : une variable globale peut être modifiée de n'importe où (sans même parler de sa volatilité), et n'importe quelle partie d'un programme peut en être dépendante. Cela rend donc le programme inmaintenable à partir d'une certaine taille.

Vulgaire pédanterie d'un béotien en mathématique : l'emploi de variables globales et les dépendances qu'il instille entre toutes les parties d'un programme me fait penser visuellement à un pavage hyperbolique du disque de Poincaré. Il montre bien que toute chose est dépendante d'une autre et change de couleur aux croisements des autres dépendances.

L'orienté-objet force à l'abstraction mais n'empêche pas les variables globales de proliférer. Au delà d'éléments globaux d'une application, l'emploie de variables globales s'est immiscé subrepticement au cœur des classes d'objet, et le paradigme Orienté-Objet en a fait son beurre.

On pourrait même croire que, suite à la reconnaissance, par toute la communauté des programmeurs, de l'emploie de variables globales comme mauvaise pratique, des êtres mal intentionnés auraient inventés un paradigme leur permettant de programmer localement avec des variables globales, de restreindre, en fait, les variables globales à quelques fonctions.

L'orienté-objet n'est qu'une manière déguisée de programmer en utilisant des variables globales. Il pousse (mais n'oblige pas) à s'intéresser aux invariants de classe et d'objet, de savoir, en fait, gérer des variables globales pour qu'elles ne posent aucun problème lors de l'appel des méthodes.

Nombreux ont été les échecs quant à la préservation de l'intégrité des données d'une classe vis à vis de ses méthodes. Il est apparu, avec le temps, des principes de construction d'une classe, qui permettent de mettre de l'ordre dans la gestion de ces variables globales, locale à un objet ou à une classe d'objet. Ces principes ont été clairement exprimés par Robert C. Martin dans ses livres et sur le site Web de sa société Object Mentor.

Les principes de conception de classes orientée-objet sont regroupés au travers de la mnémonique SOLID, et permettent ainsi de surpasser la difficulté de maintenir des données globales dans un objet :
  • S : Single Responsability Principle (SRP) : There should never be more than one reason for a class to change.
  • O : Open-Closed Principle (OCP) : A module should be open for extension but closed for modification.
  • L : Liskov Principle (LSP) : Subclasses should be substitutable for their base classes
  • I : Interface Segregation Principle (ISP) : Many client specific interfaces are better than one general purpose interface
  • D : Dependency Inversion Principle (DIP) : Depend upon Abstractions. Do not depend upon concretions.
Robert C. Martin énonce aussi des principes d'architecture des modules (packages) très intéressants qui permettent de mieux gérer les dépendances des modules entre eux à fin de tenir la plus grande promesse de l'orienté objet : la réutilisabilité.
  • Release Reuse Equivalency Principle (REP)
  • Common Closure Principle (CCP)
  • Common Reuse Principle (CRP)
  • Acyclic Dependencies Principle (ADP)
  • Stable Dependencies Principle (SDP)
  • Stable Abstractions Principle (SAP)
Ces principes sont tout aussi importants que ceux relevant de la conception de classes, et des outils permettent de vérifier de façon automatique dans la plupart des cas, qu'on ne les enfreint pas. C'est le cas de JDepend dans le monde Java, NDepend pour le monde .NET, PHP Depend, Module::Dependency pour Perl...

C'est bien le genre de métrique qu'on a envi, en tant que chef de projet, de mettre dans les 'hooks' de subversion et de bloquer tout commit qui ne les satisferait pas. Quand on a la responsabilité d'un projet pour lequel on vous a affecté sans possibilité de discuter, des débutants, on croit toujours pouvoir être Big Brother et scruter le moindre faits et gestes des développeurs, en leur mettant par exemple, comme dans les sous-doués passe le bac, une bonne décharge électrique à chaque fois qu'ils essaient de commiter du code pas convenable.

Le problème c'est que tout ces principes ne sont pas toujours mis en œuvre dans les modules et librairies de base des langages comme Java notamment (par exemple, la fameuse classe Date qui au fur et à mesure des versions de java s'est vue refondre entièrement sans jamais atteindre une bonne implémentation, des fous sont même allez jusqu'à déposer un brevet logiciel pour refondre cette classe. La description du brevet est instructive sur les limites de la classe Date)

Ceci dit, une orientation objet bien maîtrisée peut aboutir aux nobles objectifs qu'elles s'était fixés dans la mesure où l'on sait que l'on joue avec le feu des variables globales.