Atelier 04
Dans ce laboratoire, vous reverrez l’extraction de hiérarchies de classes à partir de descriptions informelles, ainsi que leur syntaxe Java associée.
Carnivores et herbivores
Lisez attentivement la description des exigences suivantes :
- Contractuellement, tous les animaux doivent être capables de consommer de l’eau et de la nourriture. Tous les animaux doivent avoir :
- une méthode
drink(commune à tous les animaux, affiche « Glou, glou, glou »); - une méthode
eat(dont l’implémentation varie);
- une méthode
- Les animaux se divisent en deux sous-catégories. Ils peuvent être soit herbivores, soit carnivores (on suppose qu’ils ne peuvent jamais être les deux).
- Les herbivores et les carnivores partagent une même implémentation pour la consommation d’eau.
- Les herbivores et les carnivores n’ont pas la même implémentation pour manger :
- Les herbivores affichent « Miam, délicieuse salade ! »
- Les carnivores affichent « Rooar, steak juteux ! »
- Seuls certains types d’animaux doivent pouvoir être instanciés (avec
new). Ce sont :Lion(Carnivore)Tigre(Carnivore)Zèbre(Herbivore)Gazelle(Herbivore)
Extraction conceptuelle
- Traduisez les exigences techniques suivantes en code Java conceptuel, en vous basant sur les notions vues en classe.
- L’objectif est d’éviter toute duplication de code (aucun copier-coller de méthodes entre classes).
- Utilisez la notation UML vue en classe :
Récapitulatif des exigences
Pour chaque fichier de classe, déterminez :
1. Le type de classe : standard / abstraite / interface
2. Les méthodes qui doivent être spécifiées dans la classe
3. Si la classe hérite d’une autre : mot-clé approprié
Implémentation d’une interface
Exemple de notation :
classDiagram
class Interface {
+foo()
}
class Class {
+foo()
}
Interface <|.. Class : implements
Extension d’une classe
Exemple de notation :
classDiagram
class BaseClass {
+bar()
}
class ChildClass {
+foo()
}
BaseClass <|-- ChildClass : extends
Implémentation
Après avoir établi une hiérarchie de classes appropriée sur papier, traduisez votre solution conceptuelle en une implémentation.
- Créez la hiérarchie requise de classes Java.
- Assurez-vous qu’il n’y a aucune duplication de code entre les méthodes.
Révision du code
Dans cet exercice, vous êtes confronté à un court morceau de logiciel qui « fonctionne », mais qui illustre un mauvais style de programmation provoquant des sorties trompeuses dans certaines situations.
Votre tâche consiste à :
- Identifier le problème.
- Examiner le code pendant au moins 5 à 10 minutes.
- Expérimenter et exécuter le code jusqu'à ce que vous le compreniez entièrement et sachiez quelles entrées produisent une sortie trompeuse.
- Ne demandez pas à une IA.
- Corriger le problème.
Code
Il n'y a que trois classes. Je recommande de démarrer un nouveau projet dans IntelliJ et de coller le code dans les classes correspondantes.
Launcher
package handling;
public class ExceptionHandlerLauncher {
public static void main(String[] args) {
// The closestRoot algorithm has an exception in its signature, so we need a try/catch block.
try {
System.out.println(
"The closest int to your square-route is: " + ClosestRoot.getClosestRoot());
} catch (Exception e) {
System.out.println("This is not a parseable number!");
}
}
}
Custom Exception
package handling;
public class BlameTheUserException extends Exception {
public BlameTheUserException(String message) {
super(message);
}
}
ClosestRoot
package handling;
import java.math.BigInteger;
import java.util.Scanner;
/**
* This class computes the "closest" int to a provided number's squareroute, provided on command
* line. It throws an exception, if the provided input cannot be parsed.
* Since user inputs are potentially very large, we internally use the BigInteger class for
* computing the squareroute.
*
* @author Maximilian Schiedermeier.
*/
public class ClosestRoot {
/**
* Reads a value from command line and then returns the closest int to the input's squareroute.
*
* @return closest int to the inputs square root.
* @throws BlameTheUserException if the provided input cannot be parsed to an int.
*/
public static int getClosestRoot() throws BlameTheUserException {
// Tell the user what to do. Then read the input.
System.out.println(
"Hey user, give me a number, and I'll compute the closest int to it's square route for "
+ "you!");
// Now actually read what comes back.
Scanner sc = new Scanner(System.in);
String userInput = sc.next();
BigInteger userInputAsBigInteger;
// Next we need to convert the input to an int (ignore the fact that we could have used
// nextInt()). This may fail, because the user may have provided something else than a
// number, e.g. "Hello!". If it fails, we want to be specific and blame the user with a
// custom exception.
try {
userInputAsBigInteger = BigInteger.valueOf(Integer.parseInt(userInput));
} catch (NumberFormatException nfe) {
// Our custom exception is **not** a RunTimeException, thus it has to be added to the
// method signature.
throw new BlameTheUserException("The input provided by the user is not a parse-able number.");
}
// If we were able to parse the input, we can proceed.
return userInputAsBigInteger.sqrt().intValue();
}
}
Quel est le problème
N'accédez pas à la solution avant d'avoir fait un effort raisonnable pour trouver la réponse par vous-même. Écrivez votre réponse sur une feuille de papier, comme s'il s'agissait d'une question d'examen corrigée.
Quel est le problème
Le programme illustre le mauvais style de programmation consistant à capturer la classe de base Exception. Cela signifie que le bloc catch est activé pour toute exception, y compris les exceptions qui n'ont aucun rapport avec notre exception personnalisée. Le problème se manifeste lorsqu'on transmet des nombres négatifs : le message d'erreur laisse entendre que l'utilisateur est en faute, alors qu'en réalité le code n'a jamais anticipé cette possibilité. Une négligence manifeste du programmeur est dissimulée derrière un catch Exception e générique.
Que faire à ce sujet
N'accédez pas à la solution avant d'avoir fait un effort raisonnable pour trouver la réponse par vous-même. Corrigez le programme et justifiez vos modifications par des commentaires, comme s'il s'agissait d'une situation d'examen.
Que faire à ce sujet
Il existe plusieurs options, mais la première modification doit, en tout état de cause, consister à modifier le bloc catch afin qu'il ne capture que l'exception déclarée dans la signature de la méthode appelée. La règle de base consiste à ne jamais capturer une RuntimeException et à ne jamais lister une RuntimeException dans une signature de méthode. Deuxièmement, le code devrait être renforcé contre la possibilité de recevoir des entrées négatives. Il devrait y avoir une vérification explicite dans la méthode, jetant une BlameTheUserException avec un message d'erreur adapté. À titre alternatif, le code pourrait convertir l'entrée en un entier positif à l'aide de la méthode Math.abs. Dans tous les cas, le code devrait documenter la manière dont il traite les entrées négatives et fournir cette information sous forme de JavaDoc au développeur qui s'appuie sur ce morceau de code.