Programmation Objet (en Java)denoyer/Courses/2006-2007/LI314-eleves/uploa… · Les Exceptions En...

Preview:

Citation preview

Programmation Objet (en Java)

UPMC – LicenceInformatique - LI 314

© 2006-2007 Frédéric PeschanskiLaboratoire d'Informatique de Paris 6

email: Frederic.Peschanski@lip6.fr

Tutorat

Même combat : lundi 9h et 14hAutre horaire ? (venir me voir)

Projet

Page de FAQ mis à jourEclairsissement

Créer une petite histoire (10 pargraphesmax)Paragraphe:

ChoixEpreuve et Combat

Exemple

Cours Java : deuxième saison

➢ Cours 6 : Exceptions, tests unitaires et assertions

➢ Cours 7 : Interfaces graphiques en Swing

➢ Cours 8 : Collections et classes génériques

➢ Cours 9 : Design Patterns 1

➢ Cours 10 : Design Patterns 2

Exceptions, Tests Unitaires et Assertions

Introduction aux exceptionsTraitement des exceptions en JavaConception par contrat

PrérequisGaranties

Tests unitaires avec JunitClasses de testMéthodes de tests

Programmes robustes

Approches formelles : ex. Floyd/Hoare

Fiable, Long, Coûteux

Approches semi-formelles : ex. Contrat

Assez Fiable, Moins long et coûteux

Approches empiriques : ex. Junit

Dans ce cours

Gestion propre des erreurs en Java

Important: souvent mal fait

Conception par contrat « light »

Méthode de conception

Systématise la gestion des exceptions

Test unitaire avec Junit

Pour écrire proprement des tests

Typologie des erreurs

Erreurs de compilation : par le compilateur javac

Erreurs de syntaxe, pbm. de typage

Erreurs d'exécution : par la machine virtuelle java

Erreurs système : générée par l'environnement => RuntimeException

Erreurs contractuelles : mauvaise utilisation d'un objet => Exception

Erreurs logiques : un bug du programme !

Les Exceptions

En Java, toute erreur est une exception

Une exception = un objet d'une classe qui

hérite de java.lang.RuntimeException : exceptions système, non-vérifiées par le compilateur (pas de déclarations throws)

hérite de java.lang.Exception : exceptions vérifiées par le compilateur (nécessité des déclarations throws)

Règle (POBJ) :

on ne définit nous -même que des exception non-vérifiées, donc on héritera systématiquement de java.lang.Exception (ou d'une sous-classe)

Pourquoi les exceptions ?Erreurs à la construction

Un constructeur ne retourne rien, donc surement pas un code d'erreur

Séparation des préoccupationsD'un côté : code qui génère les erreurs => throwDe l'autre côté : code qui traite les erreurs => try ... catch

Syndrome du « segmentation fault »Messages d'erreurs« remontée » de la pile d'exécutionPossibilité de récupération (poursuivre malgré l'exception)

Traitement des exceptionsTraitement immédiat

On traite l'exception dès qu'on la détecte, c'est le cas le plus fréquent

Clauses multiplesPlusieurs types d'exceptions traitées au même endroit

Délégation / FiltrageOn délègue le traitement de l'exception, même si on la détecte

Clause finaleDu code exécuté quoi qu'il arrive (exception levée ou non)

Traitement immédiat

Public class MaClasse {....public void maMethode(...) {

try {// ici, code générant éventuellement// une exception de type MonException} catch(MonException e) {// traitement de l'exception}....

}}

Public class MaClasse {....public void maMethode(...) {

try {// ici, code générant éventuellement// une exception de type MonException} catch(MonException e) {// traitement de l'exception}....

}}

Les blocs try ... catch :

Exemple: utilisation du JDK (1/2)

FileReader

public FileReader(String fileName)throws FileNotFoundException

Creates a new FileReader, given the name of the file to read from.

Parameters:fileName - the name of the file to read from

Throws:FileNotFoundException - if the named file does not exist, is a directory rather than a regular file, or for some other reason cannot be opened for reading.

FileReader

public FileReader(String fileName)throws FileNotFoundException

Creates a new FileReader, given the name of the file to read from.

Parameters:fileName - the name of the file to read from

Throws:FileNotFoundException - if the named file does not exist, is a directory rather than a regular file, or for some other reason cannot be opened for reading.

Dans la doc. du jdk:

Important: si on veut utiliser une méthode qui throws une exception, le programme ne compilera pas si on indique comment traiter cette exception

Exemple: utilisation du JDK (2/2)Mode d'emploi:

Public class MaClasse {....public void maMethode(...) {

try {// ici, code générant éventuellement// une exceptionFileReader fr = new FileReader("toto.txt");// et la suite ...

} catch(FileNotFoundException e) {// traitement de l'exceptionSystem.err.println("Je ne trouve pas toto.txt");

}....

}}

Public class MaClasse {....public void maMethode(...) {

try {// ici, code générant éventuellement// une exceptionFileReader fr = new FileReader("toto.txt");// et la suite ...

} catch(FileNotFoundException e) {// traitement de l'exceptionSystem.err.println("Je ne trouve pas toto.txt");

}....

}}

Clauses multiples : pourquoi ?

ProblèmesUn même instruction peut générer plusieurs types d'exception différents

Exemple: constructeur de java.io.FileInputStream throws FileNotFoundException et SecurityException

On veut mettre plusieurs instructions dans le corps d'un try ... catch, chaque instruction peut lever plusieurs types d'instructions

Exemple: première instruction : constructeur FileReader peut lever FileNotFoundExceptionSecond instruction: lecture dans le fichier avec méthode read de FileReader, peut lever IOException

Clauses multiples : comment ?public class MaClasse {

....public void maMethode(...) {

try {// ici, code générant éventuellement// une exceptionFileReader fr = new FileReader("toto.txt");int carac = fr.read();// et la suite ...

} catch(FileNotFoundException e) {// traitement de l'exceptionSystem.err.println("Je ne trouve pas toto.txt");

} catch(IOException e) {// traitement de l'exceptionSystem.err.println("Problème de lecture");

}....

}}

public class MaClasse {....public void maMethode(...) {

try {// ici, code générant éventuellement// une exceptionFileReader fr = new FileReader("toto.txt");int carac = fr.read();// et la suite ...

} catch(FileNotFoundException e) {// traitement de l'exceptionSystem.err.println("Je ne trouve pas toto.txt");

} catch(IOException e) {// traitement de l'exceptionSystem.err.println("Problème de lecture");

}....

}}

Clauses multiples et héritageLes exceptions sont des objets, ils sont donc instances de classes et on peut donc hériter de classes d'exceptions

L'ordre dans lequel on liste les exceptions « catch » est important

D'abord les sous-classes d'exceptions les moins génériques

Ensuite les super-classes d'exceptions les plus génériques

Raison: c'est simple, si on traite d'abord un cas plus générique, on ne traitera jamais le cas le plus spécifique => Ne pas trop s'inquiéter, le compilateur vérifie tout cela

} catch(MonException e) {System.err.println("Erreur : patati");e.printStackTrace(System.err);

}

Que faire dans une clause catch ?

Au minimum (phase de développement):Afficher un message d'erreur

Conseil :

Au mieux (programme diffusé):Récupération de l'erreurDélégation ou filtrage : prévenir les « supérieurs »

INTERDIT !De ne rien fairePourquoi ? Parce que l'utilisateur ne sait pas qu'il s'est passé quelque chose !

} catch(MonException e) {// ici je ne fais rien

}

Délégation

Question: que faire si on ne veut/peut pas traiter une exception ?Réponse 1: on peut déléguer à celui qui nous a appelé

public class MaClasse {....public void maMethode(...) throws FileNotFoundException,

IOException {

// ici, code générant éventuellement// des exceptionsFileReader fr = new FileReader("toto.txt");int carac = fr.read();// et la suite ......

}}

public class MaClasse {....public void maMethode(...) throws FileNotFoundException,

IOException {

// ici, code générant éventuellement// des exceptionsFileReader fr = new FileReader("toto.txt");int carac = fr.read();// et la suite ......

}}

Remarque: si on oublie le throws alors le compilateur se plaint

Filtrage

Question: que faire si on ne veut/peut pas traiter une exception ?Réponse 2: on peut filtrer pour celui qui nous a appelé

public class MaClasse {....public void maMethode(...) throws FichierException {

try {FileReader fr = new FileReader("toto.txt");int carac = fr.read();// et la suite ...

} catch(Exception e) { // pour toute exceptionFichierException fe = new FichierException("problème de

fichier");fe.initCause(e); // enregistrer la causethrow fr; // lancer l'exception filtrée

}}

}

public class MaClasse {....public void maMethode(...) throws FichierException {

try {FileReader fr = new FileReader("toto.txt");int carac = fr.read();// et la suite ...

} catch(Exception e) { // pour toute exceptionFichierException fe = new FichierException("problème de

fichier");fe.initCause(e); // enregistrer la causethrow fr; // lancer l'exception filtrée

}}

}

Remarque: la classe FichierException est une exceptionpersonalisée (cf. suite du cours)

Clause finale

public class MaClasse {....public void maMethode(...) {

try {// ici, code générant éventuellement// une exceptionFileReader fr = new FileReader("toto.txt");// et la suite ...

} catch(FileNotFoundException e) {// traitement de l'exceptionSystem.err.println("Je ne trouve pas toto.txt");

} finally {fr.close(); // en fait pas nécessaire en Java

} // pour les fichiers, mais d'autres... // ressources doivent être gérées

} // manuellement (ex. réseau, etc.)}

public class MaClasse {....public void maMethode(...) {

try {// ici, code générant éventuellement// une exceptionFileReader fr = new FileReader("toto.txt");// et la suite ...

} catch(FileNotFoundException e) {// traitement de l'exceptionSystem.err.println("Je ne trouve pas toto.txt");

} finally {fr.close(); // en fait pas nécessaire en Java

} // pour les fichiers, mais d'autres... // ressources doivent être gérées

} // manuellement (ex. réseau, etc.)}

Du code exécuté dans tous les cas, même si exception il y a

Exceptions et conception par contrat

Questions rituelles sur les « exceptions »Pourquoi créer ses propres classes d'exceptions ?Quand et pourquoi signaler une exception personnalisée ?

Réponses de la conception par contrat:On créer une classe d'exception (ou hiérarchie) par catégorie de contrat (en général une hiérarchie par paquetage de 10 à 20 classes maxi)On signale une exception si un contrat est rompu

La conception par contrat sert de guide pour l'élaboration des tests unitaires

Principes de la conception par contrat

FournisseursClasses/Méthodes que l'on définit nous-même

ClientsCode externe (que l'on ne voit pas) et qui utilise nos classes fournisseurs

Contrats (pour chaque méthode publique d'une classe en cours de conception) :

Prérequis (ou précondition externe) : ce que les clients doivent respecter lorsqu'ils font appel au fournisseur en cours de conception.Garanties (ou postcondition externe) : ce que le fournisseur se charge de fournir si le client respecte sa part du contrat etque tout se passe bien.

Exemple de contrat (monde réel)Fournisseur:

Société de chemins de fer

Clients:Passagers

Contrat : transport de Paris à MarseillePrérequis (conditions booléennes imposées au client)

Le client paye son billetLe client arrive à l'heure pour son train

Garanties (conditions vraies après traitement):Transport en tout sécurité du passager dans les délais prévusPrévoir une indeminisation en cas de retard

Exemple: Cuve (1)

public class CuveBornee {private double niveau;private double limite;public CuveBornee(double limite) {

this.limite = limite; niveau = 0;}public double getNiveau() { return niveau; }

public void remplir(double quantite) { ...

}

public void vider(double quantite) { ...

}}

public class CuveBornee {private double niveau;private double limite;public CuveBornee(double limite) {

this.limite = limite; niveau = 0;}public double getNiveau() { return niveau; }

public void remplir(double quantite) { ...

}

public void vider(double quantite) { ...

}}

Une classe de Cuve bornée contenant du liquide (en litres)

Exemple: Cuve (2)

Fournisseur:Méthode remplir() de la classe CuveBornee

Clients:Toute expression qui invoque la méthode remplir sur un objet de la classe CuveBornee depuis l'extérieur (ex.: dans une classe de test pour cuve bornée, dans un programme qui a besoin d'une cuve bornée, etc.)

Contrat: remplir la cuve avec du liquidePrérequis : le liquide versé par le client, ajouté au niveau actuel ne dépasse pas la limiteGaranties : le niveau est l'ancien niveau auquel on ajoute la quantité versée par le client

Exemple: Cuve (3)

Que peut-il se passer ?Le client assure les prérequis

Le fournisseur fournit les garantiesLe client peut tester les garanties => Ecrire un testSi un test de garantie échoue, alors il s'agit d'un bug !

Un problème d'environnement survient(ex. plus de mémoire) :

une exception système est levée par Java: il faut la traiter, la filtrer ou la déléguer

Exemple: Cuve (4)

Que peut-il se passer ?Le client n'assure pas les prérequis

Le fournisseur lève une exception personnalisée=> définir une (ou plusieurs) classe(s) personnalisée(s)

Exemple: Cuve (5)

// Exception de base pour tous les problèmes de cuvepublic class CuveException extends Exception {

public CuveException(String message) {super("Problème de cuve : " + message);

}}

// Exception spécifique pour le contrat de remplir()public class CuvePleineException extends CuveException {

public CuvePleineException() {super("Cuve pleine");

}}

// Exception de base pour tous les problèmes de cuvepublic class CuveException extends Exception {

public CuveException(String message) {super("Problème de cuve : " + message);

}}

// Exception spécifique pour le contrat de remplir()public class CuvePleineException extends CuveException {

public CuvePleineException() {super("Cuve pleine");

}}

Phase 1 : Classe(s) d'exception(s) personnalisée(s)

Exemple: Cuve (6)

Public class CuveBornee {...public void remplir(double liquide) throws CuvePleineException {// PREREQUIS : Le liquide versé, ajouté au niveau actuelle,// ne dépasse pas la limite if(niveau+liquide>limite) throw new CuvePleineException();

// ... la suite}...

}

Public class CuveBornee {...public void remplir(double liquide) throws CuvePleineException {// PREREQUIS : Le liquide versé, ajouté au niveau actuelle,// ne dépasse pas la limite if(niveau+liquide>limite) throw new CuvePleineException();

// ... la suite}...

}

Phase 2 : Vérification des prérequis=> tester la condition de prérequis=> lever une exception personnalisée si la condition est fausse

Rappels : Le fournisseur est la méthode remplir, Les clients sont ceux qui invoquent la méthode

Exemple: Cuve (7)

Public class CuveBornee {...public void remplir(double liquide) throws CuvePleineException {// PREREQUIS : Le liquide versé, ajouté au niveau actuelle,// ne dépasse pas la limite if(niveau+liquide>limite) throw new CuvePleineException();

// TRAITEMENTniveau = niveau+liquide;

// la suite}...

}

Public class CuveBornee {...public void remplir(double liquide) throws CuvePleineException {// PREREQUIS : Le liquide versé, ajouté au niveau actuelle,// ne dépasse pas la limite if(niveau+liquide>limite) throw new CuvePleineException();

// TRAITEMENTniveau = niveau+liquide;

// la suite}...

}

Phase 3 : Description des traitements=> (enfin) le code java de la méthode !

Exemple: Cuve (8)

public class CuveBornee {...public void remplir(double liquide) throws CuvePleineException {// PREREQUIS : Le liquide versé, ajouté au niveau actuelle,// ne dépasse pas la limite if(niveau+liquide>limite) throw new CuvePleineException();

// TRAITEMENTniveau = niveau+liquide;

// GARANTIE : le niveau est l'ancien niveau auquel on ajoute // la quantité versée par le client// this.getNiveau() = old.getNiveau()+liquide;// Problème : java ne connaît pas old, donc commentaire !}...

}

public class CuveBornee {...public void remplir(double liquide) throws CuvePleineException {// PREREQUIS : Le liquide versé, ajouté au niveau actuelle,// ne dépasse pas la limite if(niveau+liquide>limite) throw new CuvePleineException();

// TRAITEMENTniveau = niveau+liquide;

// GARANTIE : le niveau est l'ancien niveau auquel on ajoute // la quantité versée par le client// this.getNiveau() = old.getNiveau()+liquide;// Problème : java ne connaît pas old, donc commentaire !}...

}

Phase 4 : Expression des garanties=> un commentaire qui permettra ensuite de créer un test

Test unitaire avec Junit

Evidence : il faut tester ses programmesConstat : manque de méthodologie

Faire un main dans une classe séparéetester « un peu au pif »

Solution : Junit (sur http://www.junit.org)Pour chaque classe MaClasse, créer une classe MaClasseTestqui hérite de junit.framework.TestCasePour chaque méthode maMethode() de MaClasse, créer (au moins) une méthode de test testMaMethode() dans MaClasseTest

Il faut aussi créer des test unitaires pour tester la combinaison de plusieurs méthodes et de plusieurs classes (tests d'intégration)

Structure d'une classe de testimport junit.framework.*;public class MaClasseTest extends TestCase {private MaClasse mon_objet; // pour tout les tests

public void setUp() { // préparation global de tous les testsmon_objet = new MaClasse(...);

}

public void tearDown() { // terminaison de tous les testsmon_objet = null;

}

public void testMaMethode() { // test unitaire// ici les tests pour maMethode() dans MaClasse// on peut utiliser mon_objet ou utiliser des// objets MaClasse locaux

}}

import junit.framework.*;public class MaClasseTest extends TestCase {private MaClasse mon_objet; // pour tout les tests

public void setUp() { // préparation global de tous les testsmon_objet = new MaClasse(...);

}

public void tearDown() { // terminaison de tous les testsmon_objet = null;

}

public void testMaMethode() { // test unitaire// ici les tests pour maMethode() dans MaClasse// on peut utiliser mon_objet ou utiliser des// objets MaClasse locaux

}}

Contenu d'un test unitaire

import junit.framework.*;public class MaClasseTest extends TestCase {...public void testMaMethode() { // test unitaire

// ici les tests pour maMethode() dans MaClasse// on peut utiliser mon_objet ou utiliser des// objets MaClasse locaux

<resultat> = mon_objet.MaMethode(...);assertTrue(<test booléen sur le résultat>);// ... autres tests possibles

}}

import junit.framework.*;public class MaClasseTest extends TestCase {...public void testMaMethode() { // test unitaire

// ici les tests pour maMethode() dans MaClasse// on peut utiliser mon_objet ou utiliser des// objets MaClasse locaux

<resultat> = mon_objet.MaMethode(...);assertTrue(<test booléen sur le résultat>);// ... autres tests possibles

}}

Chaque test contient:● Du code java réalisant les tests● Des assertions de tests vérifiant les résultats/modifications

● assertTrue(<expression booléenne>) => vrai ok, faux erreur de testRemarque: assertTrue(true) toujours vrai et assertTrue(false) toujours

faux

Exemple: Cuve1) Tester les prérequispublic class CuveBorneeTest extends TestCase {private CuveBornee cuve1;public void setUp() { cuve1 = new CuveBornee(10.0); }public void tearDown() { cuve1 = null; }public void testRemplir() {// 1) Tester le prérequis// 1.a) prérequis non validetry { cuve1.remplir(12.0);

assertTrue(false); // il ne faut pas arriver ici} catch(CuvePleineException e) {

assertTrue(true); // il faut arriver ici}// 1.b) prérequis valide try { cuve1.remplir(4.0);

assertTrue(true); // il faut arriver ici} catch(CuvePleineException e) {

assertTrue(false); // il ne faut pas arriver ici}

}

public class CuveBorneeTest extends TestCase {private CuveBornee cuve1;public void setUp() { cuve1 = new CuveBornee(10.0); }public void tearDown() { cuve1 = null; }public void testRemplir() {// 1) Tester le prérequis// 1.a) prérequis non validetry { cuve1.remplir(12.0);

assertTrue(false); // il ne faut pas arriver ici} catch(CuvePleineException e) {

assertTrue(true); // il faut arriver ici}// 1.b) prérequis valide try { cuve1.remplir(4.0);

assertTrue(true); // il faut arriver ici} catch(CuvePleineException e) {

assertTrue(false); // il ne faut pas arriver ici}

}

Exemple: Cuve2) Tester les garanties

public class CuveBorneeTest extends TestCase {...public void testRemplir() {// 1) Tester le prérequis...double old_getNiveau = cuve1.getNiveau(); // avant de remplirtry { cuve1.remplir(4.0);

assertTrue(true); // il faut arriver ici} catch(CuvePleineException e) {

assertTrue(false); // il ne faut pas arriver ici}// 2) Tester les garanties// GARANTIE: this.getNiveau() = old.getNiveau()+liquideassertTrue(cuve1.getNiveau()=old_getNiveau+4.0);

}}

public class CuveBorneeTest extends TestCase {...public void testRemplir() {// 1) Tester le prérequis...double old_getNiveau = cuve1.getNiveau(); // avant de remplirtry { cuve1.remplir(4.0);

assertTrue(true); // il faut arriver ici} catch(CuvePleineException e) {

assertTrue(false); // il ne faut pas arriver ici}// 2) Tester les garanties// GARANTIE: this.getNiveau() = old.getNiveau()+liquideassertTrue(cuve1.getNiveau()=old_getNiveau+4.0);

}}

Remarque : on pourrait aussi cloner la cuve avant de la modifier mais il faut des objets clonables pour cela.

Exemple: Cuve3) Lancer les tests

java junit.swingui.TestRunner CuveBorneeTestjava junit.swingui.TestRunner CuveBorneeTest

Attention : il faut que junit.jar et CuveBorneeTest soient dans le CLASSPATH

Remarque: en TME, on utiliseEclipse qui intègre un supportavancé pour les tests unitaires

Conclusion

A partir de maintenant, vous savez:Comment traiter les exceptions:

Traitement immédiatDélégation ou filtrage

Comment concevoir des classes robustesVision contrat client/fournisseur

Votre rôle est de définir le fournisseurPrérequis et exceptions personnaliséesGaranties et tests unitaires

=> A partir de maintenant, vos programmes Java doivent être robustes

Le mot de la fin

Exercice à la maison:Compléter la classe de cuve bornée

Pointeurs:Junit : http://www.junit.orgConception par contrat : http://www.eiffel.com

La semaine prochaine:Les interfaces graphiques en Swing

Recommended