Dans le framework, il existe un certain nombre de classes d'exceptions génériques. Il est plus simple et plus standard de les utiliser plutot que d'en créer des nouvelles dans des cas généraux.
Par exemple une methode save ne devrait pas lancer d'exceptions SQL à moins d'être dans un package SQL. Lancer une exception générique GB_Save Exception est plus appropriée.
Design par interface: Lors de la création d'interfaces, des exceptions génériques sont souvent utiles et pratiques.
public void save(Object a_value) throws GB_SaveException { try { // ... } catch (SQLException ex) { throw new GB_SaveException("Error saving " + a_value, ex); } }
Voir package: com.loribel.commons.exception.
On distingue 2 types d'exceptions:
Exceptions les plus courantes (dérivent de GB_Exception):
Lorsqu'il survient une exception à l'éxécution d'une commande usager (bouton, menu, ...), Il est conseillé de catcher l'exception et d'afficher un message d'erreur. Pour cela vous pouvez utiliser la classe utilitaire com.loribel.commons.gui.GB_ErrorDialog qui permet d'afficher facilement une boite de message pour les erreurs. Si vous passer une exception non nulle en paramètre, la boite de dialogue contient un bouton détails pour afficher la stack trace de cette exception.
public void actionPerformed(ActionEvent a_event) { try { // ... } catch (Throwable ex) { Component l_parent = ...; GB_ErrorDialog.showErrorMsg(l_parent, ex); } }
public void actionPerformed(ActionEvent a_event) { try { // ... } catch (Throwable ex) { String l_msg = "Mon message d'erreur"; String l_title = "Titre de la boite de dialogue"; int l_typeError = JOptionPane.WARNING_MESSAGE; Component l_parent = ...; GB_ErrorDialog.showErrorMsg(l_parent, l_msg, ex, l_title, l_typeError); } }
D'après mon expérience personnelle, dans les cas où aucune précaution n'est prise, 80% des exceptions runtime qui font planter un programme viennent d'un NullPointerException. 3/4 d'entre elles sont corrigées ou auraient pu être évitées avec un simple if. Le reste, qui représente des vrais bugs, est souvent difficile à debugger.
On présente ici quelques règles simple pour que le programme soit plus robuste, et plus simple à débugger en cas de bug:
Avant de boucler sur une collection ou un tableau, toujours s'assurer qu'il n'est pas null. Dans 99% des cas, quand on code une boucle, il devrait arriver la même chose que la liste soit vide ou nulle.
On peut soit tester si la collection n'est pas nulle avant de boucler sur les éléments, ou utiliser les templates suivants qui utilise des fonction utilitaires comme CTools.getSize(Collection) qui retourne le nombre d'éléments d'une collection, et 0 si la collection est vide ou nulle.
public void test( List a_list) { int len = CTools.getSize(a_list); for (int i = 0; i < len; i++) { String l_item = (String) a_list.get(i); // ... } }
public void test( Collection a_items) { if (a_items != null) { for (Iterator it = a_items.iterator(); it.hasNext();) { Object l_item = it.next(); // ... } } }
public void test( String[] a_items) { int len = CTools.getSize(a_items); for (int i = 0; i < len; i++) { String l_item = a_items[i]; // ... } } }
Il est bon de fixer des conventions pour les retour de méthodes qui retourne des type de 'collection' (Collection ou Array). Si on regarde le code java, en général, lorsque la méthode retourne un tableau, le retour n'est jamais null, la méthode retourne un tableau vide si aucun élément n'est retourné.
Convention à suivre:
public Object[] test() { List retour = new ArrayList(); // ... int len = retour.size(); if (len == 0) { return new Object[0]; } return retour.toArray(new Object[len]); } }
Rare doivent être les cas où l'on doit supposer que le retour d'une fonction est toujours non null.
Comme on l'a vu avec les collections, avant de boucler sur une collection, il faut s'assurer qu'elle soiet non nulle. Cette règle s'applique auusi sur les autres objets, avant d'appeler des méthodes sur un objet, il faut s'assurer qu'il n'est pas null. On ne peut pas souvent supposer que l'objet retourné par une fonction par exemple est toujours non null.
2 Cas possibles:
public void capitalizeName() { Person l_person = getPerson(); if (l_person == null) { return; } String l_name = l_person.getName(); l_name = GB_StringTransformTools.toCapitalize(l_name); l_person.setName(l_name); }
Dans certains cas, il peut être utile de considérer qu'un paramètre peut être null. Un exemple facile, une méthode qui transforme une String (mettre en capitale, ...). La méthode reçoit un paramètre de type String et retourne la String transformée. Cela fait bien du sens que si le paramètre passé est null, la méthode retourne null. Cela permet d'appeler la méthode sans risque de NullPointer. Dans le framework, de nombreuses méthodes respectent cette règle, par exemple voir les méthodes de la classe STools.
public static String toCapitalize( String s) { if (STools.isNull(s)) { return s; } String retour = s.substring(1); String firstLetter = s.substring(0, 1); retour = firstLetter.toUpperCase() + retour; return retour; }
When a dynamic linking failure or some other "hard" failure in the virtual machine occurs, the virtual machine throws an Error. Typical Java programs should not catch Errors. In addition, it's unlikely that typical Java programs will ever throw Errors either.
Microsoft's C# language does not include an equivalent of Java's checked exceptions. Does this encourage lazy and error-prone code or is it an astute recognition that Java's checked exceptions are more trouble than they are worth?
Java Exceptions in a Nutshell
The Java language has three categories of exception,
Java Exception Strategy #1: Use unchecked exceptions for recoverable situations.
Use a subclass of java.lang.Exception to represent a transient or recoverable problem. The name of the new class should end with the suffix Exception to distinguish it from business-as-usual classes. Example: NullParameterRuntimeException - thrown when a parameter of a method is expected to have a non-null value.
Java Exception Strategy #2: Use unchecked for programming logic problems
Use a subclass of java.lang.RuntimeException to represent a programming problem.
The name of the new class should end with the suffix RuntimeException to make it clear that this exception is not required by
the Java compiler to be declared in the throws clause of method signatures.
Example: NullParameterRuntimeException - thrown when a parameter of a method is expected to have a non-null value.
Java Exception Strategy #3: Reserve the use of java.lang.Error for underlying virtual machine problems.
Under normal application development do not create or throw instances of java.lang.Error or its subclasses. Reserve these types of exception for
problems with the underlying virtual machine.
Example: java.lang.OutOfMemory - the exception thrown when the virtual machine has exhausted its allocated memory space.
Java Exception Strategy #4: Always throw a subclass of java.lang.Exception or java.lang.RuntimeException.
Do not explicitly create and throw instances of java.lang.RuntimeException or java.lang.Exception. Always throw a more
specific subclass instead. This enables error handling code for specific exceptional conditions to be written easily. In general, if error
handling code has to inspect the contents of an exception object to determine what action to take then the exceptions being thrown are not
specific enough.
Example:
throw new Exception( "Specific message" ); // No !!!
throw new SpecificException(); // Yes !!!
Java Exception Strategy #5: Create exception classes to represent what went wrong, not where.
Name exception classes so that they represent the type of exceptional circumstance that took place and not the class, package, component where the exceptional circumstance took place. Where the exception occurred should be part of the exception details. This promotes the reuse of exception classes and avoids having multiple different exception classes representing the same problem in different places. Example: BalanceTooLowForDebitException and not BankingAccountException.
Java Exception Strategy #6: Use the same parameterized message template for all instances of an exception class.
Objects of the same checked exception class should represent a specific exceptional situation and therefore always use the same message template. Values within the message may be different but the message template itself should be the same. The constructor of the exception class should take parameters for the message as arguments and not the message text itself. An exception class should know how to obtain its own message template text from a class constant, properties file, or other persistent store. If you find yourself wanting an exception to have a different message in a different context, create a separate exception class for that context. Having exceptions manage their own message text also makes listing exception messages for localization, user guides, programming guides, etc., much easier than if the messages are scattered throughout the whole of the source code.
Example: Instances of BalanceTooLowForDebitException should always use the message template "Account {0} does not have a high enough balance to process a debit of {1} {2}" where {0} is replaced by the account number, {1} is replaced by the debit amount, and {2} is replaced by the currency.
Java Exception Strategy #7: Do not catch and ignore exceptions
Only in relatively rare circumstances is it correct to catch and ignore an exception. If an exception cannot be handled sensibly at that point in the code, the exception should be allowed to propagate up the method call stack until it can be handled sensibly. Simply displaying an exception's details on a system console or in an error log and then allowing processing to proceed is not good considered 'handled sensibly'.
Example: challenge code similar to the following:
try { ... } catch (SomeException e1) // catch any instances of SomeException thrown in the try block { } // totally ignore the SomeException objects caught catch (SomeOtherException e2) // catch any instances of SomeOtherException thrown in the try block { e2.printStackTrace(); // log an exception's details but otherwise ignore it }
Java Exception Strategy #8: Do not catch checked exceptions and throw an unchecked exception.
It only makes sense to catch a checked exception and immediately throw an unchecked exception if, at that point in the code, the occurrence of a checked exception indicates a defect in the program logic. Catching a checked exception and immediately throwing an unchecked exception only to avoid adding to the throws clause of a method signature is never valid. Example: challenge code similar to the following:
try { ... } catch (SomeCheckedException e1) // catch instances of a checked exception { throw new SomeRuntimeException( e1 ); // throw an unchecked exception }
Java Exception Strategy #9: Catch unchecked exceptions and throw a checked exception
When a runtime exception has been thrown that the system might be able to recover from sensibly. Catch the runtime exception and throw a checked exception.
Example: catch a runtime exception representing a network time-out and throw a checked exception so that the calling methods know that they can catch a time-out and request a retry if it makes sense for them to do so.
Java Exception Strategy #10: Do not propagate exceptions from one architectural layer to another
Catch exceptions thrown by another architectural layer and throw an exception from the current architectural layer. Always pass the caught exception as an argument to the constructor of the new exception so that detailed information is not lost. This preserves the separation of concerns that the architecture layers provide.
Example: Catch an SQLException thrown by JDBC code and throw a SaveException that holds the original SQLException object in an instance variable.
Java Exception Strategy #11: Create abstract superclasses for related sets of exceptions
Regularly review existing exceptions usage and introduce abstract superclasses where the same corrective action might be used for a set of different exception classes. Callers can then name a single superclass in a catch block instead of all the individual exception classes for which a corrective action is appropriate. Making the superclasses abstract continues to enforce the throwing of specific concrete exceptions.
Example: Create an abstract superclass InvalidDebitValueException for exceptions like BalanceTooLowForDebitException and NegativeDebitValueException.
Java Exception Strategy #12: Consider declaring that a method throws a more generic exception
Before adding a specific exception class to the throws clause of a method, consider adding a superclass of that exception instead. Closely
related to strategy #11, careful thought about what can go wrong in a
method's execution and the sort of corrective action that needs to be taken, can help avoid ripple affects later if the method's implementation is changed. In the same way that parameters and return types of a method may be declared to be of more general types than strictly necessary to avoid a change in implementation causing a change in the method's signature, so with exceptions named in the throws clause.
Example: declare a method that debits an account as throwing an InvalidDebitValueException instead of a BalanceTooLowForDebitException.
Then should the implementation of the account be changed so that only debits upto a certain daily amount are allowed, a new subclass of InvalidDebitValueException can be introduced, DailyDebitAmountExceededException, without causing a change to the method's signature.
Create two general abstract classes, one that extends java.lang.Exception and another that extends java.lang.RuntimeException. Insist that all exceptions thrown be your application code be subclasses of these two new classes. These two classes can also include the code needed to retrieve the exception's message template from a property file or database, parse it and insert the parameters supplied. They can also support the nesting of other exceptions within themselves. Doing this helps developers apply strategies 4,5 and 9.
Well run code inspections or pair programming can help developers apply the other strategies in the list.
Some automated code auditing tools may be able to identify ignored exceptions and the explicit creation of java.lang.Exception, java.lang.RuntimeException and java.lang.Error instances, or explicit creation of classes ending in 'Exception' that are not subclasses of your two general abstract exception classes.
For a large project/system it may be worth assigning someone the job of managing exceptions so that existing exceptions are reused where appropriate and not used where inappropriate.
This new facility provides a common API to record the fact that one exception caused another, to access causative exceptions, and to acess the entire "causal chain" as part of the standard stack backtrace, ensuring that preexisting programs will provide this information with no additional effort on the part of their authors.
To address these issues, we have added two new methods and two new constructors to Throwable:
Other "general purpose" exception classes (like Exception, RunTimeException and Error) have been similarly outfitted with (Throwable) and (String, Throwable) constructors. However, even exceptions without such constructors will be usable as "wrapped exceptions" via the initCause method.
The implementation of Throwable.printStackTrace has been modified to display backtraces for the entire causal chain of exceptions. New method getStackTrace provides programmatic access to the stack trace information provided by printStackTrace.