Vous êtes sur la page 1sur 33

23 mars

2017
L’ART DE PROGRAMMER 1

6
L’art de
programmer1
« Il n’y a pas de règles immuables à suivre pour écrire un programme logique, efficace et lisible. Il y a
bien sûr des guides, et certains sont meilleurs que d’autres; mais c’est le style du programmeur (ou son
absence de style), sa clairvoyance (ou non), sa créativité (ou son absence) qui déterminent la qualité du
produit final. »

Peter J. Denning, ACM Computing Surveys,


Volume 6, décembre 1974.

Le travail d’un programmeur consiste à trouver des solutions informatiques pour des problèmes particuliers et à
les mettre en place sous forme de programmes. L’apprenti programmeur doit reconnaître l’importance d’avoir
un style à son art, la programmation. Il doit développer très tôt des habitudes de réflexion et de rédaction qui le
suivront constamment au cours de sa vie professionnelle. On dit qu’une bonne connaissance des règles de la
grammaire n’est pas une garantie de succès pour un écrivain; aussi, la connaissance approfondie des guides de
programmation ne sera pas non plus une garantie d’un style de programmation apprécié. Ce chapitre tentera d’y
remédier.

6-1 L’IMPORTANCE DU STYLE

Les gens en création, comme les artistes, les compositeurs, les écrivains et les architectes travaillent beaucoup
pendant leur apprentissage pour maîtriser les fondements de leur art. Ils développent alors un style qui leur est
unique et personnel. Leur succès artistique et commercial dépend de ce style, car dans un milieu compétitif,
seuls ceux dont le style plaît et est reconnu sont en demande.

1. Extrait de Logique de programmation : initiation à l’approche algorithmique, chapitre 6, Jean-Paul Tremblay, Richard B. Bunt et
Paul G. Sorenson, McGraw-Hill Éditeur, 1985, p. 297-336.
2 LOGIQUE DE PROGRAMMATION

Dans tous les domaines et selon les circonstances, il y a des styles plus appropriés que d’autres. Par
exemple, certains styles de musique ou vêtements sont plus recherchés que d’autres et souvent par des gens
différents. Un style d’écriture particulier facilite une meilleure communication des idées, alors qu’un autre est
plus approprié pour la description de détails techniques. Certains styles architecturaux sont plus appropriés que
d’autres selon le climat.

Nous verrons au cours de ce chapitre, qu’en programmation le style qu’on adopte a des conséquences
certaines sur la valeur des programmes qu’on écrit. Les considérations de style sont indépendantes des stan-
dards professionnels et contribuent à l’amélioration de la qualité des programmes. Des recherches ont démontré
que certaines pratiques stylistiques aident à réduire le nombre d’erreurs produites durant le développement d’un
programme. En même temps, le programme devient plus lisible et compréhensible aux yeux des autres pro-
grammeurs qui seront possiblement appelés à modifier ce programme (comme vous serez appelé à modifier
celui d’un collègue qui est absent ou qui a quitté). La maintenance de programmes, c’est-à-dire, l’entretien de
programmes existants (que ce soit pour en corriger des lacunes ou pour les adapter à un environnement chan-
geant), occupe une portion importante du temps des programmeurs professionnels. Il n’est pas rare de voir
qu’effectivement, on consacre plus de temps à la maintenance qu’au développement original. Il ne faut donc pas
être surpris de constater l’intérêt des programmeurs et de la direction envers les techniques d’amélioration de
l’analyse et de la programmation pour réduire la portion de temps affecté à l’entretien. Tous préfèrent en général
consacrer ce temps précieux à de nouveaux développements.

Un bon style de programmation contribue indéniablement au succès professionnel d’un programmeur. La


reconnaissance de ce succès est très tangible ($$$), mais comme dans toutes les disciplines, il est bon de ne
jamais se laisser aller. Nous étudierons donc dans ce chapitre plusieurs points importants de la programmation
stylisée. Nous espérons ainsi stimuler l’intérêt du lecteur tout en lui présentant la démarche à suivre en situation
de réalisation informatique. Au cours de l’étude de ce processus, nous en examinerons les pièges et comment il
faut les contourner.

6-2 QUALITÉ D’UN PROGRAMME

Avant de nous aventurer trop profondément dans l’étude des méthodes d’amélioration d’un programme, peut-être
devrions-nous définir ce qui est recherché. Le concept d’un bon programme est bien sûr difficile à saisir. Si l’on
pose la question aux programmeurs professionnels et à leurs supérieurs, la gamme des réponses est surprenante;
elle est matière de goût et basée sur l’expérience de chacun. Mais ils s’entendront tous sur un certain nombre de
points communs. En voici quelques-uns :
L’ART DE PROGRAMMER 3

Bon fonctionnement d’un programme

Le bon fonctionnement d’un programme est l’aspect le plus important et qu’il ne faut jamais perdre de vue. Cela
semble évident! Et pourtant c’est le point le plus difficile à garantir. Les journaux et les revues spécialisées sont
remplis d’histoires de programmations catastrophiques et dispendieuses. Bien sûr, de tels incidents ne représen-
tent qu’une faible proportion de tous les programmes écrits, mais ils perturbent le milieu professionnel informa-
tique et sont en partie responsables de la méfiance et de l’aversion du grand public en général, et des petites
compagnies en particulier.

Le programmeur doit s’assurer aussi que le programme éventuellement mis en place répondra aux spéci-
fications requises. Il est très facile de se perdre dans des détails et en conséquence, de perdre de vue le but
premier du programme. Solutionner en partie un problème posé ne satisfait pas le client et risque de lui faire
plus de tort que de bien. Il est important de revoir continuellement les spécifications (qui sont d’ailleurs sujettes
à des changements) tout au cours des phases d’analyse et de développement; on se méfiera des spécifications
erronées, incomplètes ou incomprises. Il ne faut pas non plus embellir un système de caractéristiques non requi-
ses (même si l’on a eu du plaisir à les programmer); elles sont susceptibles de tomber éventuellement en panne
et de créer du temps de maintenance, ou même d’embêter carrément un confrère qui examine le programme et
de lui faire perdre son temps (et sa patience).

Absence d’erreurs

Trop de programmeurs considèrent que les erreurs de programmation sont humaines et qu’un programme est
voué à un entretien éternel. Il n’y a aucune raison logique à cela. On a beau dire que les erreurs germent comme
des champignons dans la nuit, en fait, le programmeur est le seul responsable, par négligence ou manque de
compréhension, des spécifications et des particularités du langage ou de l’ordinateur, ou simplement par l’oubli
d’un cas particulier qui était possible (ou même impossible) et qui s’est présenté à un moment donné. Bien sûr,
le programmeur ne le fait pas délibérément (peu adorent provoquer les erreurs), mais elles peuvent être évitées
pour la plupart. Bestougeff (1975) et Arsac (1977) parlent de techniques d’écriture de programmes sans erreurs
à l’aide d’une écriture simple et récurrente des algorithmes. On a fait beaucoup de recherches en informatique
pour trouver des modèles mathématiques formels qui assurent l’exactitude d’un algorithme. Si ces preuves sont
toujours possibles, elles n’en sont pas moins souvent ardues, longues et rarement pratiques dans l’industrie.
Puisque le programmeur est responsable dans tous les cas de ses erreurs de programmation, il doit avoir recours
à d’autres techniques pour s’assurer de l’exactitude de son programme (par exemple, à l’aide de tests appro-
priés). Nous en reparlerons dans la prochaine section.

Bonne documentation

La documentation d’un programme est là pour aider à comprendre et à modifier un programme, et est à ce titre,
une partie importante de la programmation; il ne faut pas la négliger. La documentation s’avère utile non seule-
ment pour ceux qui ont à comprendre et à modifier un programme, mais également pour l’auteur. Beaucoup de
programmeurs travaillent concurremment sur plusieurs projets, donc sur de nombreux programmes ou sections
de programmes (plus, peut-être, un peu de maintenance impromptue et urgente) et peuvent oublier facilement
où ils en étaient rendus. Ils mélangent tout, surtout s’ils n’ont pas documenté convenablement les programmes
au fur et à mesure de leur écriture.
4 LOGIQUE DE PROGRAMMATION

Il existe deux formes de documentation : la documentation externe qui consiste en documents d’utilisation
pour l’usager, de présentation du problème posé, de l’analyse, des algorithmes, des diagrammes, des program-
mes, etc. La documentation interne est constituée des commentaires et des explications que l’on retrouve dans
les algorithmes et programmes. On ne peut pas trop insister sur l’importance de cette dernière documentation
qui, avec les instructions mêmes, sont indispensables à quiconque doit examiner le corps d’un programme. Les
instructions constituent la première partie de la documentation interne et voilà pourquoi on insiste sur une
écriture soignée des programmes. La documentation externe s’adresse souvent davantage aux supérieurs ou à
l’usager qui ne savent que faire d’un imprimé du(des) programme(s), mais qui aiment bien voir la structure du
système qu’on leur propose. Elle est également destinée au collègue qui doit fouiller dans un système informa-
tisé pour y localiser un module à modifier.

Efficacité

La question d’efficacité est épineuse. Au début de l’ère informatique, les ordinateurs étaient lents, limités et
dispendieux; les programmes devaient être construits minutieusement pour maximiser l’emploi de ressources
peu abondantes, telles l’espace mémoire et le temps d’exécution. Les programmeurs consacraient de longues
heures à minimiser le temps d’exécution (sauver quelques secondes) et à forcer leurs programmes à entrer dans
un minimum de mémoire (déjà fixé). On mesurait alors l’efficacité d’un programme par le produit : espace ×
temps. L’idéal était d’obtenir la plus petite valeur de cette expression.

De nos jours, la situation a complètement changé. Les coûts du matériel ont chuté considérablement, alors
que les coûts du personnel qualifié ont crû effroyablement. L’espace mémoire et le temps d’exécution ne sont
plus aussi précieux qu’auparavant. Bien sûr, on doit toujours être attentif aux économies importantes qui peu-
vent résulter de l’emploi d’une méthode plus avantageuse, telle le remplacement d’une recherche séquentielle
par une recherche dichotomique, mais le programmeur n’a surtout plus à se soucier de l’emploi optimal des
ressources de l’ordinateur. Seules quelques applications (généralement pour les systèmes d’exploitation) justi-
fient encore un tel effort.

Quoiqu’il en soit, il existe encore beaucoup de programmeurs qui ne jurent que par cette mesure d’effica-
cité et qui en conséquence, produisent des programmes indéchiffrables. Un programme qui fonctionne pénible-
ment ou est difficile à entretenir est de piètre qualité et cela malgré l’emploi restreint de l’espace mémoire et du
temps machine.

On peut donc constater que la qualité d’un programme est multiple. Évidemment, il est important et même
primordial qu’un programme fonctionne correctement et soit fiable, c’est-à-dire que tous les cas disponibles et
impossibles soient prévus, mais cela ne s’arrête pas là. Lehman et Parr (1976) ont étudié l’évolution de l’écriture
des programmes et ont découvert que ceux-ci devaient être continuellement entretenus et modifiés pour concor-
der avec les besoins des usagers et les développements technologiques. En conséquence, les programmes doi-
vent avoir principalement un entretien aisé et être facilement modifiables; un programme lisible et rapidement
compréhensible par un collègue est aussi un prérequis indispensable. En résumé, on doit écrire des programmes
fonctionnels, corrects, fiables, d’entretien facile, modifiables, lisibles et compréhensibles.

Nous voulons au cours de la suite de ce chapitre suggérer comment écrire de « bons » programmes. On
examine d’abord les différentes phases de l’écriture d’un programme en faisant remarquer les difficultés poten-
tielles qui sont propres à chacune. On examine ensuite plus en détail les tâches confiées au programmeur. On
insiste à chaque fois sur le style d’une bonne programmation et son impact sur la qualité de l’œuvre.
L’ART DE PROGRAMMER 5

6-3 LES PHASES D’ÉCRITURE D’UN PROGRAMME

Le mot « programmeur » représente différentes choses pour différentes personnes. Une étude salariale publiée
par « La Société Canadienne de l’Informatique » définit ainsi le travail du programmeur :

Analyse au niveau des besoins et possibilités matérielles, les problèmes soulevés par l’analyste de sys-
tème. Trace les ordinogrammes détaillés. Écrit les instructions des programmes. Vérifie la logique des
programmes en préparant des jeux d’essais. Teste et corrige les programmes. Prépare pour l’opérateur,
les instructions à suivre lors de la phase de production. Examine et modifie les programmes existants pour
les adapter aux changements techniques ou technologiques.

Les phases de l’écriture de programmes sont, selon Cooke et Bunt (1975a), les suivantes :

1. Analyse du problème
2. Élaboration d’une solution algorithmique
3. Écriture du programme
4. Test
5. Maintenance

À partir de cette liste, examinons successivement chacune de ces étapes plus en détail.

À la phase d’analyse du problème, le programmeur étudie le problème pour en connaître toutes les facet-
tes. Il doit intérioriser le problème, s’identifier à l’usager. C’est une étape cognitive et difficile à décrire. Trop de
programmeurs négligent cette phase et en conséquence, les spécifications peuvent être incomprises ou mal
connues. Il est bon, comme le font plusieurs programmeurs, de rencontrer l’analyste ou l’usager pour lui expli-
quer sa compréhension des spécifications et se faire corriger au besoin, car cela réduit considérablement les
chances de faire fausse route. Les erreurs à ce niveau sont souvent difficiles à retracer, embêtantes et coûteuses
à réparer.

La phase d’élaboration d’une solution algorithmique est fondamentalement la plus créative. Ce livre entier
met l’accent sur la séparation de la phase d’élaboration d’une solution (recherche d’un algorithme) et de la
phase d’écriture d’un programme (dans un langage de programmation particulier), en insistant sur l’importance
de la première. Quelques auteurs insistent sur le caractère inhérent de l’habileté d’une personne (programmeur)
à trouver la solution adéquate d’un problème et admettent par conséquent qu’il est difficile de développer ou
d’améliorer la créativité d’un candidat. Cependant, les preuves abondent à l’effet que les méthodes d’approche
systématique (telles que la méthode descendante présentée à la section 6-4) ont de grands mérites à ce point de
vue. Malheureusement, beaucoup de programmeurs hantés par le défi de la machine succombent à la tentation
d’écrire la solution directement dans un langage de programmation (pour voir si leur première idée fonctionne),
avant même d’avoir complètement solutionné le problème et à défaut, d’avoir la meilleure solution possible;
ceux qui partent à la conquête de la machine avec une première ébauche, se retrouvent souvent trop avancés
pour pouvoir reculer et recommencer autrement, d’où l’essai de poursuite en raccommodant tant bien que mal
les incohérences de la logique initiale.

À la troisième phase, on choisit un langage de programmation particulier et on écrit les instructions du


programme (codification) selon les algorithmes et les sous-algorithmes élaborés à la phase précédente. Si la
solution est bien exprimée, cette étape est relativement simple et mécanique. On respecte les règles du langage
choisi (au besoin, on a recours aux manuels de référence du manufacturier) et les règles (standards) de style et de
6 LOGIQUE DE PROGRAMMATION

structures en vigueur (elles diffèrent légèrement d’un employeur à un autre). Style et structure sont des guides
d’écriture de programmes lisibles et compréhensibles qu’on respecte à chaque ligne du programme et qu’on ne
rajoute pas seulement à la fin de l’écriture pour contenter ses supérieurs.

La quatrième phase consiste à prouver le bon fonctionnement du programme en situation réelle. Bien sûr,
les deux étapes précédentes ont permis de vérifier sur papier le fonctionnement des algorithmes et des sous-
algorithmes puisque la trace d’exécution simule l’exécution réelle mais... on ne sait jamais. Vérifier tous les cas
possibles et impossibles est souvent une tâche fastidieuse et impossible. Dijkstra remarque que même si les tests
démontrent la présence des erreurs, ils n’en démontrent jamais leur absence. Un test réussi indique simplement
qu’aucune erreur n’a été découverte par le jeu d’essai utilisé; mais si l’on change le jeu d’essai, est-on toujours
assuré du bon fonctionnement? En théorie, la seule façon qu’un test puisse montrer qu’un programme est cor-
rect serait d’essayer toutes les combinaisons possibles (test exhaustif), ce qui est impensable même pour un
programme simple. Supposons, par exemple, qu’on veuille tester un programme qui calcule la moyenne des
notes d’un examen. Un test exhaustif exigerait la présence dans le jeu d’essai de toutes les combinaisons de
notes pour des classes de toutes dimensions; il faudrait des années pour établir cette preuve.

Est-ce que cela veut dire qu’il est impossible d’effectuer un test correct? Bien sûr que non. On peut facile-
ment réduire le nombre de combinaisons requises, en prenant soin de choisir des cas représentatifs de l’ensem-
ble et des cas représentatifs des combinaisons limites. Ainsi, le jeu d’essai sera complet et d’un volume acceptable.

La vérification d’un programme est autant un art que son écriture; il doit être vu avec enthousiasme et avec
la même ouverture d’esprit. Une certaine méthodologie est essentielle. Mettons-nous dans la peau d’un saboteur
et faisons l’essai des cas limites qui pourraient détraquer le programme; les points faibles nous sauteront aux
yeux. Soyons soupçonneux. Les tests doivent être pensés à partir des spécifications originales et non à partir du
programme qu’on a écrit. Si l’on procède suivant ce dernier on risque de fournir un produit qui sera peut-être
sans erreur mais qui ne correspondra pas encore une fois aux besoins exprimés. Pour diminuer ce risque, on
demande dans beaucoup de centres informatiques à une autre personne de faire les tests à partir des dernières
spécifications reconnues. De plus, l’usager a souvent son propre jeu d’essai qu’il voudra utiliser pour tester le
programme avant d’en prendre livraison. On doit savoir respecter la colère d’un supérieur lorsqu’un programme
échoue sur le jeu d’essai du client, car cela peut sérieusement mettre en jeu la réputation de la compagnie et la
fierté des gens qui la composent. Des tests sérieux sont essentiels à la reconnaissance des qualités d’un bon
programme.

Les apprentis programmeurs n’ont malheureusement que rarement l’occasion de s’impliquer au niveau de
la cinquième phase : la maintenance de programmes. Son importance dans la vie quotidienne d’un program-
meur ne doit pas être sous-estimée. F.P. Brooks (1975) mentionne que le coût d’entretien d’un programme
couramment utilisé représente régulièrement 40 % et plus de son coût de développement. Lors de l’entretien
d’un matériel, on remplace en général seulement les pièces défectueuses par usure ou par défaut de fabrication
et on repart à neuf. La maintenance d’un logiciel n’est pas aussi simple et on peut rarement remplacer un
programme; il faut en trouver l’erreur d’analyse ou de programmation et rapiécer ou encore rajouter des fonc-
tions ou procédures qui tiendront compte de modifications des spécifications ou de changements du matériel
informatique. La disponibilité du programmeur pour écrire de nouveaux programmes est limitée par le temps
qu’il consacre à la maintenance des vieux programmes. L’entretien obligatoire doit être reconnu et on doit, dans
la recherche d’une solution et l’écriture du code de nouveaux programmes, tenter d’en faciliter la tâche.
L’ART DE PROGRAMMER 7

Les autres sections de ce chapitre traitent principalement du développement et de la codification des pro-
grammes. Cependant, on ne doit pas perdre de vue les autres phases mentionnées car en pratique, elles sont
fortement inter-reliées et il est de ce fait aberrant et peut-être même dangereux d’essayer de les traiter isolément.
Il arrive, en effet, que l’importance d’une décision prise à une phase n’est perçue que dans une autre phase du
projet.

6-4 ANALYSE DESCENDANTE

La programmation est sans contredit une activité complexe; elle combine plusieurs activités mentales. Diffé-
rents aspects d’un même problème doivent être agencés en un produit final : le système informatisé. On peut
comparer cette activité à celle d’un jongleur qui essaie de manipuler beaucoup d’objets en même temps, il risque
fortement d’en laisser tomber quelques-uns (voir la figure 6-1).

Énoncé du
problème

Langage de
Cas
program-
spéciaux
mation

Algorithme Sous-
algorithmes

FIGURE 6-1 Le programmeur-jongleur.

Nous avons déjà discuté de l’importance de l’approche « diviser pour régner » lorsqu’on aborde un pro-
blème quelconque. Poursuivons l’analogie avec le jongleur : cela signifie qu’on ne devrait manipuler qu’un
8 LOGIQUE DE PROGRAMMATION

sous-ensemble des objets à la fois. On a fait un pas en ce sens, en séparant les phases de recherche d’une solution
de la phase d’écriture des instructions dans un langage approprié. Dans cette section, on divise davantage en
utilisant la méthode dite descendante dont le but est de structurer le découpage du problème à traiter.

La solution de tout problème peut exister sous plusieurs représentations ou autrement dit, plusieurs niveaux
d’abstraction. Pour citer Niklaus Wirth (1974) : « L’abstraction est la faculté mentale la plus importante pour
faire face à un problème complexe. Aussi ne doit-on pas envisager un problème immédiatement sous forme
d’instructions... mais plutôt en terme d’entités plus communes à ce problème, à un niveau abstrait adéquat ». Au
chapitre 1, on a traité d’algorithmes généraux et d’algorithmes détaillés ou plus spécifiques. Nous abordions
alors le problème sous un aspect général ou abstrait, en l’énonçant avec les mots de sa définition. On raffinait
ensuite cette ébauche en élaborant les détails antérieurement ignorés et le résultat en était une solution déjà
moins abstraite. On a continué ainsi en plusieurs raffinements successifs jusqu’au niveau de détail désiré. C’est
l’essence même de la méthode descendante. On procède d’une première ébauche abstraite (le niveau supérieur),
vers un niveau inférieur correspondant à la solution détaillée prête à être programmée, à l’aide d’une suite
d’étapes successives de raffinement. Cette méthode est indépendante de tous langages de programmation. En
fait, on se rapproche des considérations physiques de la programmation, mais sans y entrer.

Cette méthode a été (peut-être sans le savoir) utilisée pendant des années par les bons programmeurs. Ce
n’est que récemment qu’on lui a donnée un nom et qu’on l’a popularisée (voir entre autres, Harlan Mills (1971)).
De fait, différentes personnes lui ont donnée différents noms : « raffinement par étapes » (Wirth, 1971), « modèle
itératif multi-niveaux » (Zurcher et Randelle, 1968) et « programmation hiérarchique » (Dijkstra, 1968). Cette
méthode est intéressante parce qu’elle semble structurer les étapes du développement d’un programme. Elle met
d’abord l’emphase sur l’analyse avant de s’occuper des détails d’écriture dans un langage, réduisant ainsi le
nombre d’objets à manipuler simultanément. Comme dans le cas de tout outil, une personne qualifiée saura
mieux s’en servir qu’une autre, car le bon sens, l’intuition et la créativité demeurent les qualités essentielles
d’un bon programmeur.

Illustrons la méthode descendante à l’aide de deux cas pratiques différents. Le premier qui est simple,
permet d’attirer l’attention sur la technique de recherche de la solution et sur la façon de la représenter. Le
deuxième exemple est plus complexe et permet de reconnaître la valeur de cette technique pour affronter des
problèmes plus étoffés.

Le premier problème consiste à lier un nombre N (entier) et à imprimer la liste des carrés parfaits de 1 à N
inclus. Par exemple, si N vaut 30, on imprime la liste suivante :

1 4 9 16 25

On débute en écrivant une première solution très abstraite, mais simple :

Écrire la liste des carrés parfaits de 1 à N

Ceci décrit bien ce qu’on veut faire, mais non comment on va le faire. À ce stade, les détails importent peu; cela
viendra au fur et à mesure de l’élaboration des parties (raffinement) de cette solution de base. Ce qu’on établira
maintenant.
L’ART DE PROGRAMMER 9

Un premier raffinement consiste à séparer cette solution en deux étapes ou modules évidents :

LIRE un nombre N
Écrire les carrés parfaits de 1 à N

Pour mettre en évidence le lien hiérarchique entre ces deux modules et le module original, on les représente sous
forme graphique à la figure 6-2. Chaque niveau sur la figure correspond à un des niveaux d’abstraction, le
niveau plus abstrait se situant en haut. Les lignes indiquent la suite des raffinements. Ici, le module supérieur a
été raffiné et à l’étape suivante, on trouve deux nouveaux modules.

On raffine maintenant chacun des modules de ce deuxième niveau d’abstraction, en précisant de nouveau
le comment de leur traitement. Le module de gauche (second niveau) est déjà suffisamment détaillé pour qu’on
puisse l’écrire directement (par LIRE (N) dans la notation algorithmique); aussi n’est-il pas nécessaire de le
raffiner davantage. Le module de droite doit être décomposé de nouveau, il peut être ainsi écrit :

Initialisation d’un compteur


Boucle d’impression TANT QUE on a un carré parfait inférieur ou égal à N

La figure 6-3 illustre ceci schématiquement. Ce dernier raffinement ne semble pas avoir contribué beau-
coup à la solution, mais il ne faut pas s’y méprendre; l’initialisation et la boucle n’étaient pas explicites au
niveau précédent, mais plus abstrait. Ce sont des détails qui rapprochent de la solution finale, à laquelle il ne
manque que deux nouveaux modules à rattacher à la boucle. Ce sont :

ÉCRIRE un carré parfait


Incrémentation du compteur

Écrire la liste des


carrés parfaits
de 1 à N

LIRE un Écrire les carrés


nombre N parfaits de 1 à N

FIGURE 6-2 Méthode descendante pour le problème des carrés parfaits (solution générale).
10 LOGIQUE DE PROGRAMMATION

Écrire la liste des


carrés parfaits
de 1 à N

LIRE un Écrire les carrés


nombre N parfaits de 1 à N

Boucle d'impression
Initialisation TANT QUE on a un
d'un compteur carré parfait inférieur
ou égal à N

FIGURE 6-3 Méthode descendante pour le problème des carrés parfaits (solution plus raffinée).

Écrire la liste des


carrés parfaits
de 1 à N

LIRE un Écrire les carrés


nombre N parfaits de 1 à N

Boucle d'impression
Initialisation TANT QUE on a un
d'un compteur carré parfait inférieur
ou égal à N

ÉCRIRE un Incrémentation
carré parfait du compteur

FIGURE 6-4 Méthode descendante pour le problème des carrés parfaits (solution finale).
L’ART DE PROGRAMMER 11

Voilà le complément de l’analyse que l’on peut maintenant écrire sous forme d’un algorithme. La
décomposition finale est représentée à la figure 6-4; elle sert de guide pour l’écriture détaillée de l’algorithme.
Les quatre rectangles n’ayant pas de suites (et qualifiés de boîtes terminales) représentent des entités qui risquent
d’être rencontrées telles quelles dans les étapes réelles de l’algorithme. Les autres rectangles (boîtes non-
terminales) reflètent davantage les décisions structurales et indiquent le cheminement de la décomposition du
problème. Ils peuvent d’ailleurs apparaître dans l’algorithme final sous forme de structures de contrôle ou
seulement en commentaires. Voici l’algorithme CARRE_PARFAIT qui en résulte :

Algorithme CARRE_PARFAIT
Cet algorithme lit un nombre N (entier positif) et imprime la liste des carrés parfaits de 1 à N. La variable k
(entier) est un compteur de boucle.
1. [Lecture d’un nombre]
LIRE (N)
2. [Initialisation]
k←1
3. [Boucle de recherche et d’impression des carrés parfaits]
TANT QUE k↑2 ≤ N RÉPÉTER
4. [Portée de la boucle]
ÉCRIRE (k↑2)
k←k+1
5. [Arrêt de l’exécution]
STOP

Cet exemple illustre bien la pratique de la méthode d’analyse descendante. La solution du problème est
24 mars
2017 dérivée de la décomposition systématique du problème en sous-problèmes plus simples (« diviser pour régner »).
À chaque niveau d’abstraction et pour chacun des modules, on se demande quelle décomposition supplémen-
taire élémentaire effectuer pour se rapprocher de la solution complète et finale. On poursuit ainsi jusqu’à ce
qu’on ait des modules pouvant tous être écrits facilement en notation algorithmique.

Cette approche comporte plusieurs autres avantages. Les modules deviennent suffisamment « petits » (en
termes de lignes de codification) pour être facilement compris par tous. Le danger d’oublier quelque chose est
amoindri, ainsi que les chances d’erreurs. Le plan de vérification s’impose presque de lui-même (on suit géné-
ralement la structure ainsi mise en évidence). Les modules, dont les fonctions sont maintenant clairement iden-
tifiées, peuvent être testées séparément, ce qui est plus simple à réaliser que le test du programme complet pris
comme un ensemble. Les liens (d’appel dans certains cas) entre deux modules sont également définis par la
structure du diagramme et peuvent être vérifiés par des tests extensifs sur chacun de ceux-ci. Un dernier avan-
tage de la méthode descendante, encore une fois suggéré par la structure de la décomposition, est l’évidence des
commentaires à inclure dans la documentation du programme et du système informatisé qui contribuent égale-
ment à améliorer la lisibilité générale du programme.

Voici maintenant un problème plus complexe, soit le problème de la paternité (ou maternité) d’un texte.
Sommairement, le problème consiste à déterminer qui de deux auteurs est responsable de l’écriture d’un texte
donné. En utilisant une méthode similaire à celle représentée à la section 4-4.1, on cherchera dans des textes
connus et appartenant à chacun des auteurs, la fréquence de certains mots discriminants. Ces fréquences sont
alors comparées aux fréquences des mêmes mots dans le texte en litige; une étude statistique peut alors prédire
12 LOGIQUE DE PROGRAMMATION

qui pourrait en être l’auteur. Mosteller et Wallace (1963) rapportent que cette méthode a été utilisée pour iden-
tifier les auteurs de pamphlets anonymes qui avaient été écrits par Alexander Hamilton, James Madison et John
Jay lors de l’adoption de la Constitution des États-Unis.

TABLEAU 6-1 Fréquences observées de mots connus (par 1000 mots) chez Tremblay et chez Bunt.

Mot discriminant chez Tremblay chez Bunt


à 22,0 19,7
alors 2,7 4,6
avec 3,1 19,3
dans 62,1 41,0
de 38,7 56,9
donnée 46,9 32,3
efficace 4,6 13,8
est 23,6 31,8
et 17,7 18,5
être 18,0 16,0
exemple 10,3 8,0
il 17,3 5,2
lequel 27,2 35,2
lorsque 6,0 12,9
mémoire 8,2 12,0
nous 8,3 20,6
pour 9,7 3,2
pratique 2,7 15,7
programmeur 9,1 11,1
qualité 3,1 24,2
que 1,0 11,4
sur 19,3 27,4
un 73,2 60,4

Deux auteurs, Tremblay et Bunt, réclament les droits d’auteur d’un texte. On analyse des échantillons de
textes de chacun; le tableau 6-1 en donne les résultats. En trouvant la fréquence des mêmes mots discriminants
dans le texte en litige et en utilisant la méthode des moindres carrés, on essaie de déclarer qui est l’auteur
probable du texte. Illustrons la méthode des moindres carrés en prenant le mot « sur ». Si ce mot revient dans le
texte contesté à la fréquence de 22 fois tous les 1000 mots, alors les différences aux carrés sont :

(19,3 – 22)2 pour Tremblay


et (27,4 – 22)2 pour Bunt.

Remarquons que la méthode des moindres carrés est une approche statistique qui détermine une valeur des plus
probables et indépendante du signe de la différence puisqu’on en prend le carré. Elle permet également d’ampli-
fier cette différence si elle existe; il devient alors plus facile de « voir » cette différence.
L’ART DE PROGRAMMER 13

Le fichier de lecture comprend les informations suivantes et dans cet ordre :

– nombre de mots discriminants


– le nom des deux auteurs (ici, Tremblay et Bunt)
– liste des mots discriminants et des fréquences usuelles chez les deux auteurs
– le texte à examiner (où tous les mots sont séparés par des espaces, la ponctuation a été éliminée).

On débute l’analyse telle que précédemment, avec une première solution des plus abstraites :

Déterminer l’auteur du texte.

Que l’on peut tout de suite séparer en deux modules plus simples :

Obtenir les fréquences connues des mots discriminants et les fréquences des mêmes mots dans le texte en
litige.

Identifier l’auteur probable.

La figure 6-5 illustre la structure hiérarchique de ces trois modules. Les modules du niveau 2 indiquent com-
ment déterminer l’auteur, mais ils ne sont pas suffisamment détaillés pour qu’on puisse en écrire l’algorithme.

Le raffinement du module de gauche nécessite plusieurs sous-modules. On introduit donc certains détails
de la structure des données utilisées, ainsi que les principaux calculs requis. Voici un premier découpage :

Lire les mots discriminants et les fréquences connues des mots discriminants de chaque auteur et les
ranger dans trois vecteurs.

Trouver les fréquences des mots discriminants du texte à l’examen et les ranger dans un vecteur.

Déterminer
l'auteur
du texte

Obtenir les fréquences


connues les mots Identifier l'auteur
discriminants et les probable
fréquences tirées du texte

FIGURE 6-5 Méthode descendante pour le problème de paternité (solution générale).

On retrouve de nouveau à la figure 6-6, le schéma de représentation de cet ajout à la solution. Afin de
repérer aisément un module, on numérote chaque module de la façon suivante : numéro du module immédiate-
ment supérieur suivi d’un point et suivi d’un chiffre, donnant l’ordre (de gauche vers la droite) de ce sous-module
par rapport au module auquel il se rattache. Le premier module porte simplement le numéro 1. Le nombre de
chiffres composant ce numéro donne le niveau (on ne devrait pas avoir plus de 9 modules rattachés à un même
module supérieur).
14 LOGIQUE DE PROGRAMMATION

Déterminer
l'auteur
du texte

Obtenir les fréquences


connues des mots Identifier l'auteur
discriminants et les fré- probable
quences du texte en litige

Lire les mots discriminants


Trouver les fréquences
et les fréquences connues
des mots discriminants
des mots discriminants de
du texte à l'examen et les
chaque auteur, et les ranger
ranger dans un vecteur
dans trois vecteurs

FIGURE 6-6 Méthode descendante pour le problème de paternité (solution plus détaillée).

Une élaboration plus complète des détails de ces derniers modules implique le traitement physique de la
lecture des données (selon l’organisation mentionnée des données dans la description du problème), du range-
ment de ces données en mémoire (organisation des structures de données requises) et des calculs à effectuer.
Pour le module 1.1.1 (voir la figure 6-7), les étapes sont maintenant :

Lecture du nombre de mots


Lecture des noms des auteurs
Boucle de lecture des mots et des fréquences de chaque auteur

Ceci est un exemple évident d’un module supérieur déclarant une action à effectuer et de modules subal-
ternes qui font le travail et retournent les résultats requis. En particulier, le module supérieur requiert les fré-
quences des deux auteurs, les modules subalternes les lui fournissent. Le module supérieur n’est pas concerné
par les détails du format de lecture mais seulement du fait qu’il veut trois vecteurs correctement remplis. Ceci
illustre également un principe important de la structure hiérarchique descendante : le contrôle se fait vers le bas
et les résultats voyagent vers le haut.

Le traitement du module 1.1.2 exige la lecture du texte controversé, puis le comptage des occurrences de
chacun des mots discriminants ainsi que le calcul de leur fréquence (par 1000 mots). Le découpage de ce
module peut donc être (voir la figure 6-8) :

Lecture du texte
Boucle sur les mots du texte
Boucle sur les mots discriminants
Comptage des occurrences
Boucle de calcul des fréquences
L’ART DE PROGRAMMER 15

Le module 1.2 n’est pas très complexe, on peut cependant le décomposer en deux modules, comme suit
(voir figure 6-9) :

Lire les mots discriminants et les


fréquences connues des mots
discriminants de chaque auteur, et
les ranger dans trois vecteurs

Boucle de lecture
Lecture du nombre Lecture du nom des mots et des
de mots des auteurs fréquences de
chaque auteur

FIGURE 6-7 Méthode descendante pour le problème de paternité (schéma du module 1.1.1)

Trouver les fréquences des


mots discriminants du texte
à l'examen et les ranger
dans un vecteur

Boucle sur les mots du texte


Boucle sur les mots Boucle de calcul
Lecture du texte
discriminants des fréquences
Comptage des occurrences

FIGURE 6-8 Méthode descendante pour le problème de paternité (schéma du module 1.1.2)

Calcul des moindres carrés de chaque auteur (à l’aide d’une double boucle)
Choix de l’auteur ayant la plus petite somme de ces moindres carrés.

Ceci termine l’analyse de la solution de ce problème; la figure 6-10 en présente un schéma complet. Bien
sûr, cette solution n’est pas unique; les niveaux peuvent être plus ou moins nombreux et cependant représenter
une solution tout aussi valable. Dans ces deux cas, on a peut-être exagéré le niveau du détail mais nous voulions
illustrer par des exemples simples le processus du découpage descendant permettant d’aboutir à la solution
adéquate. Voici maintenant la solution détaillée en notation algorithmique :

Algorithme AUTEUR_NITE

Cet algorithme essaie de déterminer la paternité ou maternité d’un texte. Les données sont dans l’ordre men-
tionné précédemment. Des vecteurs parallèles contiennent les mots (MOT: vecteur l..NB_MOTS de textes), les
fréquences des mots des deux auteurs (FREQ_1, FREQ_2: vecteurs
16 LOGIQUE DE PROGRAMMATION

Identifier l'auteur
probable

Calcul des moindres Choix de l'auteur


carrés de chaque ayant la plus petite
auteur somme de ces
(double boucle) moindres carrés

FIGURE 6-9 Méthode descendante pour le problème de paternité (schéma du module 1.2)

Déterminer
l'auteur
du texte

Obtenir les fréquences


connues des mots discri- Identifier l'auteur
minants et les fréquences probable
du texte en litige

Calcul des moindres Choix de l'auteur


carrés de chaque ayant la plus petite
auteur somme de ces
(boucle double) moindres carrés

Lire les mots discriminants et les Trouver les fréquences


fréquences connues des mots des mots discriminants
discriminants de chaque auteur, du texte à l'examen et les
et les ranger dans trois vecteurs ranger dans un vecteur

Boucle sur les


Boucle de lecture mots du texte
Lecture du Lecture du Lecture Boucle de
des mots et des Boucle sur les
nombre de nom des du texte calcul des
fréquences de mots discriminants
mots auteurs Comptage des fréquences
chaque auteur
occurrences

FIGURE 6-10 Méthode descendante pour le problème de paternité (solution finale et complète).
L’ART DE PROGRAMMER 17

l..NB_MOTS de réels) et la fréquence des mots dans le texte (FREQ_TEXTE: vecteur l..NB_MOTS de réels).
Les variables NB_MOTS (entier), AUTEUR_1 et AUTEUR_2 (textes) contiennent respectivement le nombre
de mots discriminants et les noms des deux auteurs. On détermine l’auteur mystère du texte en litige en prenant
celui dont les fréquences sont les plus rapprochées (par la méthode des moindres carrés) des fréquences obser-
vées dans le texte. On suppose que le texte est contenu entièrement dans la variable TEXTE (texte).
1. [Lecture des données]
LIRE NB_MOTS) (nombre de mots discriminants)
LIRE (AUTEUR_1 , AUTEUR_2)
DÉFINIR MOT [1..NB_MOTS], FREQ_1 [1..NB_MOTS],
FREQ_2 [1..NB_MOTS], FREQ_TEXTE [1..NB_MOTS]
POUR i ← 1, 2, ..., NB_MOTS RÉPÉTER
LIRE (MOT [i], FREQ_1 (i), FREQ_2 [i])
LIRE (TEXTE)
2. [Recherche des fréquences des mots dans le texte]
FREQ_TEXTE ← 0
POUR i ← 1, 2, ..., NB_MOTS RÉPÉTER
INDICE ← POSITION (TEXTE, ' ' ºMOT[i]º ' ', 1)
TANT QUE INDICE ≠ 0
FREQ_TEXTE [i] ← FREQ_TEXTE [i] + 1
INDICE ← POSITION (TEXTE, ' ' ºMOT[i]º ' ', INDICE)
INDICE ← POSITION (TEXTE, ' ', 1)
NB_MOTS_TEXTE ← 0
TANT QUE INDICE ≠ 0
NB_MOTS_TEXTE ← NB_MOTS_TEXTE + 1
INDICE ← POSITION (TEXTE, ' ' , INDICE+1)
POUR i ← 1 , 2, ..., NB_MOTS RÉPÉTER
FREQ_TEXTE[i] ← FREQ_TEXTE [i] / NB_MOTS_TEXTE * 1000
3. [Calcul de la somme des moindres carrés de chaque auteur]
SOM_1 ← 0
SOM_2 ← 0
POUR i ← 1, 2, ..., NB_MOTS
SOM_1 ← SOM_1 + (FREQ_1 [i] – FREQ_TEXTE [i]) ↑ 2
SOM_2 ← SOM_2 + (FREQ_2 [i] – FREQ_TEXTE [i]) ↑ 2
4. [Impression du nom de l’auteur probable]
SI SOM_1 = SOM_2
ALORS ÉCRIRE ('Il y a egalite, on ne peut decider.')
SINON SI SOM_1 > SOM_2
ALORS ÉCRIRE (AUTEUR_ 2, 'est l''auteur probable')
SINON ÉCRIRE (AUTEUR_1, 'est l''auteur probable')
5. [Arrêt de l’exécution]
STOP

On remarquera l’emploi du troisième paramètre de la fonction POSITION pour chercher dans le texte, sans
avoir à le raccourcir à chaque trouvaille, ainsi que l’emploi du mot-clé DÉFINIR pour réserver l’espace mémoire
correspondant exactement aux besoins.
18 LOGIQUE DE PROGRAMMATION

Ceci termine la présentation de la méthode d’analyse descendante. Il est nécessaire de posséder comme
outil de travail une certaine habilité, avant de pouvoir l’utiliser à bon escient. N’hésitez pas à recommencer en
tout temps une portion qui mérite une amélioration, même si vous vous trouvez à la phase d’écriture dans un
langage de programmation. En pratique, on reprend plusieurs fois une analyse jusqu’à ce qu’on obtienne une
solution « convenable » et logique. Comme dit le proverbe : « Cent fois sur le métier, remettez votre ouvrage ».
Même si la méthode exige beaucoup de pratique avant d’être maîtrisée, il est prouvé que ses bienfaits surpassent
les efforts investis.

Suite à l’analyse du problème et à l’élaboration d’une solution, examinons maintenant quelques considé-
rations sur l’écriture des programmes.

6-5 ÉLÉMENTS DE STYLE

« ... nous commençons à voir une percée dans la programmation en tant que processus mental. Cette
percée est plus basée sur des considérations de style que sur des particularités. Elle implique que l’on
prenne le style au sérieux, non seulement pour l’aspect des programmes une fois finis, mais aussi dans le
processus mental de leur création. En programmation, il ne suffit pas d’être inventif et ingénieux. Il faut
savoir se discipliner et se contrôler afin de ne pas être étouffé par ses propres complexités. »

Harlan Mills, préface de


Proverbes de programmation,
par Henry F. Ledgard, DUNOD, 1978.

Ce chapitre insiste sur l’importance d’un bon style de programmation, non seulement pour respecter les stan-
dards de la profession, mais davantage dans le but de faciliter l’écriture d’excellents programmes. Le style est
une matière de goût, aussi bien en programmation que dans d’autres domaines. On ne cherche pas à imposer au
lecteur un style particulier, mais cette recherche d’un style n’est pas uniquement un caprice. Nous espérons
cependant que les recommandations suggérées encourageront le lecteur à adopter une attitude correcte dans
l’écriture de ses programmes. Dans les limites de ce livre et là où il sera possible de le faire, nous essaierons de
stimuler l’intérêt du lecteur en lui démontrant des exemples de bon style et de mauvais style.

Récemment, on a vu apparaître quelques livres dédiés exclusivement au style de programmation, entre


autres : « The Elements of Programming Style » (Kernighan et Plauger, 1974) et « Proverbes de Programma-
tion » (Ledgard, 1975). En plus de l’intérêt réel qu’ils peuvent susciter, ces livres présentent des suggestions
dont pourront bénéficier même un programmeur expérimenté. Cette section en présentera les principales idées.

La séparation de l’analyse du problème de la recherche d’une solution et de l’écriture du programme à


l’aide d’un langage de programmation est un des points importants de ce livre. On a présenté à la section
précédente de ce chapitre, une structuration de l’approche de la solution d’un problème; nous verrons mainte-
nant quelques considérations sur l’écriture matérielle d’un programme. On comprendra qu’il est difficile de
traiter de l’écriture d’un programme sans traiter du langage utilisé. La maîtrise de ce langage est indispensable
à l’écriture de bons programmes. Cependant, plusieurs remarques importantes sont indépendantes du langage
choisi.

On a insisté dans ce chapitre et en fait dans la majeure partie de ce livre sur l’importance de deux aspects
principaux de l’écriture de bons programmes; ce sont le découpage de problèmes complexes et la lisibilité
L’ART DE PROGRAMMER 19

(compréhensibilité) du produit final. Évidemment, ils ne sont pas indépendants; si l’on maîtrise bien la com-
plexité d’un problème, il est plus probable que le programme résultant sera plus lisible que celui pour lequel le
problème n’aura pas été suffisamment découpé. Inversement, un programme lisible est certainement plus com-
préhensible qu’un autre illisible et par conséquent, moins complexe. Quoique ces deux aspects puissent être
abordés différemment, ils se combinent efficacement dans la pratique.

Plusieurs personnes ont reconnu l’importance d’une gestion pratique de la complexité d’un problème.
Kernighan et Plauger (1976) ont écrit :

La gestion de la complexité d’un problème est l’essence même de la programmation. Nous serons toujours
limités par le nombre restreint de détails qu’on peut conserver clairement en mémoire. Ce que nous avons
essayé d’enseigner dans ce livre, est comment aborder la complexité d’un problème.

Dijstra (1972a) écrit également :

Nous devons reconnaître que... l’art de programmer est l’art de gérer la complexité d’un problème, sa
multiplicité et celui d’éviter autant que possible de tomber dans un chaos impénétrable.

L’importance de la lisibilité a été défendue avec autant de conviction. Alors que la fonction première d’un
programme est de communiquer avec l’ordinateur, il est également important que des collègues puissent facile-
ment le lire, car on consacre plus de temps à lire un programme qu’à l’écrire. La recherche d’erreurs ou la
modification d’un programme en nécessite la lecture et la compréhension avant toute action. La lisibilité d’un
programme est essentielle à sa compréhension; un programme qui ne peut être compris ne peut être ni entretenu
ni modifié et a par conséquent peu de valeur. Même l’auteur d’un programme illisible, en dépit du fait que le
programme lui semblait clair lors de son écriture, aura de la difficulté à se rappeler après un certain temps
(même court) ce que font certaines parties de ce programme et même pourquoi il l’a écrit comme cela. Kernighan
et Plauger (1976) ont dit à ce sujet :

Selon notre expérience, la lisibilité est le critère le plus simple d’évaluation de la qualité d’un programme;
si un programme est facile à lire, c’est probablement un bon programme, s’il est difficile à lire, c’est
probablement un mauvais programme.

Spencer, Tremblay et Sorenson (1977) mentionnent également :

Il est capital de distinguer entre lisibilité et habileté d’écriture. Il est important d’être capable d’écrire des
programmes facilement. Il est essentiel d’être capable de lire un programme facilement.

Dans cette section, nous continuerons d’élaborer sur ces sujets en traitant de la question du style de programma-
tion. Cette section se divise maintenant en trois sous-sections, soient respectivement : l’analyse d’un problème,
l’écriture d’un programme réel et la présentation du produit final. Certains points touchant l’analyse d’un pro-
blème ont été abordés à la section 6-4, mais nous étions plus intéressés à rechercher une solution qu’à nous
occuper de sa représentation sous forme de programme concret. Dans cette partie, nous nous attarderons sur ce
dernier point. Après ceci, nous verrons que même si l’écriture d’un programme est étroitement rattachée au
choix du langage de programmation, il y a certaines considérations qui sont supérieures à tout choix. Finale-
ment, nous nous pencherons sur des détails de présentation, c’est-à-dire, sur l’apparence visuelle du programme
(liste des instructions d’un programme). À chaque sous-section, nous vous présenterons un certain nombre de
suggestions (illustrées si possible) qui conduisent à l’amélioration de la qualité des programmes.
20 LOGIQUE DE PROGRAMMATION

6-5.1 ANALYSE DU PROBLÈME

En un sens, on peut faire ici les gains les plus substantiels en termes de qualité. La qualité ne s’ajoute pas a
posteriori. Beaucoup de caractéristiques souhaitables (comme la modularité) sont difficiles à ajouter à un pro-
gramme déjà écrit. Elles doivent essentiellement faire partie intégrante du programme et être incluses à l’écriture
originale. On peut changer en tout temps la présentation physique des instructions d’un programme, mais il est
beaucoup plus difficile d’en modifier la démarche.

Plusieurs spécialistes considèrent que la phase d’analyse est l’étape idéale pour s’occuper des erreurs de
programmation. Les programmes écrits sans erreurs (les erreurs possibles ayant été « pensées » et éliminées)
diminuent et même suppriment presque complètement la phase de mise au point. Cela libère non seulement le
programmeur, mais est aussi un facteur clé dans l’écriture de programmes fiables.

L’ingéniosité constitue la perte de beaucoup de programmeurs. Ils considèrent un programme comme un


puzzle et ils sont fiers d’écrire des programmes tortueux qui entrent en place parmi les autres morceaux du
puzzle. Une telle attitude de programmation se remarque par la présence excessive de trucs de programmation,
trucs qui prennent souvent avantage de certains détails obscurs du langage de programmation. De tels trucs
peuvent réduire le produit espace-temps d’un programme, mais au coût d’une diminution excessive de la clarté,
perte qu’on ne peut généralement pas s’offrir. En règle générale, on ne doit jamais sacrifier la clarté à l’effica-
cité. Il ne faut jamais réduire la clarté d’une logique efficace pour démontrer son ingéniosité.

L’un des avantages intéressants de la méthode descendante pour l’analyse d’un problème est l’obligation
d’identifier et de séparer les fonctions principales. Ce découpage peut avoir un impact important sur la facilité
avec laquelle on pourra par la suite modifier un programme, si le besoin s’en fait sentir. La séparation des
fonctions est fondée sur la prémisse suivante : l’influence d’un module sur l’ensemble du problème doit être
faible et ainsi, ses liens avec les autres modules peuvent être connus et compris facilement. L’analyse par la
méthode descendante tente de renforcer cette vision en contrôlant les types d’interactions permis entre les modules.
Voici deux exemples simples illustrant l’emploi et l’effet de la décomposition par fonctions.

Un premier exemple de la nécessité de la séparation fonctionnelle concerne le syndrôme du « nombre


magique ». Les nombres magiques sont des constantes numériques qui apparaissent mystérieusement dans un
calcul, une boucle ou une déclaration et généralement avec peu ou pas d’explications. Ils sont souvent utilisés
comme paramètre de boucle, pour définir la taille d’un vecteur ou d’un tableau, ou simplement comme cons-
tante dans une formule. Quoique leur apport semble ne représenter aucun danger, leur emploi excessif peut
sérieusement compromettre la flexibilité d’adaptation d’un programme.

Supposons, par exemple, qu’on veuille écrire un modeste système d’interrogation et de mises à jour des
informations de 37 étudiants d’une classe de sciences. Pour chaque étudiant, on conserve les informations
suivantes : nom, nombre d’exercices de labo complétés, note de labo à ce jour. On décide de ranger ces informa-
tions dans trois vecteurs parallèles, chacun de 37 éléments : NOM, un vecteur 1..37 de textes; NB_EXERCICES,
un vecteur 1..37 d’entiers et NOTE_LABO, un vecteur 1..37 de réels. On écrit des sous-algorithmes pour ajou-
ter la mention d’un nouvel exercice complété et sa note pour corriger des erreurs ou simplement pour interroger
le fichier. Chacune de ces opérations requiert un ou plusieurs examens de ces vecteurs, probablement à l’aide
d’une boucle comme :

POUR i ← 1, 2, ..., 37 RÉPÉTER


L’ART DE PROGRAMMER 21

Dans un tel programme, le nombre 37 est évidemment un nombre magique. En fait, l’exécution de ce
programme est limitée par l’emploi de ce nombre. Ce nombre apparaît sûrement à plusieurs endroits : déclara-
tions, boucles de recherche, boucles de calcul, boucles d’impression et sûrement une douzaine de fois. Il lie
ensemble intimement les diverses portions de la solution et cela n’est pas toujours tout à fait évident.

Si l’on désire utiliser ce même programme pour différentes classes, pour une classe de 212 étudiants par
exemple, on doit alors parcourir le programme et remplacer toutes les occurrences (pertinentes) du nombre
magique 37 pour le nombre magique 212. On doit toutes les trouver; celles qu’on ne trouve pas causeront
éventuellement une erreur d’exécution et pas nécessairement lors des premiers emplois. De plus, si on trouve
des 36 et des 38, ils doivent (peut-être) être changés pour des 211 et des 213 et ainsi de suite. L’emploi de
nombres magiques a évidemment compliqué indûment la « modifiabilité » du programme.

Ce cas particulier se corrige facilement; plutôt que d’utiliser un nombre magique pour représenter le nom-
bre d’étudiants de la classe, on déclare une variable, disons NB_ETUDIANTS et on l’utilise toutes les fois
qu’on se réfère à ce nombre. De plus, le nom choisi ajoute un peu plus à la documentation interne du pro-
gramme. À chaque emploi du programme avec un nombre différent d’étudiants, on ne doit que donner une
nouvelle valeur à NB_ETUDIANTS, soit par affectation (un seul endroit dans le programme avant recompilation)
ou par lecture (à l’exécution). C’est un exemple évident de la séparation des fonctions : une section Initialisation
ou Lecture s’occupe des détails des dimensions physiques alors qu’une ou plusieurs autre(s) sections s’occupent
des généralités du traitement (indépendamment de la taille particulière d’une classe).

La séparation fonctionnelle est souvent obtenue par l’usage de sous-programmes; des modules de traite-
ment sont écrits sous forme de sous-programmes qui sont appelés par des modules supérieurs lorsqu’on requiert
leur exécution. La séparation fonctionnelle est encore plus accentuée lorsque les données de travail et les résul-
tats sont échangés via la liste des paramètres plutôt que par celle des variables globales, mais il ne faut pas
oublier de minimiser les effets de bord. Dans l’exemple du système d’interrogation, supposons que chaque
opération sur le fichier est un sous-programme et que tous les échanges d’informations entre ceux-ci se font par
l’intermédiaire d’une liste de paramètres. Voici dans ce cas, le sous-algorithme d’interrogation :

Procédure RECHERCHE (NOM_ETUDIANT, NB_EXERCICES, NOTE_LABO, LIMITE)


Cette procédure recherche dans un fichier rangé en mémoire, en fait dans les vecteurs globaux (NOM_ETUDIANT,
LABO_ETUDIANT et NOTE_ETUDIANT) les informations d’un étudiant (NOM: texte). On retourne le nom-
bre d’exercices complétés (NB_EXERCICES : entier) et sa note courante (NOTE_LABO: réel). La variable
LIMITE (entier) donne le nombre d’étudiants dans le fichier.
1. [Initialisation]
i←0
2. [Recherche de l’étudiant (on suppose le nom unique)]
TANT QUE ETUDIANT [i] ≠ NOM ET i < LIMITE RÉPÉTER
i←i+1
3. [Recherche réussie?]
SI ETUDIANT[i] = NOM
ALORS NB_EXERCICES ← LABO_ETUDIANT [i]
NOTE_LABO ← NOTE_ETUDIANT [i]
SINON NB_EXERCICES ← –1
NOTE_LABO ← –1
22 LOGIQUE DE PROGRAMMATION

4. [Retour au programme appelant]


RETOUR
Un appel à cette procédure ressemble alors à ceci :
LIRE (NOM_RECHERCHE)
APPEL RECHERCHE (NOM_RECHERCHE, NB, NOTE, NB_ETUDIANTS)
SI NB = –1
ALORS ÉCRIRE ('L''etudiant', NOM_RECHERCHE, 'n''apparait pas au fichier')
SINON ÉCRIRE (NOM_RECHERCHE, 'a complete', NB, 'exercices et a accumule', NOTE, 'points')

La procédure RECHERCHE effectue une recherche linéaire. Elle peut s’avérer coûteuse lorsque le fichier
est de grande taille. Si le programme est utilisé fréquemment sur de tels fichiers, l’analyste préférera peut-être
modifier la stratégie de recherche et utiliser, par exemple, une recherche dichotomique. La séparation fonction-
nelle de la recherche des autres opérations permet de la modifier aisément, même si son emploi est intensif. Ce
changement de méthode n’affecte pas les modules ou sous-programmes où la recherche fait partie intégrante,
c’est-à-dire si la recherche se fait toujours via l’appel du sous-algorithme RECHERCHE, puisque c’est le seul
endroit où il y a effectivement modification. Un module qui appelle RECHERCHE pour n’en utiliser que les
valeurs résultantes n’a pas (si les paramètres d’appel ne changent pas) à se préoccuper de la façon dont il
fonctionne. Voilà un avantage certain de la séparation fonctionnelle.

Dans cette sous-section, nous avons insisté sur deux points au niveau de l’analyse du problème. Un pro-
gramme doit d’abord toujours être écrit de la façon la plus simple possible; il ne faut jamais sacrifier la clarté à
l’ingéniosité. Deuxièmement, la séparation fonctionnelle est un concept important à retenir lors de l’analyse. La
réunion de ces deux aspects conduit à une analyse claire, simple et dont les bienfaits seront appréciés tout au
long de la vie du programme.

6-5.2 ÉCRITURE D’UN PROGRAMME

Plusieurs programmeurs croient que l’écriture d’un programme au niveau d’un langage de programmation est la
phase la plus intéressante et la plus importante. Même si l’analyse et la solution du problème sont déjà connues,
l’écriture du programme ne s’effectue jamais aussi rapidement que prévue. La dernière décennie a vu naître une
nouvelle approche connue sous le nom de programmation structurée, et cette dernière est naïvement considérée
comme solution à tous les maux de la codification. Malheureusement, cela n’est pas le cas. Harlan Mills (1976)
fait remarquer dans un article sur la croissance de l’informatique, qu’« il y a beaucoup de surestimation et de
confusion au sujet de la programmation structurée, principalement parce que le milieu informatique encore
adolescent est anxieux de trouver des réponses simples à des questions complexes. »

La programmation structurée n’est rien de plus qu’une approche de l’écriture de programmes dans laquelle
rigueur et structure remplace l’usure des fonds de culotte. Les bons programmeurs travaillent de cette façon
depuis fort longtemps, avant même qu’un nom leur ait été donné. L’approche employée dans ce livre suit la
philosophie de la programmation structurée.

Une grande part de la « structure » d’un programme est déterminée par les structures de contrôle utilisées
dans la démarche de celle-ci. Il est important de se rappeler en lisant la liste des instructions de haut en bas, que
leur ordre d’exécution peut être très différente. Un des buts premiers de la programmation structurée est d’orga-
niser la démarche d’un programme de telle sorte qu’elle se conforme le plus possible à la séquence de lecture.
L’ART DE PROGRAMMER 23

Ceci impose au programmeur une grande discipline au niveau des structures de contrôle qu’il peut utiliser et
encore davantage au niveau de la façon dont il peut les utiliser. Dans un premier temps, nous nous limiterons à
deux structures introduites au chapitre 2, soient la structure alternative et la structure de boucle.

Si l’on s’en tient au pied de la lettre, tout programme écrit en utilisant uniquement ces structures de con-
trôle est, par définition, un programme structuré. Malheureusement, il est facile d’écrire des gribouillis à l’aide
de n’importe quelle méthode. Le plus important, c’est de s’en tenir à l’esprit de la lettre ou aux bonnes inten-
tions de la programmation structurée.

Revoyons l’algorithme MAX_MIN_3_VALEURS donné à la section 2-2, pour illustrer l’emploi de SI


emboîtés.

Algorithme MAX_MIN_3_VALEURS
Cet algorithme lit trois valeurs A, B et C, et en imprime la plus grande et la plus petite valeur. Les valeurs lues
sont distinctes.
1. [Lecture des données]
LIRE (A, B, C)
2. [Recherche de la plus grande et de la plus petite valeur par comparaisons deux à deux]
SI A < B
ALORS SI A < C
ALORS MIN ← A
SI B > C
ALORS MAX ← B (A <C <B)
SINON MAX ← C (A <B <C)
SINON MIN ← C (C <A <B)
MAX ← B
SINON SI A > C
ALORS MAX ← A
SI B > C
ALORS MIN ← C (A >B >C)
SINON MIN ← B (A >C >B)
SINON MAX ← C (C >A >B)
MIN ← B
3. [Impression des résultats]
ÉCRIRE ('La plus grande valeur est', MAX)
ÉCRIRE ('La plus petite valeur est', MIN)
4. [Arrêt de l’exécution]
STOP

Selon la stricte définition de la programmation structurée, il s’agit en fait d’un programme structuré. Sa
lisibilité peut cependant être améliorée, au moyen d’une perte minime d’efficacité, en supprimant quelques
emboîtements. L’esprit humain a généralement de la difficulté à comprendre les structures complexes et emboî-
tées; de telles structures obligent à retenir plusieurs conditions simultanément. Les structures profondément
emboîtées sont hautement sujettes aux erreurs et peuvent normalement être évitées ou supprimées.
24 LOGIQUE DE PROGRAMMATION

Il y a plusieurs façons d’éviter les structures trop emboîtées. Une première méthode, probablement la plus
appropriée dans ce cas, est d’utiliser des conditions composées dans l’expression conditionnelle du SI, afin de
bien préciser l’alternative. On doit cependant veiller à ce que la condition elle-même reste facilement compré-
hensible et surveiller les cas où on y gagne peu. Une deuxième méthode consiste simplement à répéter les
instructions; par exemple, le test peut être répété. Mais attention, car si pour éviter la répétition d’une petite
section d’instructions on la troque pour un truc, quelqu’un aura peut-être le trac lors de l’entretien!

La révision suivante de l’algorithme illustre l’emploi de cette deuxième méthode.

Algorithme MAX_MIN_3_VALEURS
Cet algorithme lit trois valeurs A, B et C, et en imprime la plus grande et la plus petite. Les valeurs lues sont
distinctes.
1. [Lecture des données]
LIRE (A, B, C)
2. [Recherche de la plus grande valeur]
MAX ← A
SI B > MAX
ALORS MAX ← B
SI C > MAX
ALORS MAX ← C
3. [Recherche de la plus petite valeur]
MIN ← A
SI B < MIN
ALORS MIN ← B
SI C < MIN
ALORS MIN ← C
4. [Impression des résultats]
ÉCRIRE ('La plus grande valeur est', MAX)
ÉCRIRE ('La plus petite valeur est', MIN)
5. [Arrêt de l’exécution]
STOP

Quoique cette version possède une étape supplémentaire et prend plus de temps d’exécution, elle est
moins longue en terme de lignes et surtout plus facile à comprendre que la première version. Ne laissons pas
l’apparence structurée d’un programme nous empêcher de le modifier pour l’améliorer.

Outre l’importance d’une programmation structurée, il existe d’autres points à surveiller lors de la phase
d’écriture d’un programme. Même si cela ressemble à une chasse au sorcières, on doit surtout se méfier des
détails. Wirth (1976) observe ainsi : « en programmation, le diable se cache dans les détails ».

Un certain nombre d’expériences (voir par exemple Weissman (1974)) ont démontré que le choix des
noms de variables joue un rôle significatif dans la compréhension d’un programme. On a déjà mentionné que la
liste d’un programme sert de document explicatif et qu’il se trouve au premier plan du programme. L’emploi de
noms de variables qui décrivent clairement leur utilisation dans le programme peut être plus utile pour rendre le
programme autodocumenté que la présence de commentaires. C’est parce que les variables sont parties intégrantes
L’ART DE PROGRAMMER 25

de la codification, alors que les commentaires n’en sont qu’un appendice. Trop de programmeurs oublient ce
point et écrivent des banalités du genre :

X←Y*Z

là où par un léger effort ils auraient dû écrire :

FORCE ← MASSE * ACCELERATION

La plupart des langages de programmation (sauf certaines versions de BASIC) permettent l’écriture de
noms significatifs. Toutes les fois que vous le pouvez, tirez-en avantage.

Un dernier mot concernant l’emploi des variables : quelques langages de programmation (tels FORTRAN,
PL/1 et BASIC) permettent la présence de variables n’ayant pas été explicitement déclarées dans le programme.
Ces compilateurs génèrent une déclaration implicite de ces variables lorsqu’ils les rencontrent pour la première
fois et leur donnent même des attributs par défaut (par exemple de type). En règle générale, il est dangereux de
se fier à ces déclarations par défaut. Soyez assuré qu’il est de bon ton et de bon style de toujours déclarer toutes
ses variables lorsqu’on code, peu importe le langage choisi.

6-5.3 PRÉSENTATION D’UN PROGRAMME

La forme et l’apparence de la liste d’un programme ne sont pas accessoires à la qualité du produit. C’est ici
qu’on peut faire le plus pour améliorer la lisibilité d’un programme. Dans cette sous-section, nous étudierons
deux aspects de cette question : les commentaires et les paragraphes.

Les commentaires constituent un autre point important de la documentation interne d’un programme. Ils
servent à éclairer le lecteur sur l’intention ou le but de portions de codification et aident à expliquer la logique de
sections plus difficiles. Les programmeurs apprentis reçoivent rarement des directives sur l’écriture de com-
mentaires et cependant, l’écriture de bons commentaires est probablement aussi importante et peut-être aussi
difficile à apprendre que l’écriture de bons programmes. De bons commentaires n’améliorent pas une mauvaise
codification, mais de mauvais commentaires peuvent sérieusement déprécier une bonne codification.

L’article de Sacks (1976) contient l’une des meilleures présentations du bon emploi de commentaires. On
verra également ceux de Kernighan et Plauger (1974), et de Ledgard (1975).

Plusieurs programmeurs se situent dans l’une des deux extrêmes : ils écrivent trop ou trop peu de commentaires.
Chacune de ces extrêmes diminue la lisibilité d’un programme à sa façon; une insuffisance de commentaires
n’apporte pas le support d’informations appropriées et la surabondance noie le code dans le désordre (le pro-
grammeur qui ajoute trop de commentaires pour éclairer une pauvre codification ne fait que s’enliser davan-
tage). Les commentaires ne doivent pas copier la codification mais plutôt la soutenir et l’expliquer. Cela signifie
que le programmeur aborde son programme simultanément de deux points de vue différents : celui du program-
meur et celui du documentaliste (ou plutôt d’un lecteur étranger), tout en essayant d’être complètement objectif
dans les deux cas.

Ce qu’on peut et ne peut pas faire avec des commentaires dépend du langage de programmation utilisé.
Malheureusement, certains langages qui en requièrent le plus, n’offrent que peu de flexibilité pour écrire de
bons commentaires. La plupart des langages permettent au programmeur d’utiliser une ligne complète en guise
26 LOGIQUE DE PROGRAMMATION

de commentaire et permettent donc d’écrire des commentaires sur plusieurs lignes qui se suivent; cela est idéal
pour expliquer le but ou les liens de portions d’un programme ou d’un module. Par exemple, tout sous-programme
devrait commencer par un certain commentaire pour expliquer ce qu’il fait, la façon dont se font les appels, pour
mentionner les modes de passage des paramètres, la description des paramètres et tout ce qui est digne d’intérêt
pour un collègue. Un tel commentaire peut fort bien ressembler à la description que nous donnons au début de
chacun des sous-algorithmes.

En plus de lignes complètes de commentaires, la plupart des langages permettent l’insertion d’un com-
mentaire sur la même ligne qu’une instruction, généralement après celle-ci (une exception, PASCAL permet
également l’insertion dans l’instruction; à ne pas faire). Ces brefs commentaires peuvent s’avérer très utiles
pour expliquer une instruction ou une opération délicate, sans perturber la présentation visuelle du programme.
De tels commentaires doivent être séparés autant que possible de la codification, en les plaçant à droite et
généralement à partir d’une même colonne.

Un dernier point : il faut toujours s’assurer que les commentaires et la codification coïncident. Si l’on
modifie la codification, on doit s’assurer qu’on modifie au besoin les commentaires qui lui sont rattachés; c’est
souvent oublié.

L’importance de faire des paragraphes et de contrôler les décalages des instructions les unes par rapport
aux autres, est également une question de rehausser la lisibilité d’un programme. Dans tous textes, les paragra-
phes ont deux buts : identifier les unités structurelles du texte et agrémenter la lecture en permettant une pause
de temps à autre. Cela s’applique également à l’écriture de programmes. Les paragraphes aident énormément à
mettre en valeur la structure logique des algorithmes et des programmes.

Dans ce livre, nous avons adopté plusieurs conventions, dont la règle stipulant que les branches d’une
alternative débutent sur des lignes différentes, mais en retrait du SI et dans les mêmes colonnes. De plus, s’il y
a plus d’une instruction pour une branche, elles sont enlignées dans la même colonne. Tout cela met nettement
les choix ainsi que leurs actions en évidence. La présence de structures emboîtées saute alors aux yeux.

En voici un exemple tiré du chapitre 3 :

SI POINT_1 > POINT_2


ALORS STAT [LIGNE_1, 2] ← STAT [LIGNE_1, 2] + 1
STAT [LIGNE_2, 3] ← STAT [LIGNE_2, 3] + 1
SINON SI POINT_2 > POINT_1
ALORS STAT [LIGNE_2, 2] ← STAT [LIGNE_2, 2] + 1
STAT [LIGNE_1, 3] ← STAT [LIGNE_1, 3] + 1
SINON STAT [LIGNE_2, 4] ← STAT [LIGNE_1, 4] + 1
STAT [LIGNE_2, 4] ← STAT [LIGNE_2, 4] + 1

Imaginons la difficulté de lire même cette petite portion de codification sans la présence de décalages. Le
décalage s’utilise également beaucoup pour montrer la portée d’une boucle. Toutes les instructions de la portée
(sauf l’énoncé de la boucle) sont décalées vers la droite; s’il y a emboîtement, on répète. En voici un exemple,
également tiré du chapitre 3 :

POUR ligne← 1, 2, ... 12 RÉPÉTER


SI EQUIPES [ligne] = EQUIPE_1
L’ART DE PROGRAMMER 27

ALORS LIGNE_1 ← ligne


SINON SI EQUIPES [ligne] = EQUIPE_2
ALORS LIGNE_2 ← ligne
STAT [LIGNE_1, 1] ← STAT [LIGNE_1, 1] + 1
STAT [LIGNE_2, 1] ← STAT [LIGNE_2, 1] + 1

La bonne présentation d’un programme cause rarement des erreurs et elle joue un rôle non négligeable
pour les éviter. Trop de programmeurs considèrent ce sujet superficiel et préfèrent consacrer leur énergie à ce
qu’il considèrent plus créatif; c’est bien sûr une erreur. La bonne présentation d’un programme constitue un
facteur clé pour sa lisibilité; elle en rehausse les qualités générales, le programme agréable à lire et lui donne une
apparence professionnelle.

6-5.4 POST-SCRIPTUM

Un pot à fleur en pièces est souvent plus facile à remplacer qu’à réparer.

Henry F. Ledgard,
Proverbes de Programmation
DUNOD Informatique, 1978.

Paradoxalement, et cela semble devenir une nouvelle loi de la nature, plus la qualité d’un programme est
évidente, plus il est susceptible d’être modifié à brève échéance. On a présentement tendance à prendre, en
informatique, les meilleurs programmes et à les adapter à ses besoins, plutôt que de se donner la peine d’en
développer de nouveaux. Il ne faut pas en conclure que cela est condamnable; au contraire, pour des raisons
temporelles et financières, on doit encourager les programmeurs, toutes les fois qu’il est possible, d’adopter et
d’adapter plutôt que d’essayer de réinventer la roue. Cependant, chaque modification ou rapiéçage d’un pro-
gramme incomplètement intégrés contribuent à en diminuer la cohérence. Un programme trop altéré devient
rapidement une structure fragile, un château de cartes qui s’effondrera, si on le touche au mauvais endroit.

Les programmes comme beaucoup d’autres objets ont une durée de vie qui est fonction d’éléments tels
l’évolution du matériel et l’évolution des besoins. Un programmeur d’expérience sait reconnaître le moment où
il faut remplacer un programme. Lorsque cela se produit, il ne faut pas hésiter à supprimer le vieux programme
et le remplacer par un nouveau; il ne s’agit pas d’une attitude défaitiste. Ledgard (1978) constate à ce propos
que : « les leçons péniblement apprises sur le vieux programme peuvent s’appliquer à l’écriture du nouveau et
produire un meilleur programme en moins de temps et avec moins de peine ». Plutôt que de laisser massacrer un
bon vieux programme, mettons-le à la retraite bien méritée.

6-6 LA PROGRAMMATION :
UNE ACTIVITÉ HUMAINE
Un programmeur qui se veut compétent, doit maîtriser plusieurs aspects du savoir-faire humain tant du point de
vue créatif (analyse et recherche de solutions), que du point de vue mécanique (codification des instructions).
Ces tâches nécessitent différentes aptitudes et le programmeur doit en être toutes dotées pour produire le pro-
gramme juste. Dans cette section, nous mettrons la technique de côté et examinerons l’effet de la condition
humaine sur les performances de l’activité du programmeur. Nous traiterons de plusieurs aspects et suggérerons
quelques recommandations pour remédier aux problèmes.
28 LOGIQUE DE PROGRAMMATION

Les programmeurs ne sont pas des machines (du moins pas encore). Ce sont des êtres humains qui effec-
tuent une tâche complexe et qui sont, pour cette raison, susceptibles de commettre des erreurs, car l’erreur est
humaine. Les limites dans la perception et la performance varient d’une personne à l’autre; il est donc important
que chaque programmeur reconnaisse et accepte de vivre avec ses propres limites.

Dans cette section, on discute des effets de certains aspects qui influencent le programmeur dans son
activité, principalement au niveau de la génération d’erreurs. On présente les causes de ces erreurs, leurs rami-
fications et les différents moyens pour les éviter ou les supprimer. Voici un classement grossier des causes
d’erreurs :

1. Complexité du traitement
2. Causes sociales
3. Environnement
4. Personnalité

Nos informations sont tirées principalement des deux articles de Cooke et de Bunt (1975a, 1975b) et des
livres de Weinberg (1971) et de Brooks (1975).

6-6.1 COMPLEXITÉ DU TRAITEMENT

Les erreurs découlant de la complexité du traitement résultent des limites inhérentes à la fiabilité des organes de
perception, de la mémoire et de la connaissance de l’être humain. La programmation exige le traitement d’une
grande quantité d’informations, telles les spécifications et les exigences du problème, les règles et les détails du
langage de programmation utilisé et les fonctions des sections de programmes déjà écrites. Le programmeur se
distingue ici par son habileté à percevoir visuellement les informations à traiter, à organiser les données brutes
(lettres, chiffres, espaces, caractères spéciaux, etc.) en informations significatives (nom de variables, mots-clé,
structures de contrôle, etc.) et à relier efficacement les diverses mémoires de l’ordinateur (mémoire centrale,
mémoires auxiliaires, mémoire cache, mémoires tampons... etc.) Sans entrer dans les détails, le programmeur en
tant qu’être humain est une piètre machine de traitement de l’information; il confond les symboles, oublie les
détails du langage, ne comprend pas le travail d’un sous-programme, etc. Il est la source fréquente et inaliénable
d’erreurs et c’est pour cela qu’on commence à lui porter une certaine attention (voir par exemple, les études de
Weissman (1973, 1974) et de Brooks (1973)).

Que peut-on faire pour réduire ces erreurs? On peut attaquer le problème sous plusieurs angles. Par exem-
ple, les concepteurs de langages de programmation utilisent les résultats de recherches sur la question pour
produire de nouveaux langages plus adaptés aux limites du traitement humain d’informations complexes (voir
par exemple, Gannon (1975)). Ceci est cependant hors du champ de ce livre et certainement hors du champ
financier de nombreux centres. Mais tout programmeur peut réaliser beaucoup dans son petit univers s’il prend
note des difficultés potentielles de l’activité humaine et les surveille lorsqu’il passe à l’action.

Par exemple, les problèmes de la vision humaine montrent que la disposition du code d’un programme est
très importante. Des recherches sur le mouvement des yeux montrent que le cerveau saisit l’information seule-
ment lorsque les yeux se fixent sur une portion de l’écriture. Le programmeur dirige le mouvement des yeux par
un usage judicieux de décalages et de lignes vides (pour indiquer les entités structurales du programme : bou-
cles, alternative, etc.). Une disposition étudiée aide, bien sûr, à la reconnaissance et à la compréhension de la
L’ART DE PROGRAMMER 29

structure générale. La vitesse et l’exactitude de la perception sont hautement influencées par la disposition du
code. Les erreurs augmentent avec la complexité, l’étrangeté et le désordre de la présentation. Tout cela renforce
premièrement, l’idée de l’espacement et du décalage dans la réduction des méandres de la démarche du pro-
gramme et deuxièmement, l’idée de l’inutilité de l’emploi de particularités non-usuelles de certains langages de
programmation (auxquelles l’œil reste accroché aux dépens de la compréhension).

La disposition non-judicieuse de commentaires embrouille également la lecture. Les commentaires sont


importants et sont là pour rehausser la compréhensibilité du programme. Par conséquent, ils ne doivent pas se
retrouver où ils risquent de gêner le mouvement des yeux. Il est donc préférable d’avoir un commentaire
multi-lignes en début de section, que d’avoir une ligne ici et là dans la liste de la section en question. Une autre
possibilité consiste à tirer avantage de l’espace qui reste généralement à droite des instructions, si le langage le
permet. Les commentaires sont alors hors de la portée des yeux lorsqu’on examine le code et on n’y prêtera
attention qu’au besoin.

Une étude sur la mémoire humaine montre qu’il existe une limite supérieure sur le nombre d’unités qu’elle
peut traiter simultanément et efficacement, une unité étant un élément simple ou un groupe d’éléments simples.
Par exemple, on peut considérer comme une unité, un opérateur, une instruction, ou même une boucle ou une
procédure. Cette recherche suggère clairement ce que devrait être la taille d’un module; celle mentionnée à la
section 6-4 à propos du découpage d’un problème. Pour qu’un module, disons une procédure, une boucle ou une
alternative, soit clairement et complètement compris, il faut limiter sa longueur en accord avec sa complexité.
Ainsi, l’emboîtement ne doit jamais dépasser les limites de la représentation mentale usuelle. En tant que pro-
grammeur, on essaiera de déterminer ses propres limites et de tenter de ne pas les dépasser.

Une autre conséquence des limites de la mémoire humaine est que le programmeur ne peut vraisemblable-
ment pas se rappeler tous les détails d’un programme complexe lorsqu’il travaille sur une de ses parties. On en
déduit deux choses : qu’on doit d’abord conserver à un strict minimum la dépendance entre chacune des parties
et le reste du programme (c’est-à-dire que les liens entre modules doivent être aussi simples et évidents que
possible) et qu’on doit ensuite conserver une description de l’objet de chaque module du programme, facile-
ment et rapidement accessible, soit sous forme d’un paragraphe commentaire en début de ces modules ou sous
forme d’une documentation externe. La méthode d’analyse descendante confirme ce fait en limitant le nombre
et la dispersion des liens entre modules. Dans la hiérarchie descendante, les modules d’un même niveau sont
indépendants (ils ont des œillères), toutes les références vers le bas contrôlent un module (faire faire quelque
chose) et toutes les références vers le haut retournent des résultats. Aussi, on se souviendra que le diagramme
hiérarchique de la méthode descendante est un outil documentaire externe important.

Dans son papier « The Humble Programmer », Dijstra (1972a) mentionne l’importance d’écrire des pro-
grammes sur lesquels le programmeur conserve « une emprise intellectuelle ». Ce commentaire porte bien sûr,
sur l’habileté du programmeur à traiter des problèmes complexes en tant qu’être humain.

Nous avons mentionné un certain nombre de facteurs dont la reconnaissance permet d’éviter certaines
possibilités d’erreurs. En règle générale, nous recommandons au programmeur de faire preuve de prévention
dans ses écrits, tout comme la conduite préventive qui est l’anticipation de ses erreurs et de celles des autres. Les
programmeurs les plus efficaces sont ceux qui anticipent les difficultés et agissent en conséquence.
30 LOGIQUE DE PROGRAMMATION

6-6.2 CAUSES SOCIALES

La pratique de la programmation a dépassé depuis longtemps le point où c’était entièrement une activité
recluse.

R. Conway et D. Gries,
An Introduction to Programming,
Winthrop Publishers, Inc., 1973.

Plusieurs aspects de la programmation nécessitent un échange avec différentes personnes (directement, ou indi-
rectement par le partage de programmes). Un programmeur travaille rarement sur une base individuelle lors-
qu’il est impliqué dans le développement d’un projet d’envergure. La coopération dans un effort de groupe peut
bénéficier à chacun ou peut être arrêtée par la compétitivité excessive ou l’ambition de ses membres. Il s’agit
d’un problème de personnalité sur lequel on reviendra. Les programmeurs qui travaillent en groupe doivent être
disposés à accepter les suggestions et les critiques constructives pour le plus grand bien du projet; les erreurs ne
doivent pas être interprétées comme un désaveux public, mais comme une mise en commun d’expériences et
d’apprentissages.

La structure d’un groupe de programmation doit être conçue dans le but de vaincre l’ego de tous. Les
programmeurs dans un groupe sont la plupart du temps en compétition les uns avec les autres pour des promo-
tions, des augmentations de salaire, etc. Cependant, tous bénéficieront du succès du projet. Brooks (1975) et
Weinberg (1971) ont beaucoup écrit sur les problèmes du travail en groupe. Weinberg a introduit le concept de
programmation « sans ego » (banalisée) et ce principe semble s’appliquer avec succès. Selon lui, les program-
mes ne sont pas perçus comme une projection de soi (contrairement aux œuvres artistiques comme les peintures
et les sculptures); ils peuvent donc bénéficier des suggestions et critiques des autres sans que l’auteur se sente
attaqué. L’essence de l’approche « sans ego » (banalisée) est que chaque programmeur d’un groupe reconnaît
ses limites et demande aux autres membres du groupe d’examiner ses programmes pour y déceler les erreurs et
les ambiguïtés. Au sein de groupes structurés et selon ce principe, des séances périodiques d’examens de code
deviennent routinières.

Une telle démarche s’avère très bénéfique. La phase de mise au point semble réduite. Les programmes
semblent mieux adaptés, puisque plus d’une personne ont dû être capables de les comprendre. L’horaire du
projet semble moins sujet aux absences d’un membre pour cause de maladie, de cours à suivre ou autre. Remar-
quons que si plusieurs personnes connaissent le projet et sa codification, il est plus facile de trouver par la suite
quelqu’un qui peut entretenir efficacement les programmes. Finalement, chaque programmeur ne peut qu’amé-
liorer sa codification en examinant les programmes des autres. Cela se traduit non seulement par une plus
grande satisfaction de son travail pour chaque membre, mais par un niveau général et supérieur de compétence
pour l’ensemble.

6-6.3 ENVIRONNEMENT

L’environnement de travail influence indéniablement l’efficacité d’un programmeur. L’environnement va de la


qualité du support technique de développement (au niveau du matériel et du logiciel) à l’ambiance physique de
travail. L’indisponibilité de langages de programmation adéquats et d’ordinateurs de puissance suffisante sont
des sources certaines de mécontentement, mais souvent hors de contrôle pour le programmeur. Beaucoup de
recherches sur les mérites de la programmation interactive comparés à la programmation par lot, sous l’aspect
L’ART DE PROGRAMMER 31

principal de l’efficacité ont été entreprises. Brooks (1975) rapporte que dans certaines applications, on peut
doubler la productivité du programmeur. Cependant, il est certain que cela ne peut se produire si l’installation
informatique ne dispose pas de langages et systèmes interactifs requis. Brooks conclut donc : « Je suis con-
vaincu que les systèmes interactifs ne viendront jamais remplacer les systèmes en lots pour certaines applications ».

Il existe d’autres outils pour aider à bien programmer. Par exemple, une bonne bibliothèque de program-
mes et de sous-programmes peut éviter de « réinventer la roue » inutilement. Certains systèmes sont également
disponibles pour aider : à la mise au point du code, à l’élaboration de tests, à l’examen des performances et à la
rédaction de la documentation des programmes. De tels systèmes, dont le Programmer’s Workbench développé
aux Laboratoires Bell (Dolotta et Mashey (1976)), s’adaptent particulièrement bien aux besoins des personnes
qui développent des programmes. Il en résulte une diminution des chances d’erreurs.

L’environnement physique qui donne les meilleurs résultats varie d’une personne à l’autre. Les uns préfè-
rent le silence absolu, les autres donnent un meilleur rendement lorsqu’il y a un bruit de fond. Beaucoup de
programmeurs sont, par choix, des oiseaux de nuit; ils se concentrent mieux dans les ténèbres (d’une part, ils ont
pratiquement l’ordinateur à eux seuls et d’autre part la date limite est souvent le lendemain). Certains aiment
voir des gens autour d’eux, d’autres préfèrent être isolés. La direction est sans contredit responsable de trouver
pour chacun un environnement adéquat qui maximisera la productivité du personnel (et minimisera les dépen-
ses). Elle doit également être prête à modifier certaines pratiques courantes pour s’adapter à des situations
spéciales. Weinberg (1971) et Brooks (1973) rapportent des anecdotes amusantes sur certains environnements
considérés adéquats.

6-6.4 PERSONNALITÉ
Plusieurs personnes, en dépit de leurs bonnes intentions, ne deviendront jamais des programmeurs efficaces.
Dans certains cas, cette inaptitude est moins dûe à des limitations d’ordre mental qu’à des traits de caractère de
l’individu. Des caractéristiques comme l’insouciance, le manque de motivation, le manque d’organisation, l’in-
capacité d’accepter ou de donner des directives, ou l’inaptitude à travailler sous stress, génèrent plus que leur
part d’erreurs. On a fait peu de recherche dans ce sens; il est donc difficile d’en donner une idée précise. Ce sont
quand même des aspects que les programmeurs doivent surveiller et que la direction garde ainsi en mémoire.

6-6.5 CONCLUSION
On termine cette section sur les facteurs humains, en se rappelant que reconnaître un problème constitue déjà un
grand pas vers sa solution. Comme individu, chaque programmeur doit reconnaître et apprendre à vivre avec ses
limites physiques et mentales. La direction a intérêt à apprendre à respecter les caractères individuels (ils sont
enrichissants) et à reconnaître les contraintes et les effets du travail de groupe. On a présentement peu de statis-
tiques dans ces domaines pour suggérer des techniques concrètes. On ne peut qu’espérer avec d’autres, qu’on
fasse des recherches plus approfondies dans un bref avenir.

6-7 RÉSUMÉ
L’informatique est en période d’introspection profonde. Le besoin d’améliorer la qualité des programmes est
réel et pressant. Il est relativement reconnu que la meilleure façon de le faire serait d’encourager les program-
meurs à avoir un sens plus aigu de l’importance du style de programmation.
32 LOGIQUE DE PROGRAMMATION

Nous avons tenté de présenter au cours de ce chapitre une vue d’ensemble des courants de pensée en
matière de programmation. Nous avons voulu donner certaines définitions utiles de la qualité d’un programme
et quelques moyens d’y parvenir. Nous espérons que la structure formelle d’analyse d’un problème que nous
avons présentée sera suivie, ainsi que les suggestions sur l’écriture de bons programmes. Finalement, nous
avons présenté quelques réflexions sur les aspects humains de la programmation.

À cause de l’espace réduit, nous n’avons que brièvement traité de ces sujets. Le lecteur soucieux d’appro-
fondir ces domaines est prié d’examiner la liste des références qui suivent. En plus des références citées dans le
texte de ce chapitre, nous recommandons la lecture d’une édition spéciale de « Computing Surveys » (ACM
(1974)) dédiée complètement aux différents aspects de la programmation, ainsi que les livres de Dijstra (1976)
et de Jackson (1975).

BIBLIOGRAPHIE
Associating for Computing Machinery : Computing Surveys, special issue on programming, ed. Peter J.
Denning, vol. 6, December 1974.
Brooks, Frederick P. Jr. : The Mythical Man-Month, Addison-Wesley, Reading, Mass., 1975.
Brooks, R. : « Cognitive Processes in Computer Programming, » Psychology Department, Carnegie-Mellon
University, 1973.
CIPS : Computer Magazine, vol. 4, April 1973.
Cooke, John E. and Bunt, Richard B. : « Human Error in Programming as a Result of Conventional Training
Methods, » Proc. IBM Scientific Symposium on Software Engineering Education, May 1975, pp. 63-69. (a)
, and Bunt, Richard B. : « Human Error in Programming : The Need to Study the Individual Programmer, »
INFOR, vol. 13, October 1975, p. 296. (b)
Dijkstra, Edsger W. : « Complexity Controlled by Hierarchical Ordering of Function and Variability, » in Software
Engineering, ed. P. Naur and B. Randell, NATO Scientific Affairs Division, 1968, p. 181.
: « The Humble Programmer, » Communications of the ACM, vol. 15, October 1972, p. 859. (a)
: « Notes on Structured Programming, » in Structured Programming, ed. Dahl, Dijkstra, Hoare, Academic,
New York, 1972, p. 1. (b)
: A Discipline of Programming, Prentice-Hall, Englewood Cliffs, N.J., 1976.
Dolotta, T.A., and Mashey, J.R. : « An Introduction to the Programmer’s Workbench, » Proc. Second Interna-
tional Conference on Software Engineering, October 1976, p. 164.
Gannon, John D. : « Language Design to Enhance Programming Reliability, » Technical Report CSRG-47,
Computer Systems Research Group, University of Toronto, 1975.
Jackson, Michael A. : Principles of Program Design, Academic, New York, 1975.
Kernighan, Brian W., and Plauger, P.J. : The Elements of Programming Style, McGraw-Hill, New York, 1974.
, and Plauger, P.J. : Software Tools, Addison-Wesley, Reading, Mass., 1976.
Ledgard, Henry F. : Programming Proverbs, Hayden, Rochelle Park, N.J., 1975.
L’ART DE PROGRAMMER 33

Lehman, M.M. and Parr, F.N. : « Program Evolution and Its Impact on Software Engineering, » Proc. Second
International Conference on Software Engineering, October 1976, p. 350.
Mills, Harlan : « Top Down Programming in Large Systems, » in Debugging Techniques in Large Systems,
ed. R. Rustin, Prentice-Hall, Englewood Cliffs, N.J., 1971, p. 41.
: « Software Development, » Transactions on Software Engineering, vol. SE-2, 1976, p. 265.
Mosteller, Frederick and Wallace, Davind, L. : « Interference in an Authorship Problem, Journal of the American
Statistical Association, vol. 53, 1963, p. 275.
Sachs, Jon : « Some Comments on Comments, » Systems Documentation Newsletter, vol. 3, December 1976.
Spencer, Henry A., Tremblay, Jean-Paul, and Sorenson, Paul G. : « Programming Language Design, » Department
of Computational Science, University of Saskatchewan, Saskatoon, 1977.
Weinberg, Gerald M. : The Psychology of Computer Programming, Van Nostrand Reinhold, New York, 1971.
Weissman, L. : « Psychological Complexity of Computer Programs : An Initial Experiment, » Technical Re-
port CSRG-26, Computer Systems Research Group, University of Toronto, 1973.
: « A Methodology for Studying the Psychological Complexity of Computer Programs, » Technical Re-
port CSRG-37, Computer Systems Research Group, University of Toronto, 1974.
Wirth, Niklaus : « Program Development by Stepwise Refinement, » Communications of the ACM, vol. 14,
April 1971, p. 221.
: « On the Composition of Well-Structured Programs », Computing Surveys, vol. 6, December 1974, p. 247.
: Algorithms + Data Structures = Programs, Prentice-Hall, Englewood Cliffs, N.J., 1976.
Yourdan, Edward : Techniques of Program Structure and Design, Prentice-Hall, Englewood Cliffs, N.J., 1975.
Zurcher, F.W. and Randell, B. : « Iterative Multi-level Modelling – A Methodology for Computer System
Design, » Proc. IFIP Congress, 1968, p. D138.

Vous aimerez peut-être aussi