Le modèle de développement

Afin d'utiliser un langage efficacement, il est nécessaire de comprendre comment ses concepteurs avaient envisagé de s'en servir, c'est-à-dire comprendre son modèle de développement.

La plupart des langages d'aujourd'hui (C, C++, Java, ...) sont basés sur un modèle que nous appelons édition-compilation-test (voir figure 2.1). Le programmeur utilise un éditeur de texte pour écrire ou modifier son programme. Ce programme est ensuite compilé (transformé en code exécutable) puis finalement exécuté et son comportement enfin observé. Ces étapes sont répétées tant que le programme ne se comporte pas selon sa spécification.

Figure 2.1: Modèle de développement traditionnel
\begin{figure}\begin{center}
\input devtrad.pstex_t
\end{center}
\end{figure}

Ce modèle a un certain nombre de conséquences. En particulier, il y a une distinction claire entre déclarations et code exécutable. Certaines déclarations (variable globale, déclaration de classe, déclaration de fonction, etc) sont considérées comme existant avant l'exécution du programme principal. Il est impossible de modifier ces déclarations en cours d'exécution. Une autre conséquence est que le programme ne peut pas être exécuté tant que toutes les déclarations qu'il référence n'ont pas été créées, même si elles ne sont pas vraiment nécessaires pour ne tester qu'une partie du programme. L'éditeur de liens (en Anglais : linker) va simplement refuser la création d'un exécutable tant que toutes les déclarations ne sont pas faites. Le programmeur a donc un choix difficile : soit il crée une grande quantité de code avant de commencer à tester, soit il crée des déclarations temporaires (en Anglais : stubs) juste pour satisfaire l'éditeur de liens et qu'il modifiera au fur et à mesure de l'avancement du développement. Dans le premier cas, le test devient plus difficile, car un problème détecté peut se trouver dans plusieurs endroits possibles du code. Dans le deuxième cas, beaucoup d'énergie est dépensée à écrire du code qui sera jeté.

Common Lisp en revanche, est un langage interactif et incrémental. Le programme est construit en interaction avec une boucle lecture-évaluation-affichage (en Anglais : read-eval-print loop ou REPL). L'environnement de base est alors augmenté et modifié incrémentalement à chaque interaction avec l'utilisateur. Il n'y a pas de distinction entre code exécutable et déclaration (voir figure 2.2). Chaque déclaration est la conséquence d'une interaction. En conséquence, les déclarations Common Lisp sont aussi exécutables et peuvent donc être modifiées à n'importe quel moment du processus de développement. Les conséquences sur le programme de ce type de modification doivent donc être bien définies par la sémantique du langage. Ceci permet au programmeur de modifier ou de rajouter des déclarations par l'intermédiaire de l'exécution d'un programme, ce qui ouvre des possibilités concernant les méthodes et techniques de la programmation.

Figure 2.2: Modèle de développement Common Lisp
\begin{figure}\begin{center}
\input devlisp.pstex_t
\end{center}
\end{figure}

Un programme Common Lisp est donc simplement un état de l'environnement de programmation après exécution d'un certain nombre d'itérations de la boucle REPL. Il n'y a pas de programme principal. Le programmeur peut appeler n'importe quelle fonction de l'environnement et prétendre que c'est le programme principal. Bien évidement, avant de livrer le produit final, le programmeur créera très probablement un exécutable qui, dès son lancement, exécutera une fonction choisie par le programmeur et qui sera donc considérée comme le programme principal. Mais il est habituel qu'un programme Common Lisp n'ait de programme principal que très tard dans le cycle de développement.

Grâce à cette interactivité, le programmeur Common Lisp a tendance à pratiquer la programmation ascendante. Il écrit quelques fonctions et quelques déclarations, puis il les teste pour s'assurer qu'elles fonctionnent. À presque chaque instant du développement, le code existant fonctionne ou presque. Quand la dernière ligne de code a été tapée, il y a donc très peu de tests à effectuer pour terminer le travail.

Afin de permettre la diffusion du code, le programmeur doit le stocker dans un ou plusieurs fichiers. Comme avec des langages traditionnels, ces fichiers doivent donc contenir du code et des déclarations. Mais leur interprétation est complètement différente. Avec les langages traditionnels toutes les déclarations présentes dans les fichiers sources sont considérées comme créées simultanément, avant que le programme principal ne soit lancé. Avec Common Lisp le code de ces fichiers est considéré comme une suite d'interactions avec la boucle REPL. Il faut alors charger (en Anglais : load) ces fichiers dans l'environnement de programmation, séquentiellement, avant de démarrer le programme (ce démarrage peut d'ailleurs être une conséquence du chargement).

Pour disposer à la fois de la souplesse de l'environnement interactif et des fichiers de code source, le programmeur Common Lisp travaille souvent avec un éditeur sophistiqué comme Emacs. Typiquement, il crée et modifie le code à l'intérieur d'un tampon Emacs (en Anglais : buffer), puis demande à Emacs d'envoyer le code par morceaux dans l'environnement de programmation. Cet environnement s'exécute donc dans un sous-processus de Emacs. On consultera la section 5 pour une description complète de cette manière de travailler avec Emacs et Lisp en collaboration.

Irene DURAND
2011-10-14