Sommaire
Avant‑propos
Malgré l’émergence de certains langages, les exercices de programmation n’ont guère évolué ces dernières décennies. Par exemple, un exercice de résolution d’(in)équation du 1er degré — un classique, les cours de programmation étant généralement assurés par des professeurs de mathématiques — ressemble à ça :
C’est assez austère, et l’ergonomie laisse à désirer, car il faut ressaisir systématiquement l’ensemble des paramètres, même si l’on ne veut en changer qu’un seul. Il est certes possible d’améliorer l’interface ou de gérer les paramètres en tant qu’arguments passés au programme lors de son invocation. Toutefois, cela devient rapidement trop compliqué à programmer par rapport à la finalité de l’exercice, et l’on ne pourra jamais obtenir le confort d’utilisation que confère une interface graphique.
Les interfaces graphiques sont justement le type d’interface auquel les jeunes sont habitués. Ce sont elles que l’on retrouve dans la majorité, voire la totalité des applications qu’ils utilisent sur leur smartphone. Ils n’ont probablement jamais eu affaire à une interface textuelle telle que celle mise en œuvre dans l’exemple ci‑dessus.
Supposons que l’on modifie l’exercice ci‑dessus pour lui donner la forme suivante :
Sous cette forme, l’exercice est bien plus attrayant et d’une bien meilleure ergonomie que sous la précédente forme.
Cette dépêche ne porte pas sur la pertinence de cette approche, dont vous trouverez un lien vers une description plus détaillée en fin de dépêche, mais sur la solution technique qui permet de la mettre en œuvre.
Cette solution s’appuie sur le toolkit Atlas, qui a l’avantage d’être simple à mettre en œuvre, facile à utiliser et léger (un lien vers le site dédié à ce toolkit, ainsi qu’un autre détaillant son API, sont disponibles en fin de dépêche). Cependant, bien que ce toolkit simplifie considérablement la manipulation de l’interface, cela reste trop compliqué pour des débutants en programmation.
C’est sur la couche logicielle qui permet l’écriture d’exercices adaptés au niveau de l’élève, notamment concernant la manipulation de l’interface, que cette dépêche va s’attarder. Pour cela, une série de fichiers, tirée d’un exercice mettant en œuvre cette couche logicielle, va être passée en revue. L’adresse du dépôt GitHub qui contient la totalité des fichiers constituant cet exercice est donnée en fin de dépêche.
Cette couche logicielle est disponible sous forme d’un paquet PyPi nommé edutk (voir en fin de dépêche). Néanmoins, il sera embarqué directement dans l’archive contenant l’exercice, et non pas installé via un « pip install …
», ceci dans le but de simplifier l’installation de l’exercice. Cela vaut également pour le paquet relatif au toolkit Atlas (atlastk).
Principales caractéristiques
Par rapport à sa forme classique, un exercice mettant en œuvre cette solution présente les particularités suivantes, dont les trois premières, gérées par le toolkit Atlas, ne seront pas détaillées dans cette dépêche, contrairement aux trois dernières.
Utilisation d’une interface graphique
Dans la plupart des cas, un exercice consistera en l’écriture d’un programme avec lequel l’utilisateur peut interagir. Ces interactions se feront alors via une interface graphique, plus précisément une interface Web. Lorsque l’exercice est lancé, un navigateur Web s’ouvrira automatiquement pour afficher l’interface de l’exercice.
Accès facile depuis Internet
Dès son lancement, l’exercice est automatiquement et instantanément accessible de n’importe quel dispositif (smartphone, tablette…) connecté à Internet et équipé d’un navigateur Web moderne. Cet accès est facilité par le fait de ne pas avoir :
- à déployer le code de l’exercice sur un serveur distant ;
- ni à configurer un routeur pour ouvrir et rediriger un port ;
- ni à configurer le dispositif pour le connecter au réseau local.
Il suffit, en effet, que l’ordinateur hébergeant l’exercice ainsi que le dispositif avec lequel on veut y accéder soient tous les deux connectés à Internet.
Multi‑utilisateur
Comme indiqué, l’exercice est une application Web, qui plus est facilement accessible de n’importe où sur Internet. De ce fait, il est possible que plusieurs personnes accèdent, simultanément, au même exercice. Cette situation ne pose pas de problèmes, les mécanismes nécessaires à sa gestion étant mis en œuvre de manière automatique et totalement transparente.
Gestion simplifiée de l’interface
L’exercice pourra être élaboré de manière à ce que les instructions nécessaires à la manipulation de l’interface soient adaptées au niveau de difficulté de l’exercice. Au besoin, ces instructions pourront être aussi simples à utiliser que les classiques input(…)
et print(…)
.
Internationalisation simplifiée
Bien que les identifiants des différents objets, classes, fonctions… que l’élève doit implémenter peuvent être adaptés à la langue de l’élève, l’accès à chacun de ces éléments, pour le créateur de l’exercice, se fera via un identifiant unique. Ainsi, le code constituant le cœur de l’exercice n’aura pas à être adapté à chacune des langues pour lesquelles l’exercice sera disponible.
Redirection des messages d’erreur
Comme l’application s’exécute dans un navigateur Web, l’élève n’aura pas forcément le réflexe de regarder la console à partir de laquelle l’application est lancée lorsque son code lève une exception. Il ne comprendra donc pas pourquoi son application ne fonctionne pas. C’est pourquoi, par défaut, les exceptions s’affichent dans une boîte de dialogue. Cependant, parce que cela est plus pratique durant l’élaboration de l’exercice, il sera possible de rétablir l’affichage des erreurs en console.
L’exercice côté enseignant
L’exercice proposé à titre d’exemple est une version un peu plus évoluée de l’habituelle Hello World! (probablement pas le premier exercice que l’on va soumettre à un débutant), qui ressemble à ceci :
Comme annoncé, chacun des fichiers constituant l’exercice va maintenant être passé en revue.
workshop/Body.html
<div style="display: table; margin: 50px auto auto auto;">
<fieldset>
<div style="display: table; margin: auto auto 10px auto;">
<input id="input"
placeholder="$NameToDisplay$"
data-xdh-onevent="Submit"
maxlength="15"/>
div>
<fieldset class="hidden"
style="border: 1px solid; padding: 10px;box-shadow: 5px 5px;"
id="output"/>
fieldset>
div>
Ce fichier contient l’interface HTML de l’exercice. Il n’y a rien de particulier, mis à part l’attribut data-xdh-onevent
, qui sera abordé plus tard, ainsi que le contenu de l’attribut placeholder
($NameToDisplay$
), avec la présence du symbole $
qui sera expliquée par la suite. On retrouvera la valeur des différents attributs id
dans d’autres fichiers.
On peut remarquer que l’on a à disposition toute la puissance du CSS. On peut ainsi facilement modifier l’apparence de l’interface en rajoutant des couleurs, des formes, des animations, etc. ; bref, tout ce qui est susceptible de renforcer l’aspect ludique de l’exercice.
workshop/Head.html
<style>
.hidden {
display: none;
}
style>
Le contenu de ce fichier est placé dans la section head
de l’interface HTML. Il contient généralement la définition des règles CSS utilisées dans le fichier workshop/Body.html
ou, plus généralement, utilisées par l’application.
workshop/core.py
Ce fichier contient les différents éléments nécessaires au fonctionnement de l’exercice et en constitue le cœur.
import edutk as _
from edutk import Core
On importe le paquet destiné à faciliter l’écriture de l’exercice, paquet à l’API duquel on accédera via l’identifiant _
. L’objet Core
, lui, est rendu directement accessible.
F_HELLO = "Hello"
_.defineUserItem(globals(), "uf", F_HELLO)
On déclare là une fonction que l’élève devra définir et par laquelle le concepteur de l’exercice accédera via la fonction d’identifiant ufHello
. Celle-ci n’appelle pas directement la fonction définie par l’élève, mais en retourne une référence. Grâce à cela, l’identifiant utilisé pour cette fonction dans le prototype exposé à l’élève sera différent de celui donné ici (ufHello
). On verra plus tard comment cela est mis en œuvre.
_.setEnums(globals(),"label",("MissingName", "NameToDisplay"))
Cela définit une classe label
avec deux membres MissingName
et NameToDisplay
avec, comme contenu, la chaîne de caractères contenant leur nom. Concrètement, la fonction ci‑dessus est équivalente à :
class label:
MissingName = "MissingName"
NameToDisplay = "NameToDisplay"
Cela sert à définir les libellés qui devront être traduits.
Notez le membre NameToDisplay
qui correspond à la valeur de l’attribut placeholder
, les $
en moins, présent dans le fichier workshop/Body.html
.
Les différentes chaînes de caractères définies ici vont, comme on le verra plus bas, être utilisées comme clefs d’un dictionnaire, et doivent donc être uniques. Le fait que ces chaînes de caractères reprennent le libellé du membre auquel elles sont affectées n’est qu’un moyen de garantir cette unicité, vu que chaque membre au sein d’une classe doit porter un identifiant unique. Le but est en fait d’obtenir l’équivalent d’un enum.
def clear():
_.dom().setLayout("output", "")
Cela définit une première fonction de l’API à utiliser par l’élève dans la réalisation de l’exercice. Cette fonction procède à l’effacement de la zone d’affichage, output
étant la valeur de l’attribut id
associé à cette zone dans le fichier workshop/Body.html
, en écrasant son contenu par la chaîne de caractères
. L’objet retourné par la fonction dom()
, ainsi que la méthode associée setLayout(…)
, sont fournis par l’API du toolkit Atlas, dont vous trouverez une description détaillée dans la documentation.
Notez que, comme vous le verrez par la suite, l’identifiant donné à la fonction (clear
) n’est pas l’identifiant par lequel l’élève accédera à cette fonction. Ce sera également le cas pour toutes les autres fonctions de l’API destinée à l’élève.
def display(text):
output = _.Atlas.createHTML()
output.putTagAndValue("h1", text)
_.dom().appendLayout("output", output)
Seconde fonction de l’API destinée à être utilisée dans l’exercice. En utilisant le toolkit Atlas, elle crée un arbre HTML constitué d’une balise h1
contenant le texte à afficher. Cet arbre est ensuite ajouté à la zone d’affichage (on retrouve le même identifiant output
que précédemment).
def _acConnect(c, dom):
dom.setLayout("", _.read(os.path.join("workshop", "Body.html"), c.body_i18n))
dom.focus("input")
Cette fonction sera appelée en interne à chaque lancement d’une nouvelle session utilisateur (on la retrouvera plus bas). Elle affiche le contenu du fichier workshop/Body.html
en remplaçant, grâce à la méthode _.read(…)
, le libellé $NameToDisplay$
(ainsi que toute chaîne de caractères délimitée par $
) par sa traduction contenue dans c.body_i18n
, que l’on verra par la suite. Les autres fonctions appartiennent à l’API du toolkit Atlas, dont la documentation vous fournira les détails.
Le paramètre c
est abordé plus loin, et le paramètre dom
est fourni par le toolkit Atlas. Ce sont ces deux mêmes paramètres qui sont passés à la fonction ci‑dessous.
def _acSubmit(c, dom):
input=dom.getContent("input").strip()
if (len(input)) != 0:
ufHello()(dom.getContent("input"))
dom.setContent( "input", "")
dom.removeClass("output", "hidden")
else:
dom.alert(c.i18n[label.MissingName])
dom.setContent("input", "")
dom.focus("input")
Cette fonction interne, que l’on retrouvera plus bas, sera appelée lorsque l’utilisateur valide sa saisie. C’est du Python pur jus accompagné d’appels à des méthodes du toolkit Atlas. On retrouve ici la fonction ufHello
définie plus haut. Elle permet d’appeler la fonction que l’élève doit définir dans le cadre de l’exercice. input
est la valeur de l’attribut id
du champ texte défini dans le fichier workshop/Body.html
.
Notez les deux paires de parenthèses utilisées lors de l’appel à ufHello
. La première sert à retourner une référence à la fonction définie par l’élève, la seconde à appeler cette fonction avec les paramètres adéquats.
L’objet label
est celui défini plus haut. Quant à c.i18n
, on le retrouvera plus bas.
def main(callback, globals, userFunctionLabels, title):
# Uncomment to display exceptions in terminal,
# instead of being displayed in an alert box.
#_.useRegularExceptions()
_.assignUserItems((F_HELLO,), globals, userFunctionLabels)
_.main( os.path.join("workshop", "Head.html"), callback, {
"": _acConnect,
"Submit": _acSubmit,
}, title
)
Cette fonction sert à lancer l’application qu’il y a derrière l’exercice.
callback
est la fonction à appeler pour créer un objet utilisateur. Cette fonction est appelée à chaque nouvelle session, et c’est la valeur retournée qui est passée en tant que paramètre c
aux fonctions _acConnect
et _acSubmit
ci‑dessus.
Le paramètre globals
contient l’objet retourné par la fonction globals()
lorsqu’elle est lancée par l’appelant.
userFunctionLabels
contient les liens entre les identifiants de fonctions tels que définis par le concepteur de l’exercice, et ceux, internationalisés, utilisés dans les prototypes exposés à l’élève. C’est grâce à cet objet que l’appel à la fonction ufHello()
va retourner la fonction définie par l’élève.
title
est la chaîne de caractères qui sera le titre de l’application ; son contenu est placé dans la balise title
de la section head
de la page HTML de l’application.
Durant l’élaboration de l’exercice, pour que les exceptions s’affichent dans la console, et non pas dans une boîte de dialogue, il suffira de décommenter l’appel à _.useRegularExceptions()
.
L’appel à _.assignUserItems(…)
permet d’indiquer les identifiants des fonctions, ou d’autres items (classes, variables…), que l’élève doit définir (ici, il n’y a que F_HELLO
). Ce seront ces identifiants qui seront utilisés dans les prototypes qui lui seront fournis.
_.main(…)
lance l’application. Le premier paramètre est le nom du fichier contenant les éléments à placer dans la section head
de la page HTML de l’exercice, en l’occurrence le fichier workshop/Head.html
, dont le contenu a été détaillé précédemment. Le troisième paramètre permet d’associer une action à une fonction. La chaîne vide correspond à l’action qui est appelée lors du lancement d’une nouvelle session et qui est ici associée à la fonction _acConnect(…)
précédemment définie. La chaîne Submit
correspond à l’action telle que définie par la valeur de l’attribut data-xdh-onevent
du fichier workshop/Body.html
, et est ici associée à la fonction _acSubmit(…)
précédemment définie.
workshop/fr.py
import workshop.core as _
Importe le fichier workshop/core.py
détaillé ci-dessus, dont l’API est rendu accessible via l’identifiant _
.
class _Core(_._.Core):
i18n = {
_.label.MissingName: "Veuillez saisir un prénom !"
}
body_i18n = {
_.label.NameToDisplay: "Prénom"
}
def __init__(self, dom):
_.Core.__init__(self, dom)
Une instance de l’objet _Core
va être créée lors de chaque nouvelle session. Le concepteur de l’exercice peut y placer toutes les données nécessaires au fonctionnement de cet exercice. Le membre i18n
contient les traductions des messages affichées par l’application et le membre body_i18n
, les traductions des chaînes de caractères délimitées par un $
présentes dans le fichier workshop/Body.html
.
efface = _.clear
affiche = _.display
Ces deux lignes créent des alias entre les identifiants des fonctions de l’API destinée à être utilisée par l’élève (définies dans workshop/core.py
) et les identifiants par lesquels l’élève appellera ces fonctions.
def go(globals):
_.main(lambda dom: _Core(dom), globals, {_.F_HELLO: "afficheSalutations"}, "Atelier de programmation")
Fonction appelée à la fin du code écrit par l’élève.
Le premier paramètre de l’appel à _.main(…)
est un callback qui sera appelé à chaque nouvelle session et qui devra fournir une instance d’un objet contenant les éléments propres à cette session. Ici, ce callback retourne une instance de la classe _Core
précédemment définie, dom
étant un objet créé et utilisé par le toolkit Atlas. globals
doit contenir le résultat de l’appel à globals()
à partir du code de l’élève. Le troisième paramètre est le titre de l’exercice.
workshop/en.py
import workshop.core as _
class _Core(_.Core):
i18n = {
_.label.MissingName: "Please enter a first name!"
}
body_i18n = {
_.label.NameToDisplay: "First name"
}
def __init__(self, dom):
_.Core.__init__(self, dom)
erase = _.clear
display = _.display
def go(globals):
_.main(lambda dom: _Core(dom), globals, {_.F_HELLO: "displayGreetings"}, "Programming workshop")
Il s’agit simplement de la version anglaise du fichier précédemment décrit.
L’exercice vu de l’élève
L’objet de tous les fichiers de la précédente section est de ne donner à manipuler par l’élève qu’un seul fichier, le plus concis possible. C’est pour cela que cette section contraste par sa brièveté avec la précédente.
Version française
Contenu du fichier fr.py
situé à la racine du dépôt.
from workshop.fr import *
Importation du fichier workshop/fr.py
(voir ci‑dessus) avec accès direct à son API.
def afficheSalutations(nom):
# début du code à saisir par l’élève
efface()
affiche("Bonjour, " + nom + " !")
affiche("Bonne journée, " + nom + " !")
# fin du code à saisir par l’élève
go(globals()
On voit que l’identifiant de la fonction à définir par l’élève est afficheSalutations
, comme déclaré dans le ficher workshop/fr.py
, et non pas ufHello
, qui est déclaré dans le fichier workshop/core.py
. De la même manière, c’est efface()
et affiche(…)
qui sont utilisés et non pas clear()
et erase(…)
.
Version anglaise
Contenu du fichier en.py
situé à la racine du dépôt.
from workshop.en import *
def displayGreetings(name):
erase()
display("Hello, " + name + "!")
display("Have a good day, " + name + "!")
go(globals())
Dans cette version anglaise, dans laquelle on importe workshop.en
à la place de workshop.fr
, c’est displayGreetings(…)
, erase()
et display(…)
qui sont respectivement utilisés à la place de afficheSalutations(…)
, efface()
et affiche(…)
.
Conclusion
edutk est considéré comme étant en version bêta de par son API. Cette API est, en effet, amenée à évoluer dans le but d’aboutir à un outil qui puisse être utilisé par les futurs titulaires du CAPES d’informatique, qui devrait voir le jour en 2020, pour créer leurs propres exercices. On peut aussi envisager de faire évoluer cet outil de manière à ce qu’il puisse être utilisé par les élèves des classes de terminale ayant choisi l’option NSI pour créer, à titre d’exercice, des exercices pour leurs petits camarades des classes de première et seconde.
En complément du contenu de cette dépêche, ceux qui désirent approfondir leur compréhension du fonctionnement et de la mise en œuvre de cet outil trouveront, en fin de dépêche, deux liens pointant sur des exemples d’exercices plus sophistiqués.
Toutes les personnes qui désirent utiliser cet outil, notamment celles qui officient actuellement dans les cours de SNT/NSI, sont, naturellement, les bienvenues. Conscient que beaucoup d’entre elles n’ont été que peu ou pas formées dans le domaine de la programmation, je me ferai un plaisir de les assister autant qu’il m’est possible dans leur utilisation de cet outil.
Merci à toutes celles et tous ceux qui, notamment en apportant des corrections ou en suggérant des modifications, ont contribué à l’amélioration du contenu de cette dépêche.