La philosophie Unix

Depuis 1969, les systèmes Unix se sont succédés sur des machines aussi variées que les fabricants ont su en inventer. 16 bits, 32 puis 64, Cisc, Risc, de 1 a 64 processeurs, parfois davantage sur des systèmes Cluster, les Unices ont toujours suivi l'évolution du matériel et proposé un système performant, simple et fiable. Bien sûr, le fait qu'il existe de nombreux Unices, qu'ils soient l'OS préféré des universités américaines et qu'il existe autant de versions libres que propriétaires a certainement aidé à la pérénité exceptionnelle de ce système. Mais il fallait plus que cela pour être encore en haut de l'affiche 30 ans après sa création : il fallait une philosophie.

Les choses petites sont belles

Unix est un système très complexe capable de gérer énormément de cas. Pourtant, il n'est composé quasiment que de petits programmes. Comment une association de petits programmes peut-elle apporter tant de solutions ?

En fait, c'est le principe même qui consiste à découper les problèmes en d'autres plus nombreux et plus petits de façon récursive jursqu'à ce que les solutions apportées à certains petits problèmes aident à la résolution d'autres. À partir d'un certain temps, il existe tant de solutions modulaires à des petits problèmes, qu'il suffit de les associer pour résoudre un grand nombre de problèmes.

Découper un problème en petites unités permettra de le résoudre plus simplement en utilisant des utilitaires standard Unix ou en tapant un petit morceau de code plutôt que de tenter de créer un gros programme qui ne répondra qu'au problème précis - ce qui diminuera les chances de réutilisation sur d'autres problèmes. Qui plus est, en écrivant un gros programme, on risque de ré-inventer la roue et de re-coder ce qui existe déjà sur le système.

Un programme ne sait faire qu'une chose

Non seulement votre programme doit être petit, mais il doit ne faire qu'une chose. En effet, il est beaucoup plus simple de mesurer et de s'assurer du bon fonctionnement d'un programme lorsque celui-ci n'a qu'un traitement à effectuer. Un résultat inattendu sur un programme de ce type est ainsi vite repéré et éliminé.

Au contraire, un programme qui applique plusieurs traitements oblige le développeur à vérifier chaque traitement et leurs interactions possibles au sein du programme si celui-ci est complexe et réalise un grand nombre de traitements.

La stabilité, pérennité et simplicité sont proportionnellement inverses au nombre de fonctionnalités que le programme propose.

Faire un prototype dès que possible

De toute façon, il est impossible de sortir un programme dans les temps. Il y a toujours un petit bug, une fonctionnalité supplémentaire à implémenter, un besoin, ... qui ne permetent pas de produire un code dans les temps.

En aucun cas il ne faut chercher à finaliser la première version : dès que le programme est exploitable - même incomplet ou peu stable, il faut en fournir une version aux testeurs/futurs utilisateurs. Les remontées de bug des utilisateurs sont souvent très différent de celles des développeurs : leur besoin en fonctionnalités est souvent très mouvant. Telle fonction ne sera pas utile et celle-là non prévue est au final indispensable. En fournissant un prototype très tôt, le développeur peut rectifier le tir en permanence et apporter une version utilisable beaucoup plus proche des besoins réels qu'il ne l'aurait pu faire seul.

La portabilité est plus importante que l'optimisation

Quoi qu'il en soit, une machine plus puissante sortira l'année prochaine et une autre encore plus l'année d'après. Unix a toujours traversé les époques et les plateformes grâce à la grande importance donnée à la portabilité du système.

En ce sens, il est évident qu'un développement sera plus rapide sur la génération suivante de matériel que sur la génération actuelle. Pourtant, encore faut il qu'un développement tourne sur la prochaine génération de matériel et que sa portabilité soit suffisante.

Utiliser un format de données ASCII plutôt que binaire

Il ne suffit pas qu'un développement soit portable. Il convient également que les données qu'il utilise le soient et permettent des échanges entre plateformes et systèmes d'exploitation. Les formats binaires souffrent des changements little endian / big endian : ils sont plus complexes à lire et il est indispensable d'avoir recours à un programme les comprenant et les traduisant en human readable.

L'autre intérêt d'utiliser des données au format ASCII tient à la plus grande simplicité de tests du développement et de son résultat.

Ré-utiliser le code avant de coder

Il est important de concevoir ses propres développements en pensant au résultat voulu dans l'instant, mais également, dans l'optique de ré-utilisation du code.

En ce sens, écrivons des librairies (ou des modules) pour le plus grand nombre de fonctionnalités du programme. De même qu'il vaut mieux écrire des programmes petits qui n'ont qu'une seule fonctionnalité, les fonctions seront aussi les plus petites possibles, les plus génériques, ...

Le corps principal du programme (le main) ne fera que des appels aux fonctions du programmes, il sera également le plus petit possible et surtout n'appliquera aucun traitement aux données lui-même.

Dans cette optique de ré-utilisation du code, il est tout à fait envisageable de considerer, comme code déjà utilisable, les autres programmes existants. Les utilitaire du shell sont la demonstration la plus efficace de ré-utilisation du code, ils offrent un grand nombre de fonctionnalitées déjà stabilisées et optimisées.

Préférer un langage de haut niveau

Les langages les plus simples et les plus portables sont des langages de haut niveau ; ils sont en outre les plus stables dans la mesure où leur fonctions ont déjà été testées.

En développant un shell script ou un Perl script, le développeur bénéficie déjà des milliers (millions) de lignes de code qu'il n'a pas besoin de chercher à ré-écrire. À quoi cela sert-il de créer une fonction qui existe déjà ?

Le Perl, par exemple, offre déjà un mécanisme d'allocation de mémoire stable et testé, ainsi au moment de penser votre application, vous excluez cette partie, très souvent génératrice de bugs, de votre conception. Le gain de temps de développement, lorsque l'on sait que Perl offre en fait un grand nombre de mécanismes déjà pensés et stabilisés, devient appréciable.

Bien souvent, d'ailleurs, les performances des langages de haut niveau vont être meilleures que vos propres programmes écrits avec un langage de bas niveau. Perl est optimisé, il fait lui même appel à un langage de bas niveau pour le quel il est optimisé au moment ou vous l'utilisez, il vous faudrait être au moins aussi compétent en C que les auteurs de Perl, ce qui n'est pas chose aisée.

L'autre intéret que l'on trouve à utiliser un langage de haut niveau est la portabilité. À l'époque ou le C fut inventé, l'objectif était de proposer un langage de haut niveau par rapport à l'assembleur. Perl est par rapport au C à la même échelle de portabilité que le C par rapport à l'assembleur. En Perl, vous savez que votre programme tournera sur toutes les plateformes sur les quelles Perl a été porté.

Ce qui est vrai pour Perl l'est également pour Python, le sh, Tcl, ...

Éviter les interfaces captives

La résolution d'un problème complexe est souvent simple : elle consiste à associer expérience et reflexion. Pour résoudre un problème, un utilisateur va parfois emprunter des voies que personne auparavant n'avait imaginés, et le concepteur d'un programme sera surpris de voir l'utilisation que l'on va parfois avoir de sa création.

Savez-vous que cat peut être utilisé pour copier des disquettes. dd,Même s'il répond mieux à ce type de problématique n'est pas toujours le mieux adapté au final. Le concepteur de cat n'a pas pensé son programme pour cette utilisation ; c'est l'utilisateur qui en a tiré une possibilité dérivée. Ce type d'utilisation des logiciels qui n'est pas forcèment possible sur des systèmes qui font appel à des interfaces captives.

Sur un système Unix, tout n'est que fichier, votre disque dur, lecteur de disquette, souris, ... est un fichier accessible de la même façon qu'un fichier texte. La commande cat est donc capable de lire une disquette comme si elle était un simple fichier et d'écrire sur une disquette pareillement.

En prenant ses données sur STDIN, en écrivant sa sortie sur STDOUT et les erreurs sur STDERR, vous êtes déjà sur que votre programme pourra travailler avec n'importe quel autre programme, fichier, ... de votre système.

Un programme est avant tout un filtre

Le fait de laisser passer le flux d'informations au travers les sorties standards permet d'inclure un programme au sein d'un pipe.

Unix offre la possibilité d'utiliser les pipes pour passer des informations d'un programme à un autre. Tout programme qui sait échanger ses données via un pipe est assuré de toujours pouvoir parler avec les autres programmes Unix de façon absolument standard et normale.

Pour conclure

En étudiant les programmes et utilitaires Unix les plus populaires et les plus standard, on se rend compte qu'ils suivent tous cette philosophie.

Cet article a été inspiré de The Unix Philosophy de Mike Gancarz, aux éditions Digital Press.