Type-Driven Development

Le terme TDD est souvent utilisé pour désigner Test-Driven Development, je vous propose de découvrir, si vous ne la connaissez pas déjà, une autre signification de cet acronyme : Type-Driven Development.

Avant de découvrir ce terme, je pense qu'il est important de bien cerner ce qu'est le Test-Driven Development, c'est pourquoi je vous recommande la lecture de l'article Ça c’est TDD ! de Colin Damon ainsi que le livre Test-Driven Development: By Example de Kent Beck.

Une histoire de feedback

En tant que développeur, au quotidien, je manipule des langages de programmation afin de répondre à des besoins métier. Afin d'être certain que mes programmes fonctionnent et répondent à ces besoins, j'ai besoin de vérifier rapidement leur comportement. C'est pourquoi il existe de nombreux outils et méthodes pour vérifier le fonctionnement d'un programme.

Certains outils nécessitent le déploiement de mon application sur un serveur web pour que je puisse la consulter. Le fait de vérifier l'application manuellement, par exploration peut prendre de quelques minutes à plusieurs heures et n'est pas infaillible. Il est tout à fait possible de se tromper, de ne pas envisager tous les cas et ainsi ne pas avoir vérifié l'application de manière pertinente : après tout nous sommes humains.

D'autres outils permettent d'automatiser, on entendra parler de tests fonctionnels, mais le terme tests de bout en bout ou e2e (end to end) est plus approprié parce qu'on essaie de tester une application dans son ensemble, avec les briques qui communiquent autour. Cependant, les campagnes sont aussi assez longues, elles sont généralement plus rapides que les campagnes de tests manuels, disons un feedback autour de l'heure. Cependant, les tests e2e ne sont pas exempts de problèmes : les scénarios doivent être répétables et la complexité d’une stack peut impacter la stabilité des campagnes.

Des outils permettent un feedback encore plus rapide. Ils ne nécessitent pas de lancer l’application en local, ou seulement une partie, ce sont les tests unitaires. Appliqués à des méthodes comme le Test-Driven Development, les tests unitaires sont formidables pour leur feedback de l'ordre de quelques secondes lorsque le framework de test est bien configuré.

Et s'il était possible d'avoir un retour encore plus rapide ?

Note : Il est bien entendu important de respecter l'ensemble des niveaux de tests que je viens de décrire, n'hésitez pas à regarder La pyramide des tests de Kheops avec Hippolyte et Colin pour plus d'informations à ce sujet.

Vous l'aurez sans doute deviné, nous allons parler de Type-Driven Development.

C'est quoi un Type ?

Avant de rentrer dans l'utilité des types, parlons de ce que sont les types.

Au quotidien, vous manipulez des types, peu importe votre langage, qu'ils soient explicites ou implicites.

En Java, chaque classe, chaque interface, chaque type primitif est un type. En JavaScript ou en PHP, vous profitez de l'inférence de type en permanence même si vous ne précisez pas explicitement le type.

Des types algébriques

Maintenant que vous avez une idée de ce que sont des types, il est temps d'aborder les types algébriques.

Un type algébrique regroupe deux notions, celle de type produit et celle de type somme.

Type somme

Le type somme est l'union d'un ensemble de types ou de valeurs qui le composent.

Pour que ça soit moins difficile à avaler, prenons un enum TypeScript pour représenter les jours d'une semaine :

enum WEEKDAY {
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY,
  SUNDAY,
}

Chaque jour est représenté, on a donc le type somme WEEKDAY qui a une cardinalité de 7 puisqu'il y a 7 jours représentés dans la semaine.

L'enum n'est pas la seule façon de représenter un type somme, il est tout à possible d'obtenir quelque chose de similaire avec :

type Weekday = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';

Ce qu'on peut remarquer avec cette façon d'écrire, c'est qu'il est aussi possible d'associer des types :

type NumberAndString = number | string;

Comme vous pouvez en douter, avec de tels types, la cardinalité est la somme de la cardinalité de number et celle de string, ce qui fait beaucoup de possibilités.

Type produit

Le type produit est la composition d'un ensemble de types.

Pour que ça soit plus clair, prenons la représentation d'un rendez-vous avec l'interface TypeScript :

interface Appointment {
  start: WeekDay;
  end: WeekDay;
}

Bien entendu cette représentation n'est pas vouée à être réaliste.

On peut remarquer ici que la cardinalité de Appointment est le produit de celle de start et de celle de end, soit 7 * 7 et donc 49.

Les intérêts de contraindre par les types

Selon le langage, il est possible de contraindre par les types. Plus les possibilités de typage de votre langage le permettent, plus vous pourrez profiter de la richesse des types.

C'est d'ailleurs une des raisons qui me fait choisir d'utiliser TypeScript plutôt que JavaScript lorsque je développe des applications.

De plus, une erreur de type se verra avant ou pendant la compilation/interprétation de votre programme, le feedback sera donc plus rapide qu'un test unitaire.

Un type a aussi la particularité de nommer quelque chose, ainsi si vous devez représenter une Person, il sera sans doute plus pertinent d'utiliser des types FirstName et LastName plutôt que string.

Du T(ype)DD

Une façon de bien nommer couplée à un feedback rapide, c'est un outil parfait à ajouter à l'ensemble de ceux que nous connaissons déjà.

Ainsi, comme cet outil dispose d'un feedback plus rapide que ce que nous pouvons avoir avec des tests unitaires, je vous propose d'envisager de commencer par représenter vos types avant d'écrire le moindre test unitaire.

Cependant, diriger son développement par les types ne doit pas se substituer aux autres méthodologies d’écriture de code. Lorsque je ne peux pas représenter un fonctionnement logique avec mon système de type, je bascule sur du Test-Driven Development.

L'avantage de cet effort sur les types permet, via les cardinalités, de réduire la couverture de tests nécessaire en plus de clairement faire apparaître la terminologie métier.

Ainsi, je réduis le nombre de tests unitaires (que j'aurais initialement écrit en Test-Driven Development) tout en gardant leur pertinence et en réduisant les possibilités de bugs uniquement parce que je réduis les cardinalités via mes contraintes de types.

One More Type

Finalement, cet article n'est qu'une première visualisation de ce qu'il est possible de faire avec des types, il n'a pas pour vocation de rentrer plus en détail.

Pour aller plus loin sur les types, découvrir l'intérêt, je vous invite à visionner Introduction au T(ype)DD avec F# et web app réactive avec Fable.io de Florent Pellet et Clément Bouillier.

Et si vous souhaitez plus d'informations sur ce sujet, n'hésitez pas à lire Type-Driven Development with Idris d'Edwin Brady.