Enseigner Java par la Programmation Extrême
Voici le second article d’une série qui se penche sur la façon dont Java est enseigné aux nouveaux programmeurs. Dans le premier article, je me suis intéressé au célèbre premier programme toujours présenté, HelloWorld. Je défendais dans cet article que, bien que ce soit un bon moyen de vérifier que le système fonctionne, ce n’était pas un bon moyen de débuter en Java ou dans tout autre langage de programmation orienté objet. En dépit de la ressemblance avec la syntaxe du C, nous ne devons pas enseigner Java comme s’il s’agissait d’un C avec des objets. Il faut d’abord présenter les objets.
Votre réaction à l’article précédent a été formidable. De nombreuses personnes m’ont soumis des idées de méthodes ou d’approches qui étaient parfois très originales. Il y en a qui sont partisans de ne pas enseigner les objets dès le début (mais plutôt les pointeurs et l’allocation de la mémoire) et d’autres qui pensent que Java est un joujou agréable à enseigner mais mais n’est pas fait pour du développement “sérieux”. Les étudiants et les développeurs doivent étudier le fonctionnement de la mémoire et doivent apprendre différents langages et différentes approches. La Programmation Orientée Objet (POO) de Java est une bonne entrée en matière pour programmer. Cette série d’articles va donc continuer avec cette approche “extrême” de l’enseignement.
Parmi les réponses que j’ai reçues, certains pensaient que la POO n’était qu’une couche de plus sur la programmation procédurale, et que les appels de méthodes au fond n’étaient que des appels de fonctions déguisés. Dans un prochain article, nous examinerons de plus près le concept selon lequel “un objet est au moins d’un certain type”. Un appel d’une méthode polymorphique est un appel de fonctions : Vous le faîtes sans savoir quel objet voire même quel type d’objet va répondre. Au moment de la compilation, l’objet qui vous finirez par appeler lors de l’exécution n’existe peut-être pas encore !
Dans cet article, nous allons commencer par regarder un petit programme pour commencer à apprendre à programmer. En Programmation Extrême (Extreme Programming) vous avez une tâche que vous voulez accomplir. Au sein de cette tâche, vous choisissez un premier objectif tangible. Vous définissez comment vous allez vérifier que vous avez bien atteint ce but. Vous écrivez le test à passer et ensuite le code qui permettra de franchir avec succès ce test.
Dans cet article, j’utiliserai le framework ad hoc pour arriver à mes fins. Ensuite, j’utiliserai JUnit, mais je souhaite, pour commencer, séparer ce qui est fait des outils permettant de le faire. Vous pouvez vous joindre à la discussion à la fin de chaque article (NdT : sur le site US et en anglais) et soumettre de futurs sujets d’articles dans le forum ou en m’envoyant un mail à DSteinberg@core.com (NdT : en anglais aussi évidemment !).
HelloWorld: Votre premier programme Java
Pour schématiser, la programmation orientée objet est une histoire de communication entre objets à travers leurs interfaces visibles. Avec le temps, vous apprendrez à être pointu sur ce que vous exposez au monde extérieur. De façon idéale, vous exposerez très peu de la structure de vos objets et uniquement les comportements que vous permettrez aux autres objets d’invoquer.
Pour un premier programme, voici ce que je veux qu’un débutant apprenne à faire :
- Utiliser un éditeur et un compilateur pour créer des classes Java.
- Créer une classe Java qui pourrait être instanciée comme je vous l’indiquerai.
- Créer une méthode publique dans cette classe qui se conformerait à une interface.
- S’assurer que cette méthode retourne votre prénom dans un format que je peux utiliser.
Par exemple, regardez les quelques lignes de code ci-dessous:
Friend friend = new Friend();
String yourName = friend.getName();
// on dirait bien que je pourrai afficher votre nom (yourName) dès que je l'aurai
Votre première tâche c’est d’écrire le code qui va faire tourner ces deux lignes. Bien sûr, cela vous semble trivial. Vous allez vite écrire le fichier Friend.java, qui doit ressembler à ça :
public class Friend {
public String getName(){
return "Daniel"; //Si vous vous appelez Daniel!
}
}
Vous compilez et lancez le programme et vous vous attendez à être accueilli par votre nom. Une nouvelle fois, il vous paraît loin le temps où vous n’y connaissiez rien. Il y a pas mal de choses là-dedans. J’ai enseigné le calcul à des enfants pendant une douzaine d’années. Le dernier jour de chaque semestre, j’écrivai au tableau ce que nous avions étudié durant ce semestre et faisait un petit rappel. Là, sur deux tableaux noirs qui faisaient face à la pièce, se trouvait tout ce que nous avions couvert pendant quinze semaines. Sous cet angle, cela ne paraissait pas grand’chose, mais il nous avait fallu tout un semestre et un nombre incroyable de tableaux bien remplis pour en arriver là.
Enseignement extrême
Maintenant que vous vous remémorez le temps d’avant, peut-être vous rappelez-vous pour vous-mêmes, ou plutôt, vous rappelez-vous de quelqu’un que vous connaissiez qui passait son temps à taper de longs morceaux de programme, à les sauver et à essayer de les compiler. Peut-être plaçait-il des instructions pour imprimer des variables de contrôle de loin en loin ou mettait en commentaires les portions de code qui ne fonctionnaient pas plutôt que d’utiliser un débogueur “moderne”. Dans tous les cas, il passait un temps fou à rechercher ce qui n’était, la plupart du temps, que de simples coquilles.
En programmation extrême, vous avancez par tout petits pas. Le but c’est que le code se compile toujours et passe tous les tests avec succès. On peut parfois écrire quelques lignes de code et relancer la compilation et le processus de tests. Si quelque chose ne marche pas, alors on a une bonne idée de l’endroit où chercher l’erreur. Voici une explication en pseudo-code de la manière de procéder avec le code que nous avons commencé à écrire.
Utiliser le support de cours et JUnit
(NdT : Pour pouvoir exécuter les exemples de ce tutoriel, vous devez téléchargez le freeware JUnit qui fournit certaines des classes utilisées ici et un utilitaire pour automatiser les tests)
Avant toute chose, avant même d’écrire les tests, vous devez lancer le support de cours et vérifier qu’il fonctionne. Dans notre cours, nous utilserons un fichier exécutable .jar que vous lancerez en le double-cliquant. Quand il se lance vous devez voir ceci:

Test de l’existence de la classe Friend
Continuez et cliquez sur le bouton. Tant que l’étudiant n’aura pas créé un code qui fonctionne pour la classe Friend, vous devriez toujours voir quelque chose qui ressemble à ça :

Ecrivons un test pour être sûr que nous pouvons créer un objet de type Friend. Cela pourrait ressembler à ça:
public void testFriendExisits(){
Friend friend = new Friend();
checkExistenceOf(friend);
}
Ce test ne se compilera pas, parce que nous n’avons pas encore écrit la classe Friend. Créons-la cette classe Friend.
public class Friend(){
}
Le code du test se compile alors et franchit le processus. Notez que cet exercice a nécessité de maîtriser les points suivants :
- Utiliser un éditeur de texte pour créer le code de la classe
Friend. - Sauver ce code sous
Friend.javadans un répertoire connu par le chemin d’accès. - Compiler le code
Friend.javaavecjavacou tout autre compilateur Java. - Lancer le processus de test qui va contrôler le code avec JUnit.
Ce qui signifie que votre petit bout de code de deux lignes qui définit la classe Friend peut déjà servir à tester le système comme le fameux HelloWorld, sans vous obliger à aller beaucoup plus loin dans les détails. Vous avez saisi du code et avez reçu le retour qui vous prouve que votre code fonctionne correctement.
Test de la signature de la méthode getName()
Dès que la classe Friend existe au bon endroit, l’étudiant devrait voir apparaître ce message en retour:

L’étape suivante est d’écrire un test pour s’assurer que la classe Friend contient une méthode, getName(), qui renvoit une String qui va contenir le nom de l’utilisateur. Notre code pour effectuer ce test devrait avoir cette tête:
public void testFriendReturnsName(){
Friend friend = new Friend();
checkThisMethodReturnsAString(friend.getName());
}
Maintenant, vous pouvez voir tout l’intérêt de travailler étape par étape. Dès que le premier test a été franchi, nous savons que nous pouvons créer des objets de la classe Friend. Même si ce test peut paraître évident, nous l’utilisons dans l’ensemble de notre procédure. Si, pendant le développement, quelqu’un modifie le source et que ce test n’est plus franchi, nous saurons immédiatement ce qui ne fonctionne plus et saurons ce qu’il faut faire pour remédier à ce problème. Ces petits tests sont une garantie contre les futures modifications.
Dans le second test, nous créons un objet Friend, et invoquons sa méthode getName() et vérifions qu’elle renvoit bien une String. L’instanciation se fait en reprenant le code de la première étape. Quand nous regarderons le résultat via JUnit, nous utiliserons sa fonction qui permet de factoriser un même code en une seule méthode setUp(). Avec cette procédure systématique, le code d’un étudiant ne peut se compiler si la signature de la méthode getName() n’est pas correcte. C’est un peu limité pour un premier essai. Dans notre code de test, vous verrez que nous testons le nom et le type de retour séparément pour renvoyer une aide spécifique à chaque cas. Un code valide pour franchir ce test pourrait ressembler à ceci :
public class Friend {
public String getName(){
return "Any old String you want. " +
"It might be a name or it might be a meaningless sentence or two.";
}
}
Tester le contenu de la valeur de retour
Au point où nous en sommes, tout est syntaxiquement correct dans le code de l’étudiant. Vous avez dû remarquer que lorsqu’on le compile, ce code franchit le test avec succès. Nous écrirons un test supplémentaire sans rapport direct avec les autres mais qui permettra d’éclaircir un point complémentaire. Supposons que je sache que le nom de l’étudiante est Elena. Je peux vouloir tester si la valeur retournée par la méthode getName() correspond bien à “Elena”. Ce test pourrait ressembler à ça:
public void testFriendReturnsName(){
Friend friend = new Friend();
checkThisStringHasGivenValue(friend.getName(), "Elena");
}
Ce test se compile sans problème car la classe et la méthode existent et la méthode retourne bien une variable du bon type. Mais, le test ne sera pas validé tant que l’étudiante n’aura pas modifer la méthode getName() comme ceci:
public String getName(){
return "Elena";
}
Souvent, le but de ces tests unitaires est de tester la valeur de retour de différentes méthodes dans différentes situations. Notre exemple est un peu idiot et donc notre procédure de test ne fonctionne pas. Dès que la signature de la méthode getName() est correcte, notre processus affichera la valeur choisie par l’étudiant quelle qu’elle soit. On devrait avoir ce genre d’écran:

Résumé
Dans notre version revue et corrigée de HelloWorld les étudiants ont dû lire du code existant et fournir une réponse appropriée. Dès le début, le type de retour d’une méthode prend son sens car ils sont amenés à l’utiliser. Il leur est demandé de créer une classe à partir de rien et une méthode qui se conforme aux spécifications d’une tierce personne. Dès le départ, ils comprennent qu’ils n’auront peut-être à écrire qu’une partie d’application et qu’ils doivent être en mesure de comprendre, de se conformer, et plus tard, de contribuer à l’interface publique.
Le support de cours
Comme tous les profs, je suis convaincu que, pendant mes cours, j’apprends plus que mes étudiants. Pour construire cet exemple, il m’a fallu apprendre comment les classes sont chargées dans Java. La première version de ce support de cours était un fichier .jar double-cliquable que les étudiants devaient quitter et relancer à chacune des modifications qu’ils apportaient à la classe Friend. J’ai ajouté le bouton et ai compris que je n’avais pas bien saisi le fonctionnement des ClassLoaders. Je remercie Malcolm Davis et Bill Venners pour leur aide, presque tout ce que je sais sur la programmation Java est le résultat de converations à bâtons rompus avec Malcolm et Bill. J’ai même dit à mon éditeur que toute erreur de code était de leur faute, mais je crois qu’il n’est pas dupe. Allez faire un tour sur le site de Bill www.artima.com, pour avoir plus d’informations sur la Machine Virtuelle Java et sur le chargement des classes. Dans cette partie, je vais présenter le code du support de cours.
La fenêtre principale
Comme vous avez pu le constater sur les captures d’écran, l’application consiste en une fenêtre de type JFrame avec une zone de texte et un bouton. Voici le code la fenêtre JFrame.
import javax.swing.JFrame;
import java.awt.BorderLayout;
public class HelloFrame extends JFrame{
private HelloFrame(){
setUpLookOfFrame();
setUpFramesComponents();
setVisible(true);
}
private void setUpFramesComponents() {
MessagePane reporter = new MessagePane();
getContentPane().add(reporter,BorderLayout.CENTER);
getContentPane().add(new TestButton(reporter),BorderLayout.SOUTH);
}
private void setUpLookOfFrame() {
setTitle("Hello World");
getContentPane().setLayout(new BorderLayout());
setSize(350,330);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args){
new HelloFrame();
}
}
Dans setUpFramesComponents(), on crée la zone de texte et le bouton et on les ajoute à la fenêtre. Le reste du code ne fait que s’occuper de l’apparence de l’application.
Le Bouton
Le bouton ne fait pas grand’chose. Il attend tranquillement qu’on lui clique dessus. Quand on lui appuie dessus, il envoit à la zone de texte l’information nécessaire pour afficher le bon message.
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class TestButton extends JButton implements ActionListener {
private MessagePane reporter;
public TestButton(MessagePane reporter){
super("Press here to check progress");
addActionListener(this);
this.reporter = reporter;
setSelected(true);
}
public void actionPerformed(ActionEvent actionEvent){
reporter.testProgress();
}
}
La zone de texte
La zone de texte est un JEditorPane, pour permettre l’affichage de code HTML. La méthode testProgress() contient un appel à la classe personnalisée de chargement des classes. On essaie de créer un objet Friend puis d’invoquer la méthode getName(). Si le type de retour de getName() est bien String, alors on peut continuer et afficher le message de félicitations (congratulations). Il y a de nombreux cas d’erreurs possibles. Pour chacun, nous anticipons et affichons un message d’erreur adapté. Il est cependant possible qu’une erreur que nous n’avions pas anticipée intervienne. Nous affichons aussi un message approprié et gardons une journal de la pile (stack trace) pour que l’enseignant puisse comprendre ce qui a conduit à l’erreur.
import javax.swing.JEditorPane;
public class MessagePane extends JEditorPane{
public MessagePane(){
super("text/html","");
welcomeUser();
}
public void testProgress(){
try {
Class friend = (new FriendLoader()).getFriend();
String name = (String)friend.getMethod("getName",null).invoke
(friend.newInstance(),null);
if (name == null){
stringNotReturned();
} else greetFriend(name);
}
catch (NoSuchMethodException e) {
methodNotFound();
}
catch(ClassCastException e){
stringNotReturned();
}
catch (NullPointerException e){
classNotFound();
}
catch (Exception e){
e.printStackTrace();
unexpectedHappened();
}
}
private void welcomeUser(){ //...
}
private void greetFriend(String name){ //...
}
private void classNotFound(){ //...
}
private void methodNotFound(){ //...
}
private void stringNotReturned(){ //...
}
private void unexpectedHappened(){ //...
}
}
Vous pouvez vous arrêter là et ne pas créer le chargeur de classes. Remplacer juste l’appel au chargeur de classes par l’appel Class.forName(). Votre application serait plus petite, mais il faudrait la relancer à chaque fois qu’un étudiant veut vérifier son travail. Ca n’est pas grand’chose, mais vous en auriez vite marre de cliquer sur le bouton et vous ajouteriez un appel à testProgress() dans le constructeur de HelloFrame.
Le chargeur de classes
La seule méthode public de la classe FriendLoader est getFriend().
La méthode getFriend() appelle la méthode surchargée findClass() pour essayer de trouver et de charger la classe Friend. Le bonne méthode pour créer votre propre chargeur de classes est la surcharge de la méthode findClass(). Si vous trouvez le fichier de la classe, alors vous copiez le pseudo-code dans un tableau que vous passez avec le nom de la classe à la méthode defineClass(). Voici à quoi ça ressemble:
import java.io.*;
public class FriendLoader extends ClassLoader {
public Class getFriend() throws ClassNotFoundException {
return findClass("Friend");
}
protected Class findClass(String className) throws ClassNotFoundException {
byte classData[]= getFileData();
if (classData == null) {
throw new ClassNotFoundException();
}
return defineClass(className, classData,0,classData.length);
}
private byte[] getFileData() {
BufferedInputStream bufferedInputStream = getBufferedInputStream();
ByteArrayOutputStream byteArrayOutputStream =
getByteOutputFromFileInput(bufferedInputStream);
return byteArrayOutputStream.toByteArray();
}
private BufferedInputStream getBufferedInputStream(){
BufferedInputStream bufferedInputStream = null;
try {
File friend = new File("Friend.class");
FileInputStream fileInputStream =
new FileInputStream(friend.getPath());
bufferedInputStream = new BufferedInputStream(fileInputStream);
}
catch (FileNotFoundException e){
e.printStackTrace();
}
return bufferedInputStream;
}
private ByteArrayOutputStream getByteOutputFromFileInput
(BufferedInputStream bufferedInputStream){
ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream();
try {
int c = bufferedInputStream.read();
while(c != -1) {
byteArrayOutputStream.write(c);
c = bufferedInputStream.read();
}
}
catch (IOException e){
return null;
}
return byteArrayOutputStream;
}
}
Le défi
Comme prochaine étape, j’aimerais que les étudiants écrivent une interface. Dans ce cas, je me moque de la façon dont ils appelent leurs classes. En fait, je n’ai pas à m’occuper de la façon dont ils nomment l’information. Ceci impliquera des modifications dans le support de cours. Voilà un sujet pour une prochaine fois. Pour le moment, si votre but est d’enseigner la programmation orientée objet et que vous envisagez d’utiliser Java comme langage dans vos premiers cours, pensez à la façon dont vous pourriez vous servir du support proposé. Le mois prochain, nous continuerons sur ce sujet des tests dans une première expérience de programmation.

Textes originaux en anglais sur O’Reilly : Teaching Java the Extreme Way par Daniel H. Steinberg
Chargement
Commentaires récents