Article de reference

Programmation structurée

La programmation structurée est un paradigme de programmation caractérisé par un code source qui utilise une structure de code source basée sur des blocs pour encoder le flux de...

La programmation structurée est un paradigme de programmation caractérisé par un code source qui utilise une structure de code source basée sur des blocs pour encoder le flux de contrôle tel que la séquence, la sélection (c'est-à-dire si-alors-sinon et switch ) et l'itération (c'est-à-dire pour et tant que ).

À l'origine, l'objectif principal du mouvement de la programmation structurée était d'éliminer le besoin et l'utilisation de l' instruction ` goto` . Bien que `goto` offre un contrôle de flux puissant et flexible, il peut être utilisé pour écrire des algorithmes d'une complexité arbitraire, mais le code résultant présente souvent d'importants problèmes de qualité , communément appelé « code spaghetti » . La programmation structurée remplace `goto` par des constructions qui tendent à produire un code de meilleure qualité. Ce paradigme s'est popularisé et a, dans la plupart des cas, atteint son objectif de supplanter `goto`. De fait, son omniprésence est telle que, dans une grande partie du développement logiciel , il s'agit simplement de la façon d'écrire du code, et non plus d'un sujet de discussion comme auparavant.

La programmation structurée est parfois associée à la programmation modulaire, bien qu'elles soient différentes. De manière générale, « structuré » implique une certaine modularité et une conception visant à être efficace, facile à comprendre et à modifier, mais ce n'est pas le sens strict de la programmation structurée.

Après la popularisation de la programmation structurée, le style de programmation qui la précédait a été rétrospectivement qualifié de programmation non structurée . Bien qu'il s'agisse techniquement d'un paradigme de programmation, il diffère des autres paradigmes en ce qu'il n'a pas été conçu intentionnellement. Il représentait simplement l' état de l'art avant que la programmation structurée ne soit envisagée.

ALGOL 58 et ALGOL 60 , ce dernier incluant la prise en charge des structures de blocs.

Parmi les facteurs contribuant à sa popularité et à son acceptation généralisée, d'abord dans le milieu universitaire puis parmi les praticiens, figurent la publication de ce qui est maintenant connu sous le nom de théorème du programme structuré en 1966, et la publication de la lettre ouverte influente « Go To Statement Considered Harmful » en 1968 par l'informaticien néerlandais Edsger W. Dijkstra , qui a inventé le terme de programmation structurée .

Fondements théoriques

Le théorème du programme structuré fournit le fondement théorique de la programmation structurée. Il stipule que trois manières de combiner des programmes séquencement, sélection et itération suffisent à exprimer toute fonction calculable . Cette observation n'est pas propre au mouvement de la programmation structurée ; ces structures suffisent à décrire le cycle d'instructions d'un processeur , ainsi que le fonctionnement d'une machine de Turing . Par conséquent, un processeur exécute toujours un « programme structuré » en ce sens, même si les instructions qu'il lit en mémoire ne font pas partie d'un programme structuré. Cependant, les auteurs attribuent généralement ce résultat à un article de 1966 de Böhm et Jacopini, probablement parce que Dijkstra a lui-même cité cet article. Le théorème du programme structuré n'aborde pas la question de l'écriture et de l'analyse d'un programme utilement structuré. Ces questions ont été traitées à la fin des années 1960 et au début des années 1970, avec des contributions majeures de Dijkstra , Robert W. Floyd , Tony Hoare , Ole-Johan Dahl et David Gries .

Débat

PJ Plauger , un des premiers à avoir adopté la programmation structurée, a décrit sa réaction au théorème du programme structuré :

Nous, les convertis, avons brandi cette information intéressante sous le nez des programmeurs en langage assembleur irréductibles qui continuaient à présenter des bribes de logique complexes en disant : « Je parie que vous ne pouvez pas structurer ça. » Ni la preuve de Böhm et Jacopini, ni nos succès répétés dans l'écriture de code structuré ne les ont fait changer d'avis un jour plus tôt qu'ils n'étaient prêts à s'en convaincre eux-mêmes.

Donald Knuth acceptait le principe selon lequel les programmes doivent être écrits en tenant compte de la prouvabilité, mais il était en désaccord avec la suppression de l'instruction GOTO, et diagramme de flux d'un programme avec toutes les branches avant à gauche, toutes les branches arrière à droite, et aucune branche ne se croisant. Certains spécialistes des compilateurs et de la théorie des graphes ont préconisé de n'autoriser que les graphes de flux réductibles .Harlan Mills, chercheur chez IBM , a appliqué son interprétation de la théorie de la programmation structurée au développement d'un système d'indexation pour les archives du New York Times . Le projet fut un franc succès technique, et des responsables d'autres entreprises l'ont cité pour justifier l'adoption de la programmation structurée, bien que Dijkstra ait critiqué les divergences entre l'interprétation de Mills et les travaux publiés.

En 1987 encore, il était possible d'aborder la question de la programmation structurée dans une revue d'informatique. Frank Rubin l'a fait cette année-là avec une lettre ouverte intitulée « “GOTO” considéré comme nuisible ». De nombreuses objections ont suivi, notamment une réponse de Dijkstra qui critiquait vivement Rubin ainsi que les concessions faites par d'autres auteurs en lui répondant.

Résultat

À la fin du XXe siècle, la quasi-totalité des informaticiens étaient convaincus de l'utilité d'apprendre et d'appliquer les concepts de la programmation structurée. Les langages de programmation de haut niveau qui en étaient initialement dépourvus, tels que FORTRAN , COBOL et BASIC , en sont désormais dotés.

structures de contrôle

Conformément au théorème des programmes structurés , un programme est composé de trois structures de contrôle :

Séquence
Instructions ordonnées exécutées séquentiellement.
Bien que n'étant pas inclus dans le théorème de la programmation structurée, le concept de bloc est généralement présent dans les langages de programmation. Il permet de regrouper une séquence de code de manière à ce qu'elle se comporte comme une instruction unique. Un langage offre la possibilité de marquer une séquence d'instructions comme un bloc qui, sauf s'il contient des instructions de contrôle de flux, sera exécuté séquentiellement, de haut en bas. Par exemple, un bloc est délimité par des accolades {...}en C et dans d'autres langages à accolades , par des guillemets simples BEGIN...ENDen PL/I et Pascal , et par l'indentation en Python . Certains blocs utilisent une syntaxe différente selon leur structure. Par exemple, une instruction `if` est délimitée par des guillemets simples if...fien ALGOL 68 .
Sélection
Un bloc (qui peut être une seule instruction) est exécuté en fonction de l'état du programme. Cette construction est souvent exprimée à l'aide de mots-clés tels que while, repeat`else` for, `continue` ou ` do-untilbreak`. Bien que la programmation structurée limite le flux à un seul point d'entrée et un seul point de sortie, la plupart des langages autorisent plusieurs sorties anticipées.
Représentation graphique des trois modèles de base — séquence, sélection et itération — à l'aide de diagrammes NS (bleu) et d'organigrammes (vert).

Soutien linguistique

En général, un langage est conçu pour prendre en charge un ou plusieurs paradigmes de programmation. Cependant, même si un langage n'est pas spécifiquement conçu pour un paradigme particulier, il peut souvent être utilisé de cette manière. En théorie, tout langage peut servir à la programmation structurée. Parmi les langages initialement utilisés à cette fin, on peut citer ALGOL , Pascal , PL/I , Ada et RPL . Depuis, la plupart des langages de programmation procédurale récents intègrent des fonctionnalités favorisant la programmation structurée, et parfois même en omettent délibérément certaines l'instruction GOTO instruction de retour qui permet plusieurs points de sortie anticipée d'une fonction . Comme une fonction est un bloc, cette fonctionnalité est contraire au point de sortie unique décrit dans le théorème.

Sortie anticipée

De nombreux langages permettent de sortir prématurément d'un bloc (autrement que par l'instruction `return`). Par exemple, une boucle peut prendre en charge l'instruction `break` qui interrompt l'exécution avant la fin du bloc. Comme le décrit le théorème, cette logique de sortie anticipée peut être éliminée en ajoutant des branchements ou des tests, mais cela peut considérablement complexifier le langage. Le C est un exemple ancien et important de ces constructions. Certains langages plus récents proposent également des « sorties étiquetées », qui permettent de sortir de plusieurs boucles, et pas seulement de la boucle la plus interne.

La nécessité de plusieurs sorties peut survenir pour diverses raisons, le plus souvent soit parce que la fonction n'a plus de travail à effectuer (si elle renvoie une valeur, le calcul est terminé), soit parce qu'elle a rencontré des circonstances « exceptionnelles » qui l'empêchent de continuer, nécessitant ainsi une gestion des exceptions.

L'un des problèmes liés à la sortie anticipée est que les instructions de nettoyage peuvent ne pas être exécutées. Par exemple, la mémoire allouée peut ne pas être libérée, ou les fichiers ouverts peuvent ne pas être fermés, provoquant des fuites de mémoire et de ressources . Le nettoyage doit être effectué à chaque point de retour, ce qui est fragile et peut facilement engendrer des bogues. Par exemple, lors de développements ultérieurs, un développeur pourrait oublier une instruction `return`, et une action qui devrait être effectuée à la fin d'une fonction (par exemple, une instruction de traçage ) pourrait ne pas être exécutée dans certains cas. Les langages sans instruction `return`, tels que Pascal , Lisp et OCaml , ne présentent pas ce problème.

La plupart des langages modernes offrent une protection au niveau du langage pour prévenir ces fuites de mémoire (voir la gestion des ressources ). Alternative structurée à l'utilisation de `goto` et d'un bloc de nettoyage, la protection contre le déroulement de pile garantit l'exécution d'un certain code à la sortie d'un bloc ; elle est souvent implémentée conjointement à la gestion des exceptions, sous la forme d'un bloc `try-finally`. En C++, une autre approche consiste à initialiser l'acquisition des ressources , qui utilise le déroulement de pile normal (désallocation des variables) à la sortie d'une fonction pour appeler les destructeurs des variables locales et libérer les ressources.

Kent Beck , Martin Fowler et leurs co-auteurs ont soutenu dans leurs ouvrages sur la refactorisation que les instructions conditionnelles imbriquées peuvent être plus difficiles à comprendre qu'une structure plus simple utilisant plusieurs points de sortie prédites par des clauses de garde . Leur livre de 2009 affirme sans ambages : « Un seul point de sortie n'est pas une règle utile. La clarté est primordiale : si la méthode est plus claire avec un seul point de sortie, utilisez-en un ; sinon, n'en utilisez pas. » Ils proposent une solution pratique pour transformer une fonction composée uniquement d'instructions conditionnelles imbriquées en une séquence d'instructions de retour (ou de levée) prédite, suivie d'un bloc unique non prédite. Ce dernier est destiné à contenir le code pour le cas courant, tandis que les instructions prédites sont censées gérer les cas moins fréquents (ou les erreurs). Herb Sutter et Andrei Alexandrescu soutiennent également dans leur ouvrage de conseils C++ de 2004 que le point de sortie unique est une exigence obsolète.

Dans son manuel de 2004, David Watt écrit que « les flux de contrôle à entrée unique et sorties multiples sont souvent souhaitables ». S'appuyant sur la notion de séquenceur de Tennent , Watt décrit de manière uniforme les structures de contrôle présentes dans les langages de programmation contemporains et tente d'expliquer pourquoi certains types de séquenceurs sont préférables à d'autres dans le contexte des flux de contrôle à sorties multiples. Watt explique que les instructions goto non restreintes (séquenceurs de saut) sont à proscrire car la destination du saut n'est pas explicite pour le lecteur du programme tant que celui-ci n'a pas trouvé et examiné l'étiquette ou l'adresse réelle correspondant à la cible du saut. En revanche, Watt soutient que l'intention conceptuelle d'un séquenceur de retour est claire de par son contexte, sans qu'il soit nécessaire d'examiner sa destination. Watt précise qu'une classe de séquenceurs appelés séquenceurs d'échappement , définis comme un « séquenceur qui interrompt l'exécution d'une commande ou d'une procédure textuellement englobante », englobe à la fois les sorties de boucle (y compris les sorties à plusieurs niveaux) et les instructions de retour. Watt note également que, bien que les séquenceurs de saut (goto) aient été quelque peu restreints dans des langages comme le C, où la cible doit se trouver à l'intérieur du bloc local ou d'un bloc externe englobant, cette restriction seule ne suffit pas à rendre l'intention des goto en C explicite et ils peuvent donc encore produire du « code spaghetti ». Watt examine également en quoi les séquenceurs d'exceptions diffèrent des séquenceurs d'échappement et de saut ; ceci est expliqué dans la section suivante de cet article.

Contrairement à ce qui précède, Bertrand Meyer a écrit dans son manuel de 2009 que des instructions comme break et continue « ne sont que de vieux goto déguisés en agneaux » et a fortement déconseillé leur utilisation.

Gestion des exceptions

simpliste et artificiel .

David Watt analyse également la gestion des exceptions dans le cadre des séquenceurs (présentés dans la section précédente de cet article sur les sorties anticipées ). Watt note qu'une situation anormale (généralement illustrée par des dépassements de capacité arithmétiques ou des erreurs d'entrée/sortie comme l'erreur « fichier introuvable ») est un type d'erreur « détectée dans une unité de programme de bas niveau, mais pour laquelle un gestionnaire est plus naturellement placé dans une unité de programme de haut niveau ». Par exemple, un programme peut contenir plusieurs appels à la lecture de fichiers, mais l'action à entreprendre lorsqu'un fichier est introuvable dépend de la signification (de l'utilité) du fichier en question pour le programme ; par conséquent, une routine de gestion pour cette situation anormale ne peut pas être placée dans le code système de bas niveau. Watts souligne également que l'introduction de tests d'indicateurs d'état dans l'appelant, comme le feraient la programmation structurée à sortie unique ou même les séquenceurs de retour (à sorties multiples), conduit à une situation où « le code de l'application tend à être encombré de tests d'indicateurs d'état » et où « le programmeur peut, par oubli ou par négligence, omettre de tester un indicateur d'état. En fait, les situations anormales représentées par des indicateurs d'état sont ignorées par défaut ! » Il note qu'à l'inverse des tests d'indicateurs d'état, les exceptions ont un comportement par défaut opposé , provoquant l'arrêt du programme à moins que le programmeur ne gère explicitement l'exception d'une manière ou d'une autre, par exemple en ajoutant du code pour l'ignorer volontairement. Sur la base de ces arguments, Watts conclut que les séquenceurs de saut ou les séquenceurs d'échappement (abordés dans la section précédente) ne sont pas aussi appropriés qu'un séquenceur d'exceptions dédié avec la sémantique décrite ci-dessus.

L'ouvrage de Louden et Lambert souligne que la gestion des exceptions diffère des constructions de programmation structurée telles que les boucles `while`, car le transfert de contrôle « est initialisé à un point différent du programme par rapport à celui où le transfert a lieu. Au moment où le transfert se produit réellement, il peut n'y avoir aucune indication syntaxique que le contrôle sera effectivement transféré. » Arvind Kumar Bansal, professeur d'informatique, note également que dans les langages qui implémentent la gestion des exceptions, même les structures de contrôle comme `for`, qui possèdent la propriété de sortie unique en l'absence d'exceptions, ne la possèdent plus en présence d'exceptions, car une exception peut provoquer une sortie prématurée à n'importe quelle partie de la structure de contrôle ; par exemple, si init()une exception est levée dans ` for (init(); check(); increm())check()`, le point de sortie habituel après `check()` n'est pas atteint. Citant plusieurs études antérieures (1999-2004) et leurs propres résultats, Westley Weimer et George Necula ont écrit qu'un problème important avec les exceptions est qu'elles « créent des chemins d'exécution cachés, difficiles à appréhender pour les programmeurs. »

La nécessité de limiter le code à des points de sortie uniques apparaît dans certains environnements de programmation contemporains axés sur le calcul parallèle , tels qu'OpenMP . Les différentes constructions parallèles d'OpenMP, comme `parallel` parallel do, n'autorisent pas les sorties anticipées de l'intérieur vers l'extérieur de la construction parallèle ; cette restriction s'applique à tous les types de sorties, y compris `break` et les exceptions, mais toutes sont autorisées à l'intérieur de la construction parallèle si la cible du saut s'y trouve également.

Entrée multiple

Les fonctions permettent relativement rarement plusieurs entrées. Il s'agit le plus souvent d' une réentrée dans une coroutine (ou un générateur /semi-coroutine), où une fonction cède le contrôle (et éventuellement une valeur), mais peut ensuite être reprise là où elle s'était arrêtée. Ce type de programmation est couramment utilisé , notamment pour les flux (en particulier les entrées/sorties), les machines à états et la concurrence. Du point de vue de l'exécution du code, céder le contrôle depuis une coroutine est plus proche de la programmation structurée que le retour d'une fonction, car la fonction ne s'est pas réellement terminée et continuera son exécution lors d'un nouvel appel ; il ne s'agit pas d'une sortie anticipée. Cependant, les coroutines impliquent que plusieurs fonctions possèdent un état d'exécution – au lieu d'une seule pile d'appels – et introduisent ainsi une autre forme de complexité.

Il est rare que les fonctions permettent d'entrer à une position arbitraire dans la fonction, car dans ce cas l'état du programme (comme les valeurs des variables) n'est pas initialisé ou est ambigu, et cela est similaire à un goto.

Machines à états

Certains programmes, notamment les analyseurs syntaxiques et les protocoles de communication , comportent plusieurs états qui se succèdent d'une manière difficilement réductible à des structures de base, et certains programmeurs implémentent les changements d'état par un saut vers le nouvel état. Ce type de commutation d'état est fréquemment utilisé dans le noyau Linux.

Il est toutefois possible de structurer ces systèmes en faisant de chaque changement d'état une fonction distincte et en utilisant une variable pour indiquer l'état actif (voir trampoline ). On peut également les implémenter via des coroutines, ce qui dispense du trampoline.

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index