Blog ENI : Toute la veille numérique !
🎁 Jusqu'au 25/12 : 1 commande de contenus en ligne
= 1 chance de gagner un cadeau*. Cliquez ici
🎁 Jusqu'au 31/12, recevez notre
offre d'abonnement à la Bibliothèque Numérique. Cliquez ici
  1. Livres et vidéos
  2. Python
  3. Conception d'un programme
Extrait - Python Apprenez à développer des projets ludiques (3e édition)
Extraits du livre
Python Apprenez à développer des projets ludiques (3e édition)
2 avis
Revenir à la page d'achat du livre

Conception d'un programme

Présentation

Lorsque vous effectuez des exercices de programmation, des travaux pratiques ou des tutoriels, le travail à accomplir reste relativement cadré. Par exemple, lorsque le formateur vous demande de coder une fonction, il vous décrit précisément ses paramètres ainsi que le résultat attendu. Il vous reste à établir la logique et à mener les traitements correctement.

En revanche, en codant un projet personnel ou un projet scolaire, plus le projet avance, plus vous rencontrez de problèmes : des erreurs étranges apparaissent et qui plus est, par intermittence ; l’ajout de fonctionnalités devient difficile et relire le code s’avère ardu.

Pourquoi une personne capable de mettre en place 20 fonctions de 10 lignes chacune se retrouve-t-elle en difficulté sur un projet de 200 lignes ? Ces deux challenges semblent de prime abord similaires.

Pourtant, il n’en est rien. Il y a en effet une différence cruciale entre savoir faire des exercices courts et mener à bien un projet long : il s’agit de la capacité à concevoir un programme, à l’organiser et à le structurer. Cette compétence ne s’apprend pas toujours dans les cursus de formation, ceci généralement par manque de temps.

Ainsi, dans ce chapitre, pour vous permettre de réaliser des projets longs...

Principes de conception

1. Les trois grands principes : identifier, structurer, améliorer

Pour concevoir un projet informatique long, tout programmeur est amené à respecter les trois principes suivants :

  • Identifier les données et les actions/traitements.

  • Structurer les données et les actions/traitements.

  • Améliorer ses choix de conception.

Ces trois grands principes ne s’articulent pas autour des techniques de codage. En effet, réaliser un projet de plusieurs heures nécessite d’autres compétences que la programmation.

Les programmeurs doués rencontrent une difficulté car ils ont tendance à coder vite, sans prendre le temps de réfléchir à la conception, ce qui produit du code désorganisé. S’apercevant que leur conception est bancale, ces programmeurs vont reformuler les différentes parties de leur programme en espérant améliorer leur conception. Malheureusement, sans prendre davantage de recul, ils transforment leur conception bancale en une autre conception bancale, tout aussi peu satisfaisante.

Le secret pour concevoir un programme long est de prendre de la hauteur et du recul. Vous devez maintenant réfléchir en considérant les besoins et les objectifs demandés. Prendre ce temps de réflexion implique de s’éloigner momentanément du codage et ce n’est pas facile pour tout le monde.

Les trois principes proposés vont vous aider à réaliser la conception la plus adéquate possible ; nous les détaillons ci-dessous :

  • Identifier les données et les actions/traitements constitue le premier principe. Il devrait idéalement s’effectuer avant l’écriture du programme. En effet, lorsque l’on vous décrit un projet, comme PacMan par exemple, vous savez qu’il va falloir représenter en mémoire le décor ainsi que les PacGums. Il faudra aussi gérer les éléments du jeu comme PacMan, les quatre fantômes, les bonus et les super PacGums. Évidemment, il faudra aussi gérer les interactions entre tous ces éléments : PacMan ne doit pas traverser les murs, lorsqu’il passe sur une PacGum, celle-ci doit disparaître, comme les bonus par ailleurs. Les fantômes peuvent manger PacMan, sauf s’il a avalé récemment une super PacGum… Au final, en listant l’ensemble des données et des traitements, vous arrivez à identifier 90 % des besoins. Chacun de ces besoins étant inhérent au programme, ils se traduisent par la mise en place de variables et de fonctions.

Pourquoi lorsque nous cherchons à identifier les données et les actions/traitements du projet n’arrivons-nous à détecter que 90 % de nos besoins ? Les 10 % restants correspondent à des cas difficilement identifiables lors de la phase d’étude préliminaire. Ces besoins apparaîtront plus tard, généralement durant une phase de tests. Par exemple, dans le jeu PacMan, les concepteurs se sont rendu compte que les joueurs avaient tendance à cliquer trop tôt sur le joystick pour faire tourner PacMan. Ils ont donc dû ajouter...

Les variables

1. Identifier les variables

a. Règle : 1 notion = 1 variable

Lorsque vous listez les informations nécessaires pour effectuer un traitement, vous listez, sans vous en rendre compte, les variables dont vous avez besoin. Vous devez donc respecter la règle suivante :

Chaque notion doit être représentée par une variable

Prenons un exemple. Si vous devez créer un clone du jeu PacMan, il va falloir afficher les différents éléments du jeu à l’écran. Pour cela, il faut connaître la position de PacMan, des quatre fantômes et des bonus. Ainsi, pour avoir ces informations à disposition, il est nécessaire de créer des variables pour stocker les positions des éléments du jeu comme xPacMan et yPacMan…

Lorsque l’on affirme que chaque notion doit être représentée par une variable, cela sous-entend qu’il ne faut pas en choisir zéro ou plusieurs !

Dans le premier cas, l’absence de variable se produit lorsqu’un programmeur écrit un calcul à l’intérieur d’un autre traitement, comme dans l’exemple suivant :

if 0 < x + cos(theta) * y - 400 < 217 : 

Ce code est évidemment peu lisible car il est très difficile de déterminer la signification du test effectué. Par contre, en créant une variable Xecran (correspondant à une coordonnée écran issue du calcul x + cos(theta) * y - 400) et une variable LARG_AFF (correspondant à la largeur de la zone d’affichage), on obtient :

# coordonnée du héros à l'écran 
Xecran   = x + cos(theta) * y - 400 
 
# largeur de la zone d'affichage 
LARG_AFF = 217  
 
if 0 < Xecran < LARG_AFF :  # teste si héros visible à l'écran 

Le second cas où plusieurs variables existent pour décrire une même notion semble peu concevable. Pourtant, cette situation peut se produire, en voici un exemple. Pour gérer un ensemble d’éléments, un programmeur peut décider de créer une variable NbElem indiquant le nombre d’éléments dans l’ensemble et une autre variable EstVide indiquant si cet ensemble est vide. Techniquement, ce programme peut fonctionner avec ce choix de conception. Cependant, la variable EstVide est en redondance avec la variable NbElem. Ainsi, chaque fois que le nombre d’éléments change, la variable NbElem est modifiée. Mais il faut aussi penser à mettre à jour la variable EstVide, et le risque d’oublier cette variable secondaire existe. Ce choix de conception est dangereux, mieux vaut choisir une unique variable pour stocker la quantité d’éléments présents et recalculer à la volée le test permettant de déterminer si aucun élément n’est présent.

b. Identifier une variable par la présence de code répétitif

Vous avez sûrement déjà rencontré du code ressemblant à l’exemple suivant :

if Tab[k][j] == 1 :  
   ..  
elif Tab[k][j] == 0 or Tab[k][j] == 4 :  
   ...  
elif Tab[k][j]...

Les fonctions

1. Identifier une fonction

a. Prérequis

Si vous avez le choix entre mettre en place une fonction ou copier-coller du code existant pour l’adapter, la tentation du copier-coller sera forte. Voici pourquoi :

  • Écrire une fonction nécessite de connaître la syntaxe du langage. C’est souvent un point bloquant. Si c’est votre problème actuel, révisez le chapitre sur les fonctions.

  • Vous avez écrit un code fonctionnel et vous pensez que le copier-coller reste la meilleure option alors qu’écrire une fonction vous semble plus hasardeux et plus risqué. Si tel est le cas, révisez l’utilisation du mot-clé global et les règles de passage des arguments dans le chapitre sur les fonctions.

b. Analyser ou restructurer le code

Deux approches existent :

  • Vous essayez de structurer votre programme avant de l’écrire en faisant un brouillon sur papier. Cette approche a l’avantage de vous obliger à prendre un peu de temps pour réfléchir posément. Cependant, quelle que soit votre expérience en développement, vous n’arriverez à identifier que 50 % à 90 % de la structure du programme, ce qui reste incomplet finalement. Il faudra donc se lancer dans l’implémentation et découvrir les imprévus au fil de l’eau en faisant évoluer la conception du programme si besoin.

  • Vous préférez coder directement. Généralement, les débutants préfèrent coder pour se rassurer car ils souhaitent parvenir rapidement à un résultat pour savoir s’ils avancent dans la bonne direction. Ils produisent ainsi beaucoup de lignes de code et dans un deuxième temps ils restructurent leur code en créant des fonctions.

Quelle que soit l’approche choisie, étude sur papier ou codage direct, il faudra dans tous les cas que vous sachiez faire évoluer la structure de votre code. Nous allons donc nous mettre dans la situation où beaucoup de lignes de code ont été écrites et où il faut maintenant essayer de les structurer pour remodeler le programme convenablement.

c. La mauvaise approche

On peut écrire de nombreuses lignes de code les unes à la suite des autres sans appels de fonctions. Lorsque l’on veut structurer le code existant, le mauvais réflexe consiste à sélectionner, un peu au hasard, des portions de code pour les transformer en fonctions. Cette approche apporte plusieurs satisfactions à court terme :

  • L’ordre d’exécution des lignes de code étant préservé, cette nouvelle version effectue exactement les mêmes traitements et produit donc le même résultat que la version précédente.

  • Des fonctions ont été créées, ce qui était l’objectif fixé !

Voici un exemple avec un programme de cinq lignes :

Ligne1 
Ligne2 
Ligne3  
Ligne4 
Ligne5 

Le programme est restructuré en fonctions de la manière suivante :

images/06RI03NEW.png

En faisant ces choix à tâtons, il faut être conscient qu’une multitude de possibilités existent. Ainsi, on peut produire de très nombreuses configurations...

Structure hiérarchique d’un programme

1. La problématique

Lorsque nous programmons, nous nous concentrons trop souvent sur les micro-problèmes du moment : syntaxe, noms des variables, exactitude des tests logiques, mise en place des algorithmes… Bref, nous sommes trop souvent la tête dans le guidon et nous oublions de prendre du recul.

La plupart du temps, cela ne posera aucun problème car nous écrivons des programmes courts, de quelques dizaines de lignes. Néanmoins, lorsque l’on se lance dans l’écriture d’un mini-jeu durant un projet, l’aventure peut nous amener à écrire 200 à 500 lignes de code, voire plus. Si l’on ne consacre pas un minimum d’énergie pour structurer le programme, cela va être dangereux : le programme va dysfonctionner et les développeurs vont finir par ne plus savoir comment gérer les erreurs.

Les programmes longs, sans structuration, souffrent de plusieurs problèmes :

  • Le code gérant des détails se trouve au même niveau que du code gérant des aspects plus généraux du programme. Cela rend difficiles la lecture et la compréhension.

  • Différents traitements s’entremêlent. Ainsi, lorsque l’on cherche l’endroit dans le programme gérant un point spécifique, on trouve des lignes de code éparpillées un peu partout. Cela n’est pas bon car il devient très difficile d’identifier et d’isoler l’ensemble des traitements, ce qui conduit à une mauvaise compréhension du fonctionnement du programme.

  • Des bugs sont présents et leur recherche s’avère ardue. En effet, généralement, si vous avez un bug concernant le déplacement de PacMan, vous allez naturellement vous placer au début de la fonction DeplacePacman() et vérifier que les paramètres de cette fonction contiennent des valeurs correctes. Puis vous allez exécuter ligne à ligne les différents traitements pour détecter l’apparition d’une erreur. Sur trente lignes, la recherche est aisée. En revanche, si vous n’avez pas écrit une fonction spécifique pour cela, la première et la dernière ligne du traitement peuvent couvrir une région de 200 lignes de code comprenant d’autres traitements ainsi que des appels de fonctions. Dans ce contexte, la recherche s’avère beaucoup plus difficile.

  • Lorsqu’il y a un problème, on peut être tenté d’ajouter une ligne à un endroit précis, et après cela on constate que le problème a disparu. Cependant, en corrigeant un bug à un endroit, on crée parfois un autre bug un peu plus loin sans s’en rendre compte. Sans fonction, il n’y a pas de compartimentation du code. Ainsi, rien ne vous garantit qu’en retirant ou en modifiant une ligne de code à un endroit vous ne cassiez pas quelque chose quelques lignes plus loin. Les fonctions permettent d’isoler un groupe de lignes de code du reste du programme. Une erreur présente dans le code d’une fonction n’est pas censée se propager ailleurs, sauf bien sûr si l’on...

Cas pratique : le jeu du labyrinthe et des momies

1. Présentation

Nous présentons les étapes d’analyse d’un jeu de labyrinthe par un programmeur débutant que nous appellerons Tom. Nous allons découvrir ses choix de conception, ses analyses, les tests et les déceptions de notre nouvel ami. Le but de ce chapitre est de montrer qu’une réflexion sur la conception d’un programme est un point important et qu’elle se construit par phases successives d’essais et de recherche. Ce cas pratique permet ainsi de mettre en œuvre les principes introduits en début de chapitre.

a. Du point de vue du débutant

Si vous mettez en place un projet important en juxtaposant des morceaux de code et en espérant que cela se passe bien, malheureusement, cela ne va pas bien se passer. En effet, cette approche induit un manque d’organisation et une absence de structure dans votre projet. Ce dernier finira par ressembler à une pieuvre tentaculaire, les bugs que vous rencontrerez seront très difficiles à corriger et faire évoluer votre programme vous semblera une tâche insurmontable.

Tom va essayer de mettre en place des choix de conception, de découper son projet en parties indépendantes, de représenter les données de la manière la plus adéquate possible et de ne pas mélanger les différents niveaux de hiérarchie dans son programme. Tom aura beaucoup de bonnes idées, mais il s’apercevra que beaucoup de zones d’ombre restent présentes même en ayant déjà passé beaucoup de temps à repérer les pièges. Il devra donc repenser son modèle de conception, essayer de l’améliorer sans pour autant choisir une solution trop complexe pour son niveau.

À certains moments, Tom devra faire appel à son ami Benjamin pour des problèmes qui le dépassent. En effet, parfois, il est très difficile de trouver l’origine d’une erreur, même si le code semble simple et écrit correctement. Un avis externe lui sera nécessaire.

Ce chapitre original présente la construction d’un jeu plutôt simple mais relativement long, ceci par un programmeur débutant ou expérimenté. Beaucoup de débutants croient qu’il faut avoir la bonne intuition dès le premier instant. Dans le cas contraire, ils concluent que l’on n’est pas fait pour devenir un bon développeur. D’autres pensent qu’il faut s’attacher à tout prix à leur idée de départ même si celle-ci s’avère peu concluante. En bref, ces comportements sont trop extrêmes. En suivant la progression de Tom, vous allez comprendre qu’on ne peut pas connaître tous les pièges avant d’avoir commencé. Pourtant, dans beaucoup de formations IT, on vous explique qu’il faut rédiger un descriptif complet de l’application et la programmer ensuite, comme ferait un architecte pour une maison. Si vous êtes débutant ou si vous êtes expérimenté et que vous vous attaquez à un programme sortant de vos habitudes, il faut accepter l’idée que des étapes...