Le CRUD, ou la brique élémentaire du codeur

Un bon architecte logiciel est, entre autres choses, un bon concepteur / développeur. Et à ce titre, il se doit, comme tout bon développeur, d’être une feignasse rigoureuse; en effet, un développeur doit toujours avoir en tête de requérir, pour ses réalisations, le plus faible niveau d’énergie possible. Cela se traduit généralement par le fameux adage selon lequel il est inutile de réinventer la roue.

Omettons quelques instants le fait que cet adage est peut-être responsable de notre incapacité historique à inventer mieux que la roue, et concentrons-nous un peu sur l’intérêt, pour un développeur, d’être aussi paresseux que rigoureux.

Paresse et rigueur

La paresse doit pousser le développeur – et avant lui le concepteur – à toujours se demander par quel moyen il est possible de réaliser ce qu’il souhaite en moins de lignes de code qu’il ne lui en faut; par ailleurs, la rigueur doit le pousser à répondre correctement aux demandes et besoins des utilisateurs finaux. Ces préoccupations induisent les points suivants:

  • ne pas développer plus qu’il n’est demandé (c’est souvent très dur, mais c’est essentiel);
  • ne pas écrire deux fois le même code (refactoring, c’est plus classe);
  • écrire un code facilement maintenable et modulable.

Concernant ce dernier point, on m’a souvent dit que je devais coder en pensant au prochain développeur qui modifierait mon code, quelques années plus tard, et qui ne serait absolument pas en contact avec moi : il me faut, pour lui, trouver des noms explicites d’objets et de méthodes, commenter correctement mon code, etc. Mais on ne va pas se mentir, je me fous un peu – euphémisme – de ce développeur que je ne connais pas, qui visiblement ne me connaît pas, et qui en plus m’a apparemment piqué un client.


Aussi, mon premier conseil sur le sujet est le suivant : vous devez coder en imaginant que le prochain développeur qui modifiera votre code, quelques années plus tard, ce sera vous ! Ce, pour au moins deux raisons : d’abord, parce que ce sera probablement le cas, ensuite parce que vous passerez pour un gros cake devant votre client si vous êtes incapable de vous relire.

Je ne vais pas rentrer, dans cet article, dans les considérations de nomenclature ou de commentaires, sujets qui feront sans doute l’objet d’autres articles; je vais simplement vous parler du noyau qui constitue le développement d’applications en langage objets : le CRUD.

Qu’est-ce qu’un CRUD ?

CRUD est l’acronyme des quatre fonctions principales de cet objet générique : Create – Request – Update – Delete.

Lorsque vous concevez le modèle objet de votre application, vous êtes confronté à des types différents d’objets :

  • les classes utilitaires (en général static, par exemple les classes de manipulation de String, de Date, etc.);
  • les classes mécaniques, ou de motorisation (elles sont le moteur de votre application, telles que les classes de connexion à une base de données, ou d’accès à des fichiers);
  • les classes d’objets que votre application manipule, que j’appelle classes de données (car en général, ces objets sont stockés en base de données, c’est d’ailleurs à ça qu’on les reconnaît).

Il existe bien sûr d’autres types de classes, mais on va gentiment s’en cogner pour l’instant. Lorsque, donc, vous êtes confronté à des classes de données, vous aller vite vous rendre compte que celles-ci partagent un comportement commun, quasiment dans tous les cas et dans tous les langages objets (je rappelle à l’intention de ceux qui dormaient en cours de programmation ou qui, à mon instar, rataient ces cours car ils devaient bosser pour se permettre d’être étudiant – Ô, ironie – que le comportement d’un objet est défini par ses méthodes, ou fonctions); ce comportement commun est le suivant :

  • accès aux propriétés;
  • modification des propriétés;
  • insertion en base de données (Create);
  • chargement depuis une base de données (Request);
  • mise à jour dans une base de données (Update);
  • suppression d’une base de données (Delete).

Les deux premiers points de cette liste sont communément appelés accesseurs (et non pas assesseurs, ça n’a mais alors rien à voir).

Implémentation

Prenons un exemple simple, que vous comprendrez sans vous fatiguer. Soit un forum, que nous allons intelligemment nommer « forum »; nous allons simplifier le modèle en nous restreignant à ses aspects suivants :

  • le forum a des utilisateurs (login, mot de passe, nom, prénom);
  • le forum contient des catégories (nom);
  • chacune de ces catégories contient des discussions, ou threads (titre, utilisateur créateur de la discussion);
  • chacune de ces discussions contient une série de messages, ou posts (titre, contenu, utilisateur auteur, date de post).

Pour les besoins de cet exemple, disons que le forum est écrit en PHP (en Java, en Objective-C, en ActionScript, en C++…à la syntaxe près, tout ceci reste identiquement valable).

L’organisation d’un CRUD permet de rédiger le squelette de chacune de ces classes que sont : User, Category, Thread, Post (en anglais – c’est mieux – et au singulier – c’est impératif); en effet, on peut dire que chacune de ces classes contiendra, au minimum, les méthodes suivantes :

  • pour chacune de ses propriétés, une méthode permettant de récupérer la valeur de la propriété en question : getPropriete()
  • pour chacune de ses propriétés, une méthode permettant de modifier la valeur de la propriété en question : setPropriete(valeur)
  • une méthode de création en base de donnée : create()
  • une méthode de chargement depuis la base de données : init(), ou load(), ou request(), en général appelée dans le constructeur de l’objet
  • une méthode de mise à jour en base de données : update()
  • une méthode de suppression de la base de données : delete()


Les accesseurs

La signature, ainsi que le contenu, des accesseurs est, au type de la propriété près, toujours le même; imaginons la propriété login d’un User:

function getLogin() { return $this->login; }
function setLogin($value) { $this->login = $value; }

J’attire votre attention sur le fait que, concernant les utilisateurs, le mot de passe est une exception; en effet, je vous recommande chaudement de ne pas implémenter de fonction getPassword() qui permettrait de récupérer le mot de passe d’un utilisateur.

Les quatre cavaliers du CRUD

Les signatures sont toujours les mêmes (les contenus, hormis pour la suppression, sont en revanche toujours différents, car dépendant directement des propriétés):

function create() { ... }
function init() { ... }
function update() { ... }
function delete() { ... }

L’ensemble de ces fonctions renvoie true en cas de succès, et false sinon, (sauf la fonction init() qui charge directement les propriétés de l’objet et ne renvoie ensuite rien, ou void).
Considérons un instant que chacun de nos objets disposent, en base de données, d’un identifiant id; dans ce cas, la fonction init() peut être appelée, depuis le constructeur, de la façon suivante :

class User {
    public function User($theId = -1) {
        // a-t-on renseigné le paramètre permettant d'identifier l'objet ?
        if ($theId > 0) {
            // on note que ce paramètre est l'identifiant de l'objet
            $this->setId($theId);
            // on peut charger l'objet depuis la base
            $this->init();
        }
    }
    ...
}

De plus, toujours dans ce cas, je préconise le fait d’ajouter une cinquième méthode appelée save(), et dont le comportement est le suivant :

  • si l’objet que vous voulez enregistrer n’est déjà identifié en base de données, on le met à jour;
  • sinon, on le crée.
function save() {
    if ($this->getId() > 0)
        return $this->update();
    else
        return $this->create();
}

Ou bien, encore plus paresseux :

function save() {
    if ($this->getId() > 0)
        return $this->update();
    return $this->create();
}

Voire, l’ultimate paresseux :

function save() { return ($this->getId() > 0) ? $this->update() : $this->create(); }



Concernant cette dernière série de méthodes, vous vous demandez peut-être pourquoi :

  • je n’appelle pas directement la propriété $this->id mais bien la méthode getId(), alors que je suis à l’intérieur de ma classe (et que j’ai donc accès aux propriétés private);
  • je crée une nouvelle méthode qui va rediriger la création ou la mise à jour;
  • je réduis mon code de façon quasi-illisible pour les néophytes.

Je n’appelle pas directement la propriété $this->id mais bel et bien la méthode getId(), parce qu’ainsi je n’ai pas à me préoccuper de savoir si je peux accéder à cette propriété de façon brute ou traitée; un bon exemple peut être le nom de famille d’un client, ou le nom d’une ville; il est fréquent que ces propriétés soient affichées en majuscules; si tel est le cas, il me suffit d’indiquer, dans le corps de ma méthode getCity():

function getCity() { return strToUpper($this->city); }

et je n’ai plus à me préoccuper de l’affichage correct, en majuscules, de la ville, et ce partout dans le code.

Ensuite, je crée une nouvelle méthode qui va diriger vers la création ou la mise à jour pour une raison similaire (attention ! TIML) : il arrivera qu’après avoir commencé à développer votre code, votre gentil client va venir vous indiquer que vous ne devriez vraiment pas enregistrer un utilisateur qui n’aurait pas de login. Spontanément, vous auriez pu penser rajouter dans le code de la fonction create() un test sur l’existence ou non d’une valeur dans la propriété login. De même pour la mise à jour. Et vous vous retrouvez alors avec deux méthodes faisant appel à un même test, qu’éventuellement vous pouvez intégrer dans une nouvelle méthode que vous appellerez isValid(), et qui renvoie true si l’objet est prêt à être enregistré en base de données, et false sinon.

Dans mon exemple, cela donnerait quelque chose comme cela :

function isValid() { return (!is_null($this->getLogin()
                          && strLen(trim($this->login)) > 0); }

function save() {
    if ($this->isValid()) {
        return ($this->getId() > 0) ? $this->update() : $this->create();
    }
    return false;
}

Ainsi, dans tout le reste de votre code, sans avoir à réfléchir, lorsque vous souhaiterez sauver un objet, vous écrirez simplement :

$myObject->save();

et charge à l’objet de déterminer lui-même s’il est valide ou non, et s’il est identifié ou non.

Enfin, je réduis mon code de façon quasi-illisible parce que je trouve que c’est classe (et plus sérieusement, un else, par exemple, est une instruction dont on peut se passer si on est dans la seule alternative au if).

Et notre prétendue paresse rigoureuse, dans tout ça ?

Jusque là, il semble plutôt qu’on en fait plus que ce qu’on aurait pu faire; c’est partiellement vrai.

D’abord, tout le code qui pourrait découler de cet article se fait sans réfléchir (paresse).

Ensuite, ces méthodes sont suffisamment éprouvées pour qu’on puisse tranquillement les considérer comme propres, maintenables et modulables (rigueur).

Enfin, et c’est un peu la cerise sur le gâteau, pour ceux qui ne le savent pas, la plupart des frameworks de développement, comme la plupart des IDE, automatisent complètement la génération des CRUD. Par exemple, en Symfony, il suffit d’indiquer la liste des propriétés d’une classe, ensuite un script propriétaire va:

  • créer les tables correspondantes en base de données;
  • créer les CRUD correspondant;
  • adosser à ces CRUD des classes dites Peer, correspondant au Design pattern Factory (mais c’est une autre histoire).

Paresse et rigueur sont le chemin de la sagesse du développeur dans la voie de la conception.

Une réponse à to “Le CRUD, ou la brique élémentaire du codeur”

  • Emilien:

    Bonjour,

    Voila que tu réponds à une de mes récurrentes questions lors de mes développements : dois-je implémenter tous les accesseurs ?
    En effet, plusieurs de mes profs m’ont toujours dit « accesseurs uniquement quand on en a besoin ! » (puis des critiques des IDE qui rédige le code automatiquement)
    Effectivement, passer par l’accesseur permet simplement de se ôter les problèmes de casse. Personnellement, dans ma classe, j’utilisais directement mes attributs (privés bien sûr), n’ayant jamais rencontré ce cas/problème, mais pour éviter d’avoir un jour à perdre du temps, je travaillerai comme ça dorénavant.
    Merci.

    Remarque : on m’a dit un jour un développeur n’est pas fainéant (il ne fait pas néant :-)) mais il est simplement paresseux, donc je rectifierais « feignasse rigoureuse » en « paresseux rigoureux » !

    @+,
    Emilien.