Exceptions

images/whitebelt.png

Ce qu’il ne faudrait pas faire en cas d’incident

Voici un code "client" qui utilise les services d’une classe Produit:

public class Panier {
   // codes
   public void ajouter(Produit choix) {
      choix.retirerDuStock(1) ;
      this.enregistrer(choix) ;
      // .....
   }
}

Et maintenant un extrait du code de Produit :

public class Produit { // VERSION 1
   // ...
   private int stock ; // demo: probablement un type plus complexe!
   // ...
   public void retirerDuStock(int nb) {
      // supposons que l'on contrôle nb > 0
      if( nb <= this.stock) { // Il faut éviter un stock négatif!
         this.stock -= nb ;
      }
   }
[Attention]Mauvais!

Personne n’est au courant qu’un incident s’est produit (s’il y a une demande qui dépasse le stock disponible).

Le code "client" est erroné: il enregistre l’achat!

Un autre essai:

public class Produit { // VERSION 2
   // ...
   private int stock ; // demo: probablement un type plus complexe!
   // ...
   public void retirerDuStock(int nb) {
      // supposons que l'on contrôle nb > 0
      if( nb <= this.stock) { //  Il faut éviter un stock négatif!
         this.stock -= nb ;
      } else {
         System.err.println("pas en stock") ;
      }
   }
[Attention]Toujours mauvais!

Violation du principe d'indépendance: comment le code sait-il qu’il a accès à la console? (Il pourrait être utilisé dans une application graphique). Par ailleurs l’utilisateur final doit-il être la seule personne prévenue de l’incident?

Le code "client" est toujours inadequat!

public class Produit { // VERSION 3
   // ...
   private int stock ; // demo: probablement un type plus complexe!
   // ...
   public boolean retirerDuStock(int nb) {
      // supposons que l'on contrôle nb > 0
      if( nb <= this.stock) { //  pas de stock < 0
         this.stock -= nb ;
         return true ;
      } else {
          return false ;
      }
   }
[Attention]Risqué!
  • Le code "client" n’est pas forcé de tester le résultat de l’appel de la méthode retirerDuStock. Dans ce cas c’est ce code "client" qui serait en faute.
  • pas de diagnostic précis de ce qui vient de se produire: d’autres parties de l’application seraient peut-être intéressées par des informations plus précises.
public class Produit { // VERSION 4
   // ...
   private int stock ;
   // ...
   public Diagnostic retirerDuStock(int nb) {
      // supposons que l'on contrôle nb > 0
      if( nb <= this.stock) { //
         this.stock -= nb ;
         return new Diagnostic() ; // diagnostic OK
      } else {
          return new Diagnostic(stock, nb, this) ; // pas ok
      }
   }
[Attention]Encore risqué et pas toujours possible!
  • Le code "client" n’est pas forcé de tester le résultat de l’appel de la méthode retirerDuStock. Dans ce cas c’est ce code "client" qui serait encore en faute.
  • comment émettre un diagnostic si la méthode a besoin de renvoyer un résultat d’un type différent, ou si l’incident se produit dans un constructeur (qui ne renvoie aucun résultat)?

Donc pour émettre des diagnostics pertinents on doit:

  • disposer de diagnostics riches (des données qui peuvent être manipulées par d’autres parties de l’application).
  • disposer de moyen de distinguer les diagnostics des types de retour
  • forcer le code client a prendre en compte le fait qu’un incident s’est produit.

Ceci relève du mécanisme des Exceptions en Java.

Catégories d’incidents

Qu’est ce qu’un "incident" au niveau de l’exécution d’une application?:

  • Un code détecte qu’il est impossible de rendre un service. Cette impossibilité découle d’une "règle métier": par exemple il est impossible de faire un retrait d’un montant mnt (correct en lui même) parceque cela mettrait le compte à découvert.

    Ce code de contrôle générera alors un diagnostic qui relève du comportement normal de l’application! (Il est normal de signaler l’incident et de prendre les mesures appropriées).

  • Un code détecte une anomalie qui découle d’un comportement anormal des codes: dans l’exemple précédent il est exclu d’avoir un montant mnt qui soit négatif (c’est explicitement spécifié dans la documentation de la méthode retrait).

    On a ici un comportement anormal de l’application: c’est un bug qu’il faut corriger!

Dans le cadre d’un "comportement normal" le diagnostic sera récupéré par un code de traitement (et ce code sera obligatoirement présent). Le code de traitement s’appuiera sur les données transmises par l'"objet diagnostic".

Dans le cadre d’un comportement anormal il ne va pas être garanti qu’il y ait un code de prise en compte du diagnostic. Des mécanismes de récupération par défaut entrerons alors en action (et dans ce cas il faudra trouver un moyen pour rendre l’incident plus explicite).

Rentrent dans les "comportements anormaux" :

  • Des anomalies détectées au runtime par la JVM : erreur sur un index de tableau, demande passée à une référence nulle, erreur arithmétique, etc…. on a ici des RuntimeExceptions.
  • Des anomalies qui concernent le fonctionnement de la JVM elle-même et qui sont généralement fatal: épuisement de la mémoire, dysfonctionnement de la JVM, etc… on a ici des Errors.
  • Des RuntimeExceptions et des Errors volontairement déclenchées par programme: détection à l’exécution d’un paramètre non conforme aux spécifications (sous-classes de RuntimeException); détection au démarrage (souvent au loadtime) d’une erreur de configuration rendant impossible l’exécution (sous-classes de Error).

Le mécanisme des exceptions

image: schema mécanisme des exceptions

Le comportement normal est qu’il doit être garanti que le diagnostic soit capté par un code appelant situé en amont dans la pile d’exécution.

Dans certaines circonstances (RuntimeException, Error) des "exceptions orphelines" peuvent remonter la pile jusqu’au sommet et sont gérées par un UncaughtExceptionHandler - que l’on peut redéfinir - (éviter le comportement par défaut qui déclenche une mort prématurée du thread)

try/catch

Soit le code suivant saisi dans un outil interactif (IDE): image: IDE code susceptible de déclencher une erreur

On remarquera que l’appel à new ServerSocket(numeroPort) est refusé par le compilateur. En effet dans certaines conditions ce service ne sera pas exécuté et donc le compilateur exige la présence d’un code qui prenne en compte cette possibilité.

image: IDE code avec traitement erreur

S’agissant d’une demande de service qui peut potentiellement échouer (pour des raisons qui ne sont pas initialement connues du demandeur) le compilateur exige de positionner la demande dans un bloc try et de lui associer un bloc catch qui va contenir du code de traitement du diagnostic (analyse, correction d’erreur, signalement d’erreur, etc.).

L’exécution de ce code avec des paramètres erronés va déclencher une exception "orpheline" :

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at UnServeur1.main(UnServeur1.java:9)

Ici c’est une erreur dans un paramètre de lancement qui est une anomalie controlable et traitable par le demandeur: le compilateur n’a pas exigé de bloc try/catch.

Cette distinction sera approfondie ultérieurement ….

Exceptions standard

Voici une partie de la hiérarchie des exceptions standard: (Throwable est la super-classe de tous les objets qui ont ce type de comportement):

Throwable
Error
  • VirtualMachineError

    • OutOfMemoryError
    • StackOverflowError
  • AWTError
Exception
  • RuntimeException

    • ArithmeticException
    • NullPointerException
    • SecurityException
    • IllegalArgumentException
  • (Exceptions "controlées")
  • IOException

    • FileNotFoundException
    • EOFException

Les Errors sont difficiles à gérer.

Les Exceptions qui ne sont pas des RuntimeExceptions sont "controlées" (le compilateur va s’assurer de la présence obligatoire d’un bloc catch en amont sur la pile -un des codes appelant le bloc d’instructions va devoir disposer d’un bloc catch)

Definir une Exception

Definition d’une Exception. 

package com.maboite.management.taches ;

public class ExceptionAffectationHoraire extends Exception {
   public final Executant personne; // on pourrait avoir private + accesseur
   public final Tache tache ;

   public ExceptionAffectationHoraire(String detail, Executant pers, Tache tache) {
      super(detail); // c'est une donnée pas un message complet pour l'utilisateur
      this.personne = pers ;
      this.tache = tache ;
   }

   public String toString() {
      return super.toString() // TOUJOURS faire un toString de cette manière!
        + " : " + personne + "=> " + tache ;
   }
}

Une Exception définie par le programmeur peut hériter d’une des classes d’exceptions standard de java (ou d’autres exceptions).

Un tel objet contient des données qui pourront être exploités plus tard par le programme.

Ici faire attention ici à la chaîne detail : sauf pour les RuntimeException (qui peuvent échapper à tout traitement "normal") cette chaîne n’est pas un message (du genre "Atttention telle erreur est survenue" etc.). Il n’est pas du rôle du code de traitement d’exception de re-propager un tel message: il peut, par contre, en fonction d'éléments du contexte connus de lui, utiliser la chaîne informative detail pour orienter son comportement (par ex. dans un switch) et, éventuellement, générer une chaîne qu’il passera à un gestionnaire de rapports (voir plus loin). Donc la chaîne detail doit être considérée comme une donnée pas comme un message!

images/orangebelt.png

Dans certains cas le code qui traite une exception peut, à son tour, générer et propager une nouvelle exception qui "contient" l’exception précédente (exceptions "chainées").

Définition d’une Exception "chainée". 

package com.maboite.sysutils;

public class ExceptionBackup extends IOException {
   private String device ;
   public String getDevice() { return this.device ; }

   public ExceptionBackup(Exception cause, String dev) {
      super(cause) ;
      this.device = dev ;
   }
   // écrire un toString()
}

La classe Throwable (qui est la super-classe des Exceptions et des Errors) dispose d’un méthode getCause() qui rendra le Throwable précédent en cas d’incident chainé.

Déclenchement d’une Exception

images/whitebelt.png

Génération et déclenchement d’une Exception. 

   if(totalHoraire > TOTAL_HORAIRE_MAX ) {
      throw new ExceptionAffectationHoraire(
         "dépassement maximum légal" , exécutantCourant, tâcheCourante) ;

   }

images/orangebelt.png

re-propagation avec un chaînage d’exception. 

   try {
      // opérations de backup
   } catch (IOException exc) {
      throw new ExceptionBackup(exc, deviceCourant) ;
   }

blocs catch hiérarchisés

images/orangebelt.png

blocs catch disposés en tamis. 

   try {
      // on fait des tas de choses
      // y compris une sauvegarde
   } catch (ExceptionBackup bexc) { // BackupException extends IOException
      // on fait qqch.
   } catch (IOException ioexc) { // extends Exception
      // on fait autre chose
   } catch (Exception exc) {
      // comportement par défaut
   }
   // notons que les exceptions dans un tamis
   // ne sont pas nécessairement des sous-classes les unes des autres
   // par contre l'ordre est important

Pour éviter de dupliquer du code dans un ensemble de bloc catch il est possible d’opérer des regroupements:

   try {
   } catch(IOException|SQLException ex) {
      // log
      // relance
        }

--exercice--

blocs finally

images/orangebelt.png

public class Panier {
   // codes
   public void ajouter(Produit choix) {
       try {
      choix.retirerDuStock(1) ; 1
      this.enregistrer(choix) ; 2
      // .....
       } catch ( ExceptionStock stk) {
      // faire qqch
       }
   }
}

1

cette instruction peut échouer

2

si l’instruction précédente échoue alors l’instruction courante n’est pas exécutée (c’est ce que veut le programmeur).

Dans certaines circonstances ce comportement qui interrompt le flot normal des instructions peut s’avérer dangereux.

   // ouvrir une Transaction
   // faire qqch      1
   // faire qqch d'autre
   // clore la transaction 2

1

cet appel peut échouer

2

si l’instruction <1> échoue ce code n’est pas exécuté!

Dans ce cas le résultat peut être catastrophique: on doit être sûr que la transaction se termine!

On peut garantir une exécution en la mettant dans un bloc finally.

   // ouvrir Transaction
   try {
   // faire qqch
   // faire qqch d'autre
    } finally {
   // clore la transaction 1
    }

1

ce code sera toujours exécuté même s’il y a un return ou un déclenchement d’exception dans le try.

On peut combiner ces blocs de différentes manières:

   try {
   // ouvrir Transaction  1
   // faire qqch
   // faire qqch d'autre
    } catch (UneException exc) {
   // gérer l'exception
    } finally {
   // clore la transaction 2
    }

1

peut échouer!

2

Attention! Une erreur peut se déclencher ici si <1> échoue. Un opération de fermeture de la transaction ne devrait pas être invoquée sur une référence nulle (et une fermeture de transaction ne devrait avoir aucun effet si la transaction correspondante n’a pas été ouverte -ce genre d’opération devrait être idempotente mais, souvent, ce n’est malheureusement pas le cas-).

Autre combinaison :

   try {
   // ouvrir Transaction
      try {
      // faire qqch
      // faire qqch d'autre
       } finally {
      // clore transaction 1
       }
   } catch (Exception exc) {
   // gérer l'exception
   }

1

dans ce cas l’opération de fermeture peut elle-même déclencher une exception.

images/bluebelt.png

Les objets ciblés par des opérations de cloture peuvent être des objets AutoCloseable (propriété décrite par une interface: cette notion sera expliquée ultérieurement). Ces objets sont alors pris en compte par un throw avec cloture automatique:

try ( /* création objet Autocloseable 1 */ ;
           /* création objet Autocloseable n */ ) {
         code ....
      } catch (UneException exc) {
         code
      }

Ici la méthode close sera automatiquement appelée sur les objets (dans l’ordre inverse de leur création) à la fin de l’exécution du bloc.

Si ces fermetures provoquent des exceptions celle-ci seront masquées: on les retrouvera par exc.getSuppressed()

  // exemple entrée/sortie (anticipation sur la suite du cours)
 try (InputStream in = new FileInputStream(src);
       OutputStream out = new FileOutputStream(dest)) {
   // code de copie
 }

Il est également possible d’avoir une structure comme celle-ci:

try ( /* références à un AutoCloseable final */ ; ... ) {
         code ....
      } catch (UneException exc) {
         code
      }

La référence de l’objet AutoCloseable en paramètre du try devra être un objet final ou implicitement final (l’idée est que cet objet ne puisse pas "vivre" par ailleurs et qu’alors la fermeture automatique soit intempestive!).

// utilise des E/S ...que nous n'avons pas vus!
public void lecture( Path fichierTexte, ) throws IOException{
    // reader est en portée limitée!
    BufferedReader reader = Files.newBufferedReader(fichierTexte) ;
    try (reader) {
        // code
    }
}

La règle "propager ou traiter"

images/whitebelt.png

public class Produit { // VERSION 5
   // ...
   private int stock ;
   // ...
   public void retirerDuStock(int nb) {  1
      // on a controlé que nb > 0
      if( nb <= this.stock) {
         this.stock -= nb ;
      } else {
          throw new ExceptionStock(stock, nb, this) ; 2
      }
   }
    .....

1

toujours incorrect: voir ci-après …

2

On veut être sûr que quelqu’un fasse quelque chose avec le diagnostic d’erreur: exception controlée! On est ici dans un code de réalisation: on veut être sûr que dans le code appelant il y a un try/catch.

Une méthode déclarant un exception. 

public class Produit { // version correcte!
   // ...
   private int stock ;
   // ...
   // une directive de propagation d'exception accompagne la signature
   public void retirerDuStock(int nb) throws ExceptionStock {
      // on a controlé que nb > 0
      if( nb <= this.stock) {
         this.stock -= nb ;
      } else {
          throw new ExceptionStock(stock, nb, this) ;
      }
   }
    // .....

La définition de la méthode retirerDuStock utilise une directive de compilation throws (ne pas confondre avec instruction throw!): la méthode est "marquée" comme propageant une ExceptionStock. Toute compilation d’un code client utilisant cette méthode va prendre des mesures en conséquence.

code client pour une méthode qui propage une exception controlée (version 1: on traite). 

public class Panier {
   // codes
   public void ajouter(Produit choix) {
       try {
      choix.retirerDuStock(1) ;
      this.enregistrer(choix) ;
      // .....
       } catch ( ExceptionStock stk) {
      // faire qqch
       }
   }
}

code client pour une méthode qui propage une exception controlée (version 2: on propage). 

public class Panier {
   // codes
   public void ajouter(Produit choix) throws ExceptionStock{
      choix.retirerDuStock(1) ;
      // NON EXECUTE si exception
      this.enregistrer(choix) ;
   }
}

Le compilateur contrôle l’application de la règle: propager ou traiter! (et c’est ainsi qu’on est sûr qu’il y a un bloc catch quelque part en amont dans la pile d’exécution!).

Exception de runtime ou exception controlée?

Méthode déclarant une exception de runtime et une exception controlée. 

public class Produit { //
   // ...
   private int stock ;
   // ...
   public void retirerDuStock(int nb) throws ExceptionStock , IllegalArgumentException {
      if(nb <= 0 ) { // test de precondition
         throw new IllegalArgumentException("valeur obligatoirement positive") ;
      }
      if( nb <= this.stock) { //
         this.stock -= nb ;
      } else {
          throw new ExceptionStock(stock, nb, this) ;
      }
   }
    // .....

La méthode pourrait aussi être déclarée de la manière suivante:

   public void retirerDuStock(int nb) throws ExceptionStock {

Le compilateur du code courant va nous forcer à déclarer uniquement les exceptions controlées (ExceptionStock: ici l’exception standard IllegalArgumentException hérite de RuntimeException). Le compilateur des codes client va uniquement controler l’utilisation des exceptions controlées (règle "propager ou traiter").

Pourquoi cela?

Les RuntimeExceptions existent sous différentes incarnations:

  • conditions erronées qui sont détectées par la J.V.M ( IndexOutOfBoundsException , NullPointerException, …): si on forcait systématiquement les codes à capturer ces exceptions on obtiendrait un code inutilement boursouflé -de plus le compilateur ne peut détecter la plupart de ces cas-. Ces erreurs résultent de codes qui doivent être corrigés à la base.
  • conditions détectées par le code réalisant mais qui sont détectables par le code appelant (dans l’exemple: la précondition). Dans ce cas c’est le code client qui devient responsable d’un comportement correct: ou bien il met un try/catch de sa propre initiative ou il effectue le contrôle demandé. (Encore une fois forcer le code appelant à une capture risque d'être intempestif).

Vous avez noté sans doute quand dans l’exemple la runtimeException a été invoquée avec un vrai message (ce qui n’est pas le cas des exceptions contrôlées!)

Modèle d’une signature

Par exemple pour une méthode:

   public TypeResultat1 méthode ( TypeArg arg, TypeArg2 arg2 2)
            throws Exception1 , Exception2 3

1

Un résultat correspond à une donnée produite (out)

2

Les paramètres sont des données fournies (in) -Il serait idéal d’arriver à éviter des "effets de bord"-

3

Les exceptions symbolisent des productions d’erreurs (err) -distinctes du résultat "normal"-

Le même modèle s’applique aux constructeurs (il n’y a pas de résultat -si ce n’est l’instance fabriquée-).

[Avertissement]Attention!

Quand on spécialise une méthode on ne peut pas propager plus d’exceptions que cette méthode: on ne peut pas "aggraver" un contrat (mais on peut l’améliorer: propager moins d’exceptions que la super-classe ou propager des sous-classes des exceptions déclarées dans la super-classe).

Si un constructeur propage une exception controlée alors tous les contructeurs des sous-classes qui s’appuient sur ce constructeur (au travers de super(°°°°)) sont obligés de propager la même Exception.

Differences entre exceptions et rapports

images/orangebelt.png

exceptions et rapports

Les Exceptions sont un mécanisme interne à une application: elles véhiculent des données qui seront manipulées par les codes.

Les rapports sont pour le monde "extérieur" à l’application: ils peuvent véhiculer des messages lisibles par une être humain (ou des clefs de traduction de messages). Ils seront traités par des gestionnaires de rapport (handlers). Ces gestionnaires pouvant être situés à l’intérieur de l’application (par ex. interface graphique) ou à l’extérieur (outils de déploiement: on décide sur un site de mettre les rapports dans un fichier dans un certain format et certains rapports graves seront envoyés par mail à la maintenance).

Pour en savoir plus sur les rapports: package java.util.logging).

--exercice--

Notes sur les exceptions

[Note]

images/brownbelt.png

D’abord des remarques subjectives qui n’engagent que les auteurs de ce document.

Le principe qui consiste à forcer les codes appelants à traiter ou propager les exception contrôlées a été vivement critiqué. Certes c’est un mécanisme qui a été introduit pendant la toute dernière semaine d’implantation de la première version de java … mais est-ce une spécification hâtive?

On trouvera sur internet de nombreux articles écrits par des personnes tout à fait compétentes qui critiquent ce dispositif. Le résultat est que de nombreux langages qui se sont inspirés de java (de C# à Kotlin) on rejeté ce principe "propager ou traiter".

A notre humble avis cet abandon est trop violent et revient à jeter le bébé avec l’eau du bain. Certes il y a une "zone grise" dans les erreurs qui peuvent se produire dans un programme: par exemple quand on a une erreur d’entrée/sortie ce n’est pas tout à fait un bug (RuntimeException) et probablement pas une "exception métier" (exception contrôlée). On a effectivement des cas où cette obligation de traiter n’est pas facile à gérer.

Par contre nous ne voyons pas pourquoi la gestion des vraies exceptions métier serait gênante dans un gros projet (comme certains l’ont écrit) … bien au contraire. Peut-être pour de nouveaux langages aurait-il fallu trouver d’autre principes intermédiaires….

Si on était cynique on pourrait se demander si la cause principale du rejet des règles des exceptions contrôlées n’aurait pas, à la base, un aspect comportemental. Dans le milieu des programmeurs il y a plusieurs points qui font constamment l’objet de bonnes résolutions que presque personne ne tient: la documentation, la gestion des erreurs, les assertions et les tests.

Ceci dit nous vous montrerons plus tard un code java qui vous permettra d'échapper (temporairement!) à quelques situations scabreuses sans passer par une RuntimeException.

[Note]printStackTrace

images/orangebelt.png

Dans le tout premier ouvrage jamais écrit sur java (par son concepteur: James Gosling) il était écrit que les méthodes printStackTrace() de Throwable étaient destinées à des mises au point et des petits code des test.

Malheureusement cette consigne s’est perdue et on retrouve ces appels dans de trop nombreux codes. C’est regrettable: la méthode printStackTrace() va écrire, a priori, sur le canal d’erreur un dump de la pile d’exécution.

Cette pratique viole plusieurs principes fondamentaux dont le fait qu’un code ne peut savoir comment afficher des messages! Et d’ailleurs pour qui? Pour l’utilisateur ou pour le programmeur?

Imaginez la tête d’un utilisateur lambda qui tombe (par hasard?) sur ce torrent d’informations!

Conclusion: merci de respecter les consignes initiales et de ne pas mettre ces méthodes systématiquement à toutes les sauces. Si vous êtes pressés (et tous les programmeurs le sont!) prévoyez initialement dans une classe réservée de niveau package (donc une classe non public) une petite méthode utilitaire qui générera un rapport approprié (voir la leçon sur le logging). Dans ce cadre il est à noter que les exceptions contiennent effectivement des informations sur la pile d’exécution … et donc qu’il est important de les passer aux rapports de logging (puisque cela intéressera, à l’occasion, les codes d’exploitation des rapports qui sont destinés aux programmeurs).

Assertions

images/bluebelt.png

   // code réservé à l'usage exclusif des membres de mon équipe
   static double[] échéancier(double prêt, int durée, double taux) {
      // test des préconditions
      if(prêt <= 0 ) {
         throw new IllegalArgumentException(NEGATIVE_OR_NULL_VALUE) ;
         // constante chaîne définie qqpart ...
         // on pourrait aussi définir une exception spécifique
         // (et préciser l'argument fautif)
      }
      // autres tests de préconditions
      // code

   }

Ce code est en portée "package" uniquement: les codes clients sont controlés par des membres de la même équipe. Si les test de précondition sont très utiles pendant la phase de développement on voudrait pouvoir s’en débarasser en exécution "normale" (une fois qu’on est pratiquement sûr que les codes clients sont corrects)….. Sauf qu’on voudrait aussi pouvoir les réintroduire dans des tests de versions ultérieures!

Les assertions sont des propriétés qui doivent être impérativement vraies. Elles sont décrites dans le code mais leur vérification se fait uniquement sur demande (les tests correspondants sont présents dans le pseudo-code mais sont activés uniquement quand des options spéciales sont fournies).

   // code réservé à l'usage exclusif des membres de mon équipe
   static double[] échéancier(double prêt, int durée, double taux) {
      assert (prêt > 0) & (durée > 0) & (taux > 0 ) ;
      // code

   }

Si la machine virtuelle est lancée avec l’option -ea et qu’une condition n’est pas remplie alors une AssertionError sera déclenchée (et la machine virtuelle s’arrétera).

   // code réservé à l'usage exclusif des membres de mon équipe
   static double[] échéancier(double prêt, int durée, double taux) {
      assert (prêt > 0) & (durée > 0) & (taux > 0 ) :
         "prêt= " + prêt + " durée=" + durée
            + " taux=" + taux ;
      // code

   }
[Avertissement]

Ne jamais utiliser une assertion pour contrôler les préconditions d’une méthode publique!

La syntaxe:

   assert condition : object ;

Permet à l'AssertionError de présenter l’objet (via toString()).

Les assertions ne doivent pas être utilisée pour controler des préconditions dans l’A.P.I. (elles peuvent être désactivées). Elles peuvent être utiliser pour tester des conditions illégales graves dans des phases de tests: déploiement incorrect, tests durant le développement ou la maintenance, préconditions dans des codes non publics/protected, …. (Toutefois il est possible de déclencher directement une AssertionError si on détecte une condition qui rend l’exécution inacceptable).

package org.smurf ;

public class PackageChecks {
   // voici un bloc statique! exécuté au chargement de la classe
   static {
      assert PackageChecks.class.getPackage().isSealed() : " deployment problem; package org.smurf should be sealed " ;
   }
   // autres codes
}

Les assertions peuvent être activées pour seulement un package ou même seulement une classe (elle peuvent aussi être activées pour les librairies standard).

L’exécution d’une assertion ne doit avoir aucun effet de bord …. mais on peut les utiliser pour des codes transitoires sans effets de bord!

public class Traceur {
   public static boolean trace(String message) {
      System.out.println(message) ;
      return true ;
   }
}

Utilisation:

   assert Traceur.trace("je suis passé par là!") ;

La trace ne sera exécutée que si les options activant les assertions sont présentes.