Upload
others
View
9
Download
0
Embed Size (px)
Citation preview
Programmation Objet (en Java)
UPMC – LicenceInformatique - LI 314
© 2006-2007 Frédéric PeschanskiLaboratoire d'Informatique de Paris 6
email: [email protected]
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