Vous êtes sur la page 1sur 33

L’ART DE PROGRAMMER

1

6

L’art de programmer 1

« 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.

2

LOGIQUE DE PROGRAMMATION

2 LOGIQUE DE PROGRAMMATION Dans tous les domaines et selon les circonstances, il y a des

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

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.

on ne sait jamais. Vérifier tous les cas

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).

FIGURE 6-1

Énoncé du problème Langage de Cas program- spéciaux mation Sous- Algorithme algorithmes Le
Énoncé du
problème
Langage de
Cas
program-
spéciaux
mation
Sous-
Algorithme
algorithmes
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

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.

d’instructions

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 nombre N Écrire
Écrire la liste des
carrés parfaits
de 1 à N
LIRE un
nombre N
Écrire les carrés
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
Écrire la liste des
carrés parfaits
de 1 à N
LIRE un
Écrire les carrés
nombre N
parfaits de 1
à N
Initialisation
d'un compteur
Boucle d'impression
TANT QUE on a un
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 nombre N Écrire
Écrire la liste des
carrés parfaits
de 1 à N
LIRE un
nombre N
Écrire les carrés
parfaits de 1 à N
Initialisation
Boucle d'impression
TANT QUE on a un
d'un compteur
carré parfait inférieur
ou égal à N
ÉCRIRE un
Incrémentation
carré parfait
du compteur

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

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.

CARRE_PARFAIT

1. [Lecture d’un nombre] LIRE (N)

2. [Initialisation]

k

1

3. [Boucle de recherche et d’impression des carrés parfaits] TANT QUE k2 N RÉPÉTER

4.
4.

[Portée de la boucle] ÉCRIRE (k2)

k

k + 1

5. [Arrêt de l’exécution]

STOP

(k ≠ 2) k k + 1 5. [Arrêt de l’exécution] STOP Cet exemple illustre bien

Cet exemple illustre bien la pratique de la méthode d’analyse descendante. La solution du problème est 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 :

et

(19,3 – 22) 2 pour Tremblay (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 discriminants et les fréquences tirées
Déterminer
l'auteur
du texte
Obtenir les fréquences
connues les mots
discriminants et les
fréquences tirées du texte
Identifier l'auteur
probable

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
Déterminer
l'auteur
du texte
Obtenir les
fréquences
connues des mots
Identifier l'auteur
discriminants et les fré-
quences du texte en litige
probable
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

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
Lire les mots discriminants et les
fréquences connues des mots
discriminants de chaque auteur, et
les ranger dans trois vecteurs
Lecture du nombre
de mots
Lecture du nom
des auteurs
Boucle de lecture
des mots et des
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
Trouver les fréquences des
mots discriminants du texte
à l'examen et les ranger
dans un vecteur
Lecture du texte
Boucle sur les mots du texte
Boucle sur les mots
discriminants
Comptage des occurrences
Boucle de calcul
des fréquences

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 fréquences des mots des deux auteurs (FREQ_1, FREQ_2: vecteurs

NB_MOTS de textes), les

16

LOGIQUE DE PROGRAMMATION

Identifier l'auteur probable Calcul des moindres carrés de chaque auteur (double boucle) Choix de l'auteur
Identifier l'auteur
probable
Calcul des moindres
carrés de chaque
auteur
(double boucle)
Choix de l'auteur
ayant la plus petite
somme de ces
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- minants et les fréquences
Déterminer
l'auteur
du texte
Obtenir les
fréquences
connues des mots discri-
minants et les fréquences
du texte en litige
Identifier l'auteur
probable
Calcul des moindres
carrés de chaque
auteur
(boucle double)
Choix de l'auteur
ayant la plus petite
somme de ces
moindres carrés
Lire les mots discriminants et les
Trouver les
fréquences
fréquences connues des mots
discriminants de chaque auteur,
et les ranger dans trois vecteurs
des mots discriminants
du texte à l'examen et les
ranger dans un vecteur
Lecture du
Lecture du
Boucle de lecture
des mots et des
fréquences de
chaque auteur
Lecture
Boucle sur les
mots du texte
Boucle sur les
mots discriminants
Comptage des
occurrences
Boucle de
nombre de
nom des
calcul des
du texte
mots
auteurs
fréquences

L’ART DE PROGRAMMER

17

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).

l

NB_MOTS

de réels) et la fréquence des mots dans le texte (FREQ_TEXTE: vecteur l

NB_MOTS

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

POUR i

1, 2,

0

, NB_MOTS RÉPÉTER

INDICE

TANT QUE INDICE π 0

POSITION (TEXTE, ' ' ºMOT[i]º ' ', 1)

0 POSITION (TEXTE, ' ' ºMOT[i]º ' ', 1) FREQ_TEXTE [i] + 1 FREQ_TEXTE [i] INDICE
0 POSITION (TEXTE, ' ' ºMOT[i]º ' ', 1) FREQ_TEXTE [i] + 1 FREQ_TEXTE [i] INDICE

FREQ_TEXTE [i] + 1

FREQ_TEXTE [i]

INDICE

POSITION (TEXTE, ' ' ºMOT[i]º ' ', INDICE)

(TEXTE, ' ' ºMOT[i]º ' ', INDICE) INDICE NB_MOTS_TEXTE TANT QUE INDICE π 0 0 POSITION
(TEXTE, ' ' ºMOT[i]º ' ', INDICE) INDICE NB_MOTS_TEXTE TANT QUE INDICE π 0 0 POSITION

INDICE

NB_MOTS_TEXTE

TANT QUE INDICE π 0

0

POSITION (TEXTE, ' ', 1)

TANT QUE INDICE π 0 0 POSITION (TEXTE, ' ', 1) NB_MOTS_TEXTE INDICE NB_MOTS_TEXTE + 1

NB_MOTS_TEXTE

INDICE

NB_MOTS_TEXTE + 1

POSITION (TEXTE, ' ' , INDICE+1)

NB_MOTS_TEXTE + 1 POSITION (TEXTE, ' ' , INDICE+1) , NB_MOTS RÉPÉTER POUR i 1 ,

, NB_MOTS RÉPÉTER

POUR i

1 , 2,

FREQ_TEXTE[i]

FREQ_TEXTE [i] / NB_MOTS_TEXTE * 1000

3. [Calcul de la somme des moindres carrés de chaque auteur]

SOM_1 SOM_2 POUR i SOM_1 SOM_2

0

0

1, 2,

,

NB_MOTS

SOM_1 + (FREQ_1 [i] – FREQ_TEXTE [i]) 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

probable') 5. [Arrêt de l’exécution] STOP On remarquera l’emploi du troisième paramètre de la

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

multiplicité et celui d’éviter autant que possible de tomber dans un chaos impénétrable.

l’art de programmer est l’art de gérer la complexité d’un problème, sa

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-

de textes; NB_EXERCICES,

un vecteur 1

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 :

de réels. On écrit des sous-algorithmes pour ajou-

tions dans trois vecteurs parallèles, chacun de 37 éléments : NOM, un vecteur 1

37

37

d’entiers et NOTE_LABO, un vecteur 1

37

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

QUE ETUDIANT [i] π NOM ET i < LIMITE RÉPÉTER i i + 1 3. [Recherche

i

i + 1

3. [Recherche réussie?] SI ETUDIANT[i] = NOM ALORS NB_EXERCICES NOTE_LABO SINON NB_EXERCICES

LABO_ETUDIANT [i] NOTE_ETUDIANT [i]

–1

NOTE_LABO

–1

22

LOGIQUE DE PROGRAMMATION

4. [Retour au programme appelant] RETOUR

Un appel à cette procédure ressemble alors à ceci :

Un appel à cette procédure ressemble alors à ceci : LIRE (NOM_RECHERCHE) APPEL RECHERCHE (NOM_RECHERCHE, NB,

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

C

(A <C <B)

SINON MAX

(A <B <C)

SINON MIN

C

(C <A <B)

MAX

B

SINON SI A > C ALORS MAX SI B > C

A

ALORS MIN

C

B

(A >B >C)

SINON MIN

(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

valeur est', MIN) 4. [Arrêt de l’exécution] STOP Selon la stricte définition de la programmation

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

Cet algorithme lit trois valeurs A, B et C, et en imprime la plus grande et la plus petite. Les valeurs lues sont distinctes.

MAX_MIN_3_VALEURS

1. [Lecture des données] LIRE (A, B, C)

2. [Recherche de la plus grande valeur]

MAX

SI B > MAX ALORS MAX SI C > MAX ALORS MAX

A

B

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

valeur est', MIN) 5. [Arrêt de l’exécution] STOP Quoique cette version possède une étape supplémentaire et

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_2, 3] SINON SI POINT_2 > POINT_1

STAT [LIGNE_1, 2] + 1 STAT [LIGNE_2, 3] + 1

ALORS

STAT [LIGNE_1, 3] SINON STAT [LIGNE_2, 4]

STAT [LIGNE_2, 4]

STAT [LIGNE_2, 2]

STAT [LIGNE_2, 2] + 1 STAT [LIGNE_1, 3] + 1 STAT [LIGNE_1, 4] + 1 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

SINON SI EQUIPES [ligne] = EQUIPE_2

ligne

ALORS LIGNE_2

ligne

STAT [LIGNE_1, 1] STAT [LIGNE_2, 1]

STAT [LIGNE_1, 1] + 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.