"Leaky abstractions" comme un art de vivre

La plus ancienne base de données du monde : un cimetière

La plus ancienne base de données du monde : un cimetière

La conversation a commencé juste après le barcamp . Nous sommes allés prendre un petit café, Jazem, Houeida, Hatem, Kais et moi; et la conversation avait tourné autour des bases de données. Plus tard la conversation à continué entre Kais et moi par messagerie instantanée. Et encore plus tard avec Pr. Hatem Ben Sta au Caire. Ce que j’essayais d’expliquer, en mode non structuré mais caractéristique des conversations en direct, c’est pourquoi je me méfie des ORM, pourquoi nous n’utilisons pas de framework à ALIXSYS et comment on fait.

Quand vous écrivez une application qui, d’un côté est basée sur un modèle objet, et d’un autre côté fait appel à une base de donnée relationnelle pour stocker les données, vous avez  la désagréable impression de vous répéter et que toutes ces lignes de codes ordonnant a vos objets de s’enregistrer sont de trop. C’est normal. C’est que, comme l’expliquait Dan Ingals (à moins que ça ne soit Alan Kay, citation approximative de toutes façons) : "Dans la vraie vie, quand on rempli un Verre d’eau, on ne lui demande pas de se "mémoriser" juste après. ça n’a pas de sens." Un système basé sur un modèle objet devrait gèrer le stockage des données de manière transparente, sans intervention du programmeur.

Mais alors pourquoi les programmeurs persistent à utiliser des SGBDR ? Principalement par habitude, mais quelques fois aussi par souci d’intégration. En environnement d’entreprise, vous avez souvent ces conditions :

  • Un ou plusieurs SGBDR sont déjà déployés et en production
  • Les développeurs ont l’habitude de développer des applications basées sur SGBDR
  • Des applications décisionnelles (reporting, BI, etc…) sont déployées sur les SGBDR et le personnel est formé à leurs utilisation

En résumé SQL est un standard. Les gens ont tendance à l’oublier, mais SQL est probablement le standard le plus puissant du monde informatique. Le nombre de programmeurs qui parlent SQL doit être plus grand que ceux qui parlent HTML. SQL est plus puissant que HTML.

Écrire du code c’est écrire. En soi, ce n’est pas très différent d’écrire un poème, une facture ou sur un mur. Le but principal, au delà du langage, c’est de se faire comprendre; si possible par le plus grand nombre. Un standard est précisément fait dans ce but : pour que tout le monde parle la même langue. Et ça, ça n’a pas de prix.

Cette situation pose un dilemme : Objets ou SQL ? Et les réponses proposées sont :

  1. On abandonne la POO
  2. On abandonne SQL et on adopte smalltalk
  3. On abandonne SQL et  on adopte un système de stockage non relationnel
  4. On adopte un ORM
  5. La réponse 5 :)

Je vous déconseille de prendre le 50/50, je vous le donne en mille : ce sont toutes des mauvaises réponses. Mais ce sont là toutes les réponses envisageables à l’heure actuelle. Même si adopter smalltalk est tentant, vivre en autarcie n’est pas une option pour tous les projets. CouchDB, TokyoTyrant, BigTable et autres systèmes de stockage non relationnels ont le vent en poupe en ce moment, mais ils ne résolvent pas nôtre problème (Le stockage automatique et transparent des objets; juste pour rappeler). Il reste l’ORM. Qui résous bien partiellement le problème mais qui a l’énorme inconvénient de cacher SQL de la vue du programmeur. Ce qui est bien du point de vue des concepteurs d’ORM mais mal de mon point de vue.

Les standards aussi sont une écriture. Et à ce titre, je considère que leurs finalité c’est d’être lus, compris et appliqués par des hommes et non pas des machines. C’est ce qui fait la différence entre un bon standard et un mauvais comme SOAP et ses dérivés. On n’écrit plus du code comme en 1980, c’est devenu un travail collaboratif. En 1980 écrire du code ressemblait à une conversation privée entre vous et la machine. En 2009 écrire du code ressemble à un talk show : vous avez bien un animateur-ordinateur en face de vous, mais la conversation est devenue publique. Le code est devenu public. Libre. Formellement, vous vous adressez à l’ordinateur, mais pratiquement, vous vous adressez à d’autres programmeurs. Et tous ces programmeurs parlent SQL, ça serait dommage de ne pas en profiter.

Les "leaky abstractions" à la rescousse

Un Objet est une abstraction de très haut niveau. Elle permet de représenter toutes les choses de l’univers. Elle permet aussi de représenter plein d’abstractions de niveau plus bas. Avec un Objet, on peut tout faire. Notamment créer un Objet/Abstraction nommé Base de données.

Mais construire des abstractions est un art. Une bonne abstraction doit cacher ses détails. En mathématique, si j’utilise "n" comme abstraction pour représenter un nombre entier, c’est bien pour m’éviter de raisonner sur les 1, 2, 3 et autres 4 et 5 de toute la suite individuellement. Ce qui me serait impossible de toutes façons. "n" est une bonne abstraction parce que, avec, je peux prouver que la somme de deux entiers est un entier par exemple . Maintenant, si j’utilise le symbole ☹ comme abstraction pour représenter "tous les nombres contenant le chiffre 7 ou multiples de 666", outre le fait que c’est complètement débile comme vous pouvez le constater, je ne peux rien faire avec mon ☹ sans devoir me rappeler ce qu’il représente. La suite 7, 17,  27, 37, 47, 57, 67, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 87, 97, 107, 117,  127, 137, 147, 157, 167, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 187, 197, 207, 217,  227, 237, 247, 257, 267, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 287, 297, 307, 317,  327, 337, 347, 357, 367, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 387, 397, 407, 417,  427, 437, 447, 457, 467, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 487, 497, 507, 517,  527, 537, 547, 557, 567, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 587, 597, 607, 617,  627, 637, 647, 657, 666, 667, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 687, 697, 707, 717, 7 27, 737, 747, 757, 767, 770, 771, 772, 773, 774, 775, 776, 777 est bien représentée par ☹ mais pour la créer j’ai du traiter séparément les 2 cas : 7 et 666. ☹ n’est pas une bonne abstraction.

Ce genre d’abstractions, Joel Spolsky les appelle "leaky abstractions", littéralement "les abstractions qui fuient" comme un robinet,  parce qu’elles laissent transparaitre leurs charpente. Il va même plus loin, il théorise : "Toutes les abstractions non triviales fuient". Si on examine les abstractions utilisées par Ruby on Rails pour son ORM, cela se confirme.

D’abord RoR fait une simplification : 1 classe = 1 table ce qui n’est pas toujours la solution optimale. Nous avons pu constater par exemple que avoir plusieurs classes utilisant la même table est une situation assez commune de par son optimalité. Par exemple, pour la plupart des systèmes vous avez plusieurs classes d’utilisateurs : Clients, Commerciaux, Administrateurs, etc… Nous avons trouvé que utiliser 1 seule table et plusieurs Classes pour les utilisateurs est une solution optimale.

Ensuite RoR utilise des abstractions du genre "has_many:", "has_one:" pour représenter les relations entre les objets. Or ces abstractions sont très peu expressives pour représenter les relations entre les objets. Les objets peuvent avoir toutes sortes de relations : une Souris:mange(Fromage) un Chat:chasse(Souris) . Ce qui est exprimé en réalité dans RoR ce sont les relations sous-jascentes dans la base de données. Vous voyez la fuite (leaky abstraction)? Cela ne m’étonnerait pas que les programmeurs RoR finissent par construire leurs modèle objet en pensant aux tables qui vont être créées dans la base.

Quitte à avoir un modèle conscient de sa condition d’objet informatique volatile, nous allons le faire parler carrément SQL avec la base de données. Observez ce petit bout de code PHP :

$senefer = Creature::select("where name like 'Sanfour %'");
$ghadhbaan = new Creature("Sanfour Ghadhbaan");
$ghadhbaan->insert();

J’en vois certains qui sont en train de s’arracher les cheveux. Mais vous avouerez que la clareté de ce code est incontestable. Ce modèle déroge aux règles les plus élémentaires de la conception : le code est strictement lié au modèle de la base. Si on change la base on doit changer le code. Mais les autres ORM y dérogent aussi. Et j’ai horreur de faire semblant. Je préfère faire avec. Et vous verrez dans un moment que ce n’est pas si grave que cela.

La méthode select() est statique et c’est une factory. Elle génère des objets de sa Classe.

$senefer[0] instanceof Creature; //retourne TRUE

Ce genre de méthode dans sa plus simple expression est très simple à écrire :

class Creature
{
  static function select($options = NULL)
  {
    $resultats = self::$db->query("select nom from creatures $options");
    foreach ($resultats as $ligne) {
      $creatures []= new Creature($ligne['nom']);
    }
    return $creatures;
  }
}

Ici on voit bien la liaison stricte entre le code et la base. Mais dans la pratique, il est de toutes façons impossible changer la base sans changer le code d’une manière ou d’une autre. Ce qu’on fait, généralement, c’est confiner le code qui peut changer de manière à ne pas avoir à le chercher. Et dans cette optique, je trouve que le code est assez bien confiné dans cette simple méthode select(). Si par exemple il nous vient à l’idée de changer le nom du champ "nom" en "nom_et_prenom" dans la table "creatures". Nous aurions simplement à changer le code comme suit :

class Creature
{
  static function select($options = NULL)
  {
    $resultats = self::$db->query("select nom_et_prenom as nom from creatures $options");
    foreach ($resultats as $ligne) {
      $creatures []= new Creature($ligne['nom']);
    }
    return $creatures;
  }
}

PHP est une simple couche au dessus de la base de données, comme aiment à le dire ses détracteurs. On se propose de l’utiliser comme tel et d’en tirer parti. Je ne concevrais probablement pas mon modèle de la même manière si j’écrivais du code en Scheme.

La méthode insert() est dynamique. Dans sa plus simple expression l’implémentation ressemblerait à :

class Creature
{
  function insert()
  {
    $nom = $this->nom;
    self::$db->query("insert into creatures (nom) values('$nom');");
    return $this;
  }
}

Pour formaliser tout cela nous pourrions faire une petite interface :

interface presistance
{
  public static  function select($options = NULL);
  public function insert();
}

C’est tout ce dont on a besoin.

Ou sont passées update() et delete() ?

Nous avons pu constater, au bout de quelques années d’expérience quand même, que supprimer des données dans une base de données n’était pas du tout naturel. Le plus souvent les données sont plutôt marquées comme "supprimées" (ou annulé ou désactivé etc …). Dans les faits la requète "delete" est plutôt une requêtes de maintenance (optimiser la performance de la base) et de ce fait, dans la plupart des cas ne fait pas partie du système.

Nous avons pu constater aussi que les opérations de mise à jour des données "update" sont souvent complexes, impliquent plusieurs tables à la fois et quelques fois même transactionnelles. Une opération update() qui synchroniserait l’objet avec ses tables dans la base de données, dans la pratique, serait mieux implémentée conceptuellement comme une suppression (ou annulation ou désactivation etc…) suivie d’une nouvelle insertion. Par contre l’opération de suppression de l’objet utilisera forcément la requête  "update" formulée dans une méthode spécifique à l’opération :

class Creature
{
  function tuer()
  {
    $nom = $this->nom;
    self::$db->query("update creatures set supprime='oui' where nom='$nom'");
    return $this;
  }
}

Cette méthode est capable de tuer un sanfour. Ce qui est une prouesse : on n’a jamais vu un sanfour se faire tuer. Mais tuer() est la méthode appropriée pour supprimer un sanfour.

Cette conception a été utilisée sur plusieurs projets et si je vous en parle maintenant c’est que nous avons pu tester son efficacité et sa simplicité. Ce dernier point est très importants pour nous. Veuillez observer ce schema illustrant notre process :

Process ALIXSYS

Process ALIXSYS

Si j’ai mis "Former les développeurs" ostentatoirement au début du process, c’est pour insister sur la consommations de resources en amont et combien il est important d’optimiser cette activité. Vous me direz : mais vos développeurs sont déjà formés. Oui, mais on recrute.

Quand nous avons fait la refonte de notre progiciel de géstion intégrée ALIX, il y a quelques années quand nous étions un département R&D dans une autre société, l’objectif principal identifié était de simplifier son accés aux programmeurs novices. ALIX était un système assez complexe avec une base de code assez importante qui faisait que quand on écrit du code pour ALIX on a à peine l’impression d’écrire du code PHP ou HTML : tout était caché derrière les bibliothèques et l’architecture du système elle même compliquait les choses. Nous avons tout reconçu depuis le début pour que quelqu’un qui a des connaissances rudimentaires en PHP puisse s’y retrouver dans le code et se faire utile très rapidement. C’était un impératif pour nous car nous avions un turnover assez important et nous recrutions souvent des jeunes diplômés.

Aujourd’hui ce n’est plus un impératif, mais cette expérience m’a permis de me rendre compte que avoir un système simple avec une base de code accessible donne un avantage compétitif certain. Prendre un frais diplômé et le rendre opérationnel comme développeur sur le système en 3 mois est certainement le trait conceptuel le plus attrayant du système ALIX. Et j’en suis très fier.

Cependant je ne suis toujours pas tout à fait satisfait. On peut aller plus loin. Et pour la version 3 de ALIX, je compte le débarasser de tout son code. ALIX sera alors un pur processus. Le rève.

About these ads

3 comments so far

  1. Trinoo on

    C’est un post très interessant, j’ai du le relire plusieurs fois, parce que ça traite plusieurs sujets en même temps. Dommage qu’il n’y à pas l’heur à coté du "posted" je dirais que c’était 3h ou 4h du mat. Passons.

    Je pense que les solutions informatiques ne sont pas bonnes ou mauvaises, ni "fortes" ou "faible" (dans le jargon tunisien) pour endosser une étiquette.
    Chaque structure ou technologie a ces avantages et ces inconvénients.

    Pour moi, le choix d’une technologie, d’une architecture de développement, d’une base de données dépends strictement du projet à développer.

    Généralement on ne maitrise pas toutes les technologies existantes, on choisi d’adopter une ou deux, en les jugeant souples, flexibles, et passe-partout. On essaye de les modeler pour les intégrer dans tout type de problème qu’on va rencontrer…

    Par rapport à RoR, il est n’a pas obligatoire que chaque Classe ait sa propre Table.
    Le Modèle dans l’architecture MVC comme l’indique son nom est le mapping entre une table est une classe. On trouve souvent des Controlleurs sans Modèle : des Classes sans tables.

    De même on peut utiliser l’héritage dans les Modèles sous RoR : genre une seule table (users) pour différentes classes : Mortel, Esclave, Maitre, Sanfour,…

    On pourrait même écrire sous RoR toute une application avec une seule table ayant tout les attributs confondus dans celle-ci avec toute sorte de relation de la table envers elle même. Ce qui nous permettra de cuire des oeufs sur le microprocesseur et le disque dur pour chaque requête.

    Revenant aux types de relation entre les Classes. Avec Ruby tout comme Python, SmallTalk, Objectif-C,… on peux définir des métaClasses ( la métaprogrammation )
    C’est à dire, fabriquer une Classe en cours de route en fonction des cas, qui peux venir par la suite surcharger une autre Classe, ou bien se faire surcharger par une autre.

    Ainsi le type de relation entre objet, peut être dynamique en fonction du profil : Sanfour, Far Walla Gattous…

    Par Rapport aux bases de données noSQL (CouchDB, MongoDB,…) je les vois comme de l’XML en version Objet. Ce derniers à eu du mal à décoller pour abriter une structure de données chaotique qui ne réponds à aucun type de stucture. Les noSQL ont immergé et aujourd’hui on à l’impression qu’XML et XQuery n’ont jamais existé…

    Comme on parle de base de données, par rapport à ton expérience avec les requêtes de type DELETE et UPDATE, il me semble qu’avec une intégrité référentielle de la DB, ou bien même au niveau du mapping avec des observateurs. On peut oublier les opérations de maintenance d’une DB.

    Par rapport à PHP, j’ai migré depuis longtemps vers Ruby, parce que le code est plus concis et mieux adapté à la POO, même si Rails utilise des Plugins comme PHP avec ces librairies. Ces derniers sous Rails sont souvent très souples à modifier (pas plus de 50 lignes de code)

    Pour finir, je pense que Comparer PHP et Rails se résume à comparer une clé-à-molette avec un garage de réparation. Il sera plus judicieux de comparer Rails à Zend Framework, voir cas limite ; comparer PHP à SINATRA…

    En tout cas, Merci pour ce post qui nous a permit d’avoir un apercu de l’engrenage et des differents aspects des boites de développement qui ressemblent plus à des machines enigma. Ce post pour moi, est un des rares "Made in Tunisia" qui vient présenter des idées et informations enrichissantes sans rien demander en retour.

    Bon courage et bonne continuation avec ALIX.

  2. slim on

    Je vous remercie en retour pour cette réponse si généreuse. Par contre je ne suis pas d’accord pour la clé-à-mollette :) Voyez vous, PHP n’est pas un language de programmation (généraliste) à la base. C’est un framework pour applications web. Disons que PHP c’est RoR sans ruby. En fait pour être précis à sa création c’était un "templating engine" (d’ailleurs les smarty et autres m’ont toujours fait rire). Ensuite il à évolué pour devenir plus généraliste, c’est à dire un framework web.

    Je pense que PHP est comparable RoR. Mais peut être pas comparable à ruby.

  3. Trinoo on

    Je pense qu’il y a une difference entre une librairie et un framework. Certains diront qu’un framework est un ensemble de librairies…
    A la base, je suis d’accord PHP était une librairie pour PERL, mais par la suite ça était réécrit pour devenir un "Language".
    Aujourd’hui si on compare PHP à Rails, c’est que l’on compare PHP dans sa version 4 et 5 avec le coeur Zend Engine, rien à avoir avec la version : PHP/FI (Personnal Home Page / Formulars interpreter).
    Il est vrai qu’avec un echo phpinfo() on a une longue liste de librairie, mais pour moi ça reste des libs et non un framework.

    Quand on regarde la structure de Rails, avec le concept MVC, les helpers, les layouts et render, la gestion du Routes, le RJS, les initializateurs, la gestion du Cache et fichier temporaire, l’ActionMailer, l’ActionView, l’ActionRecord, le Fameux RAKE, les generateurs de structure…. la liste est encore longue….
    Je ne vois pas comment on pourrait comparer PHP à RAILS. Ce n’est pas pour faire rougir PHP que j’évoque toute cette liste, mais c’est juste pour montrer que ce sont deux environnements completement différents.

    Sans oublier qu’on peut developper des apps PHP en standalone avec GTK…

    Merci pour cet espace de débat.


Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Suivre

Recevez les nouvelles publications par mail.

Joignez-vous à 69 followers

%d bloggers like this: