Home > .NET/C#, Software, Software Architecture > Programmation organique

Programmation organique

J’aime beaucoup le refactoring. Ça n’a pas toujours été le cas! Pendant longtemps, j’ai cru que la façon la plus efficace d’écrire du code était de parfaitement conceptualiser l’ensemble de l’application. Un jour, pourtant, je me suis rendu compte qu’il était peut-être sage de commencer à écrire du code tôt. En effet, on peut ainsi mitiger certains risques, avoir un feedback fonctionnel plus rapide, et permettre d’établir certaines bases de travail pour une équipe.

D’un autre côté, je ne peux pas dire avoir complètement abandonné l’espoir de voir une conception solide, suivie d’un développement qui ne change vraiment qu’en surface. J’ai vu et revu le coût monstrueux d’une mentalité dans laquelle le refactoring est la réponse à tout. Écrire du code de mauvaise qualité dans la seule optique de le réécrire différemment (pardonne-moi Martin Fowler, je sais que ce n’est pas le réel esprit du refactoring) est peu productif et démotivant. Comment donc arriver à rallier résultats rapides et bonne conception? Laissez-moi présenter les racines d’une approche qui n’est ni particulièrement nouvelle ou impressionnante, mais qui fait ses preuves. Appelons ça, si vous le voulez bien, la programmation organique.

Pourquoi “programmation organique”? Simplement parce qu’on fait en sorte que l’évolution du code dans le temps se comporte comme lorsqu’on plante un arbre. La structure interne de l’arbre reste invisible et relativement stable, mais de l’extérieur la taille et la forme changent. Quant à nous, nous ne pouvons que sélectionner l’endroit où le planter et lui donner des guides.

L’élément technique central de la programmation organique est l’interface. Oui, je parle des interfaces de programmation! Celles-ci n’ont rien de nouveau et rien d’extraordinaire; je vous avais averti! Ces interfaces délimitent l’espace de chacune des plantes que composent un ensemble de classes. En tout temps, le code est écrit de la manière la plus agile qui soit, mais les interfaces seront toujours incluses dans un processus de conception solide. Quels sont les impacts d’une telle approche?

  • La structure de l’application sera toujours solide
  • Les impacts d’un code de mauvaise qualité ou temporaire sera mitigé
  • Une liberté de développer en mode brouillon dans un contexte carré de sable

Les motifs de conception Pont et Fabrique sont beaucoup utilisés dans cette approche. Voici un exemple simple démontrant comment appliquer cette pratique.

Vous désirez développer une interface utilisateur dans laquelle on utilisera un champ texte pour entrer un chemin vers un dossier. Si le dossier existe, un bouton sera activé. La manière facile de faire ce formulaire est de placer un TextBox et un Button dans un Form, d’attacher un comportement OnTextChanged au TextBox qui modifiera simplement le Button.Enabled en fonction de l’existence du dossier. Cette approche, quoique fonctionnelle et rapide, causera certains problèmes si, en aval dans le développement:

  • Le champ texte est séparé en deux (Dossier de base, et dossier relatif)
  • Il y a plusieurs boutons à activer (un dans le formulaire et un dans une boîte à outils)
  • On désire afficher un message qui décrit pourquoi le dossier est invalide
  • On désire remplacer le TextBox par un autre type de contrôle
  • On désire enlever le TextBox et utiliser directement une variable (boîte de dialogue)
  • On désire ajouter d’autres validations spécifiques
  • etc.

Toutes ces situations causent un changement de structure qui finit généralement par dégénérer. Voici donc comment la programmation organique peut réduire les impacts de tels changements.

Premièrement, on désire cacher le TextBox. Celui-ci peut changer, et de toute façon tout ce qui nous intéresse de celui-ci, c’est sa valeur finale. Voici donc l’interface:

public interface ITextBridge
{
  string Text {get; set;}
}

Voici l’implémentation de cette interface dans notre contexte:

public class TextBoxTextBridge : ITextBridge 
{
  private TextBox _textBox;

  public TextBoxTextBridge(TextBox textBox)
  {
    _textBox = textBox;
  }

  public string Text
  {
    get { return _textBox.Text; }
    set { _textBox.Text = value; }
  }
}

Il n’y a rien de très compliqué, mais notez qu’on a complètement découplé le champ texte de son utilisation. Si les besoins changent, on peut très bien remplacer l’implémentation pour utiliser deux champs texte concaténés, deux champs texte utilisés selon une condition ou une variable sans modifier l’interface. Faisons la même chose avec le bouton à valider:

public interface IValidationTarget
{
  void Success();
  void Failed();
}

Notez que cette interface aurait pu être différente. On aurait pu passer un message aux méthodes Success et Failed, ou n’avoir qu’une méthode qui prend un ValidationResult mais pour le moment ça sera suffisant. Pour mieux visualiser comment l’utiliser, voici l’implémentation:

public class ButtonValidationTarget : IValidationTarget
{
  private Button _button;

  public ButtonValidationTarget(Button button)
  {
    _button = button;
  }

  public void Success()
  {
    _button.Enabled = true;
  }

  public void Failed()
  {
    _button.Enabled = false;
  }
}

Maintenant on est prêts à utiliser ces deux classes ensemble. On veut pouvoir changer la source et la cible, mais aussi la validation elle-même! Voici donc l’interface qui nous intéresse:

public interface IMyActionValidator
{
  void Validate();
}

Plutôt simple, n’est-ce pas? Voici, encore une fois, l’implémentation:

public class DirectoryExistsMyActionValidator
  : IMyActionValidator
{
  private ITextBridge _textBridge;
  private IValidationTarget _validationTarget;

  public DirectoryExistsMyActionValidator(
    ITextBridge textBridge,
    IValidationTarget validationTarget
    )
  {
    _textBridge = textBridge;
    _validationTarget = validationTarget;
  }

  public void Validate()
  {
    if (Directory.Exists(_textBridge.Text))
      _validationTarget.Success();
    else
      _validationTarget.Failed();
  }
}

Jusqu’ici tout va bien? Si c’est le cas, on a presque terminé! Tous nos éléments sont en place, et on est prêts à intégrer le tout. Je n’utiliserai pas de Fabrique ici pour démontrer l’impact direct de l’utilisation massive des interfaces. Il nous faut premièrement un membre dans le Form, comme celui-ci:

private IMyActionValidator _validator;

Quelque part dans l’initialisation du Form, il faudra simplement créer la validation de cette manière:

_validator = new DirectoryExistsMyActionValidator(
  new TextBoxTextBridge(textBox1),
  new ButtonValidationTarget(button1)
  );

Il ne manque plus que l’appel! Dans l’événement TextChanged du TextBox, appellons directement:

_validator.Validate();

Évidemment, nous pourrions pousser plus loin cet article pour utiliser des fabriques qui attacheraient automatiquement le TextChanged à un module de gestion d’événements, mais je crois que l’exemple démontré ci-dessus décrit bien comment développer une structure malléable, mais toutefois solide, qui permet de facilement “coder comme ça vient” sans mettre en jeu le coût de maintenance à long terme.

Quoique cette exemple soit simpliste, son application représente bien la méthode qu’est la programmation organique. De plus, on peut appliquer cette théorie récursivement! Pour un groupe d’interfaces, il est toujours possible de cacher l’ensemble derrière une facade, qui jouerait le même rôle que le pont avec le TextBox. Je répète que malgré le drôle de nom que je lui ai affublé, il n’y a rien de nouveau dans cette technique. C’est une approche, munie de ses avantages et ses inconvénients, que j’ai eu l’occasion d’expérimenter avec succès plusieurs fois par le passé.

Si vous avez des commentaires, positifs ou non, sur cet article, ou si vous avez eu l’occasion de l’essayer et voulez me donner votre avis, je vous invite à laisser un commentaire!

  1. Bertrand YEURC'H
    July 8, 2007 at 04:30

    Bonjour,

    J’ai déjà entendu parlé de la programmation organique, mais avec un autre sens, celui d’un programme qui simulerait l’évolution dans le temps d’une organisation donnée. J’en cherche (désespérément) des exemples d’implantation.

    En effet, je travaille sur l’étude de l’évolution des structures ecclésiastiques en Bretagne (évêchés, archidiaconés, doyennés, paroisses, trèves) et de leurs chefs-lieux. Ces structures ont vu leurs limites géographiques, leurs noms et leurs chefs-lieux changer au cours du temps.

    J’ai tenté de créer un modèle UML avec des informaticiens mais ils buttent sur le point suivant. Lors de l’étude de l’évolution de ces structures, on ne peut pas savoir, a priori, quel est la plus petite brique élémentaire qui constitue la paroisse (ou la trève) à moins de prendre une granularité très petite (un hameau), ce qui n’est pas faisable vu la superficie de la zone à étudier. Cependant certains changements peuvent être décris avec cette granularité.

    Si vous avez des pistes au niveau de la méthodologie, de la modélisation ou bien des exemples similaires, je suis preneur.

    Cordialement

  2. May 4, 2009 at 17:13

    Effectivement, la terminologie utilisée ici n’est probablement pas la bonne, toutefois l’image d’un développement organique sied bien à ce que j’essayais d’exprimer ici. Pour votre cas des structures complexes, la plus grande problématique est souvent l’entrée des données et la cartographie des relations. Il serait difficile de vous donner une piste solide avec peu d’information, toutefois je peux déjà proposer certains points dans votre réflexion:

    1. Accepter l’imparfait : les structures de données très complexes se retrouvent généralement avec un certain niveau d’erreur (souvent des informations manquantes), alors que les systèmes qui le gèrent requièrent souvent une entrée “valide”. Vérifiez s’il est possible de limiter les erreurs tout en maximisant l’efficience de l’entrée des données.

    2. Établir des relations dynamiques : dans le cas de la grande majorité des logiciels, les relations sont fixes. Par exemple, une transaction financière a été faite par une personne. Dans votre cas, ces relations sont changeantes, et la nature de ces relations change au travers du temps et des recherches historiques. Il est donc primordial que le système soit bâti sur cette dynamique, tout en maximisant tout de même la traçabilité et la systématisation pour permettre au système d’évoluer.

    3. Bâtir des outils de suivi de progrès : avec un système ouvert (Wikipedia en est un exemple), vous devrez avoir des outils pour connaître les culs-de-sacs d’information, les contradictions et les profils incomplets. Un processus de travail sera bien sûr requis pour maximiser l’efficacité des gens responsables de l’entrée des données.

    4. Clarifier le but du projet : Si vous désirez retracer une information précise, par exemple la durée de vie moyenne du clergé en fonction de leur poste, vous ne développerez pas les mêmes priorités que si vous désirez créer un espace de travail pour les historiens.

    Je vous conseillerais de chercher des projets semblables, et communiquer avec les responsables de ces projets. Ils vous en apprendrons sans doute beaucoup sur leurs erreurs, ou sur des bases existantes à partir desquelles vous pourriez démarrer ce projet.

    Bonne chance!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: