ICS3U

Accueil > Programmer avec Java >

🛠️ Décomposition des problèmes

Survol et attentes

Même sans connaissances en programmation, vous êtes capable de décomposer des grands problèmes en plus petits problèmes. C’est une compétence essentielle pour la programmation et pour la résolution de problèmes en général. En particulier, à l’ère de l’intelligence artificielle, votre capacité à décrire les morceaux individuels d’un problème complexe vous permettra de tirer le plus grand profit des outils de rédaction de code, surtout si vous commencez par la rédaction des méthodes au bas de chaque chaîne d’appels.

Définitions
Modulaire
Un programme est dit modulaire quand il est divisé en parties indépendantes qui peuvent être réutilisées. Les méthodes sont les “modules” les plus simples dans un programme Java.
Décomposition / Approche descendante
Stratégie de résolution de problèmes qui consiste à diviser un problème complexe en plus petits problèmes plus faciles à résoudre. L’approche de partir du problème global et de le diviser en plus petits problèmes est appelée approche descendante. En programmation, on peut utiliser des commentaires de ligne pour créer des sections dans la méthode main pour chaque partie de la solution mais on utilise généralement une méthode séparée pour chacun de ces sous-problèmes.
Déclarer
Définir de façon plus ou moins formelle les éléments (étapes/méthodes) à inclure dans un plan pour la réalisation d’un projet, la structure d’un programme, etc. Une déclaration informelle est souvent juste un titre ou une courte description. Quand le plan est basé sur la décomposition, les éléments sont les différents “modules” (étapes / méthodes) de la solution complète.
Diagramme de dépendances
Diagramme indiquant la relation entre les étapes/modules/méthodes de la solution à un problème . Les méthodes sont des blocs nommés. Si une méthode a une flèche sortante vers une autre méthode, elle dépend de cette autre méthode. Si une méthode n’a aucune flèche sortante, elle est indépendante.
Chaîne d’appels
Une suite de flèches dans le diagramme de dépendances, du début (souvent la méthode main) jusqu’à la fin (un bloc qui n’a pas de flèche sortante).
Refactoriser
En programmation, ce terme veut dire restructurer le code pour le rendre plus lisible, plus efficace ou plus facile à maintenir. Séparer un morceau de code plus long et complexe en méthodes distinctes (le décomposer) après l’avoir écrit est un exemple de refactorisation qui rend le code plus facile à lire, à tester, à réutiliser et à modifier.

Objectifs d’apprentissage

À la fin de cette leçon vous devrez être en mesure de :

Critères de succès

Un programme modulaire

Au début d’une tâche de programmation, on peut penser à notre solution comme un seul programme :

programme monolithique

Par contre, on a souvent à faire au moins les trois étapes suivantes dans chaque programme qui interagit avec un utilisateur :

programme modulaire

Si on connaît les détails de la tâche, on peut probablement diviser l’étape “Actions” encore selon les différentes tâches à réaliser :

programme plus modulaire

Ce qu’on vient de faire est une décomposition du problème selon une approche descendante : on a pris un problème complexe et on l’a divisé en plus petits problèmes. Chaque petit problème est plus facile à résoudre que le problème global.

En programmation, on peut utiliser des méthodes pour résoudre chaque petit problème.

Un projet modulaire

On peut appliquer la même approche à la planification d’un projet dans n’importe quelle domaine.

Par exemple, un projet de recherche qui commence comme ceci :

projet

Peut être décomposé en étapes comme ceci :

projet modulaire

Voyant les étapes exposées comme ça, on peut mieux prévoir le temps nécessaire ou mieux s’organiser en équipe pour répartir les tâches.

Diagramme de dépendances

Certaines étapes ou modules d’un projet ne peuvent pas se faire sans que d’autres ne soient complétées. Un diagramme de dépendances illustre ces relations entre les modules.

On a déjà vu un diagramme de flux (qui montre la séquence des étapes) dans une leçon précédente. Un diagramme de dépendances est différent sur plusieurs points :

  • Il ne montre pas ce qui se passe dans les détails mais identifie seulement les “modules” ou les “étapes” du projet.
  • Il ne montre pas l’ordre d’exécution mais plutôt quelle étape dépend de quelle autre étape.

Le projet de recherche

Ici on compare les deux types de diagrammes pour le projet de recherche :

Diagramme de flux :

flux du projet

Diagramme de dépendances :

dépendances du projet

Avez-vous remarqué que la direction des flèches est inversée entre les deux diagrammes? C’est parce que le diagramme de dépendances commence avec le projet complet et regarde ce qui est nécessaire pour le rendre… réponse : que le projet soit rédigé. Et pour rédiger le projet, on a besoin de quoi?… réponse : les notes de recherche. Et pour faire les notes, on a besoin de quoi?… réponse : les sources d’information, etc. Ici les flèches indiquent les dépendances. Si on a une flèche de A vers B, ça veut dire que A dépend de B.

Dans le diagramme de flux, c’est l’opposé : on commence avec la première étape à réaliser. Ensuite, on regarde ce qui doit se passer après. Et après, et après, jusqu’à la fin du projet. Si on a une flèche de A vers B, ça veut dire que B est l’étape qui suit A.

C’est naturel que les flèches soient inversées entre les deux diagrammes. Si A dépend de B, alors A doit attendre que B soit fait. Cela nous donne :

  • flèche de dépendance de A vers B
  • flèche de séquence de B vers A

Le programme modulaire

Maintenant on fait le même exercice pour le programme modulaire :

Diagramme de flux :

flux du programme

Les modules se suivent dans un ordre logique.

Diagramme de dépendances :

dépendances du programme

Le programme dépend des modules Accueil, Actions et Au revoir qui sont indépendants entre eux (aucune flèche). Il y a aussi Actions qui dépend des modules Tâche 1, Tâche 2, … qui sont aussi montrés comme indépendants entre eux.

Ici la relation entre les deux types de diagrammes n’est pas aussi claire. Entre autres, on ne peut pas regarder le diagramme de flux et savoir, avec certitude, si une étape dépend de l’étape précédante ou non. De l’autre côté, il n’y a aucun lien direct entre les “modules” Accueil, Actions et Au revoir dans le diagramme de dépendances alors rien ne suggère une séquence sauf notre expérience qu’un accueil vient avant un au revoir.

En général, on produit chaque type de diagramme indépendamment en appliquant les concepts de chacun :

  • le diagramme de flux montre toujours la séquence d’exécution des étapes;
  • le diagramme de dépendances montre les dépendances entre les différents modules.

Chaînes d’appels

Dans les diagrammes de dépendances, les modules sont liées dans des chaînes de dépendance. Les modules en programmation sont souvent des méthodes. Dans ce contexte on parle de chaînes d’appels, parce qu’une méthode doit appeller les autres méthodes qu’elle a besoin d’utiliser.

Si une méthode à plusieurs dépendances, il y a des branches qui se forment dans le diagramme, créant autant de chaînes d’appels que de branches. Notre exemple de programme modulaire a donc ces cinq chaînes d’appels car la méthode Actions dépend de trois méthodes, triplant le nombre de chaînes passant par elle :

chaînes d'appels

Au début d’une chaîne d’appels (le bloc avec aucune flèche entrante), on a la méthode dépendante d’une autre. À la fin d’une chaîne d’appels, on a une méthode qui n’a pas de dépendances (aucune flèche sortante), qui est indépendante.

Si on veut profiter pleinement de la décomposition (plusieurs problèmes plus simples au lieu d’un grand problème), on ne peut pas commencer par écrire les instructions pour les méthodes dépendantes car il faudra aussi immédiatement écrire les instructions pour toutes les autres méthodes plus bas dans la chaîne d’appels (la première méthode dépend d’eux)… on n’a pas vraiment simplifié notre tâche! Par contre, si on commence avec une des méthodes indépendantes, on peut écrire le code juste pour cette méthode et la tester. On a juste un petit problème à résoudre. Ensuite, on peut soit choisir une autre méthode indépendante à faire ou bien la prochaine méthode plus haut dans la chaîne d’appels. Dans chaque cas, on reste avec juste un petit problème à résoudre dans l’immédiat. Mais peu à peu, tous les modules du projet se réalisent.

Refactoriser - quand on a déjà tout écrit

Parfois on n’a pas prévu découper notre programme en plus petites méthodes, mais la taille du programme ou sa complexité ou le besoin de réutiliser plusieurs fois le même code nous pousse à le faire. Réorganiser le code sans changer son comportement s’appelle refactoriser. Il n’y a pas de planification, juste un besoin pressant pour la décomposition, pour la création de modules.

La plupart du temps, surtout en commençant à programmer, c’est dur de savoir quand quelque chose sera trop complexe avant de l’avoir écrit, alors la refactorisation est souvent ce qui motive les premières expériences avec le code modulaire.

Exercices

🛠️ Pratique

Travaillez dans le répertoire GitHub partagé par votre enseignant pour la pratique et les exercices

Assurez-vous d’avoir installé l’extension “Draw.io Integration” pour VS Code pour éditer les fichiers .drawio.

  1. Pour la grande tâche de “préparer un repas” avec les modules “préparer la table”, “cuisiner”, “servir le mets principal”, “servir le dessert”, “remplir les breuvages” et “nettoyer”, dans votre dossier diagrammes :
    1. Créez un diagramme de dépendances pour ces modules nommé modules_repas-dependancy.drawio
    2. Créez un diagramme de flux pour ces modules nommé modules_repas-flux.drawio.