Les exceptions
Qu’est-ce qu’une exception ?
Dans les précédents chapitres, plusieurs programmes ont crashé. Ces crashs se sont caractérisés par un arrêt prématuré du programme et l’affichage de lignes en rouge dans le terminal.
Ces crashs sont provoqués par ce qu’on appelle une exception. Les exceptions font partie intégrante de la vie d’un programme et d’un développeur. Il est très important de ne pas en avoir peur et de composer avec elles.
1. Analyser une stacktrace
Exécutons le programme suivant :
fun main()
{
val result = 12 / 0
println(result)
}
Jetons un coup d’œil sur le terminal :
Exception in thread "main" java.lang.ArithmeticException: / by zero
at MainKt.main(Main.kt:3)
at MainKt.main(Main.kt)
Ces lignes rouges forment ce qu’on appelle une stacktrace.
Les stacktraces sont générées dès qu’une exception se produit dans un programme. Et les exceptions se produisent dès qu’un programme détecte un comportement anormal.
Il existe des milliers de raisons qui peuvent provoquer des exceptions lors de l’exécution d’un programme. Par exemple, une division par 0, la manipulation d’une variable ou d’un objet non initialisé...
Capturer une ou plusieurs exceptions
1. Capturer une exception
Comme on l’a vu, certaines exceptions sont récupérables, c’est-à-dire qu’elles ne devraient pas provoquer l’arrêt du programme. Plutôt que de planter, le programme compose avec l’exception et avertit l’utilisateur que l’opération demandée n’a pas pu aboutir.
Reprenons le programme qui effectue une division par 0 :
fun main()
{
val result = 12 / 0
println(result)
}
Pour capturer une exception dans un programme, il convient d’utiliser ce que l’on appelle un bloc try catch.
Qu’est-ce exactement ?
Comme son nom le laisse deviner, ce bloc permet d’essayer d’exécuter une instruction et de capturer une potentielle exception si jamais tout ne se passe pas comme prévu.
Voici la syntaxe de ce bloc, elle est relativement simple :
try
{
//...
}
catch (exception: Exception)
{
//...
}
À l’intérieur du bloc try, on écrit l’ensemble des instructions qui peuvent provoquer une exception. Au niveau du bloc catch, on renseigne le type de l’exception que l’on souhaite capturer. Toujours dans ce bloc, il est alors possible de manipuler l’exception à travers une variable afin d’obtenir les informations qui composent, par exemple la stacktrace ou encore un message.
Dans le programme effectuant une division par 0, on souhaite capturer une exception de type ArithmeticException.
fun main()
{
println("Starting the program")
try
{
val result = 12 / 0
println("The result is $result")
} ...
Lever des exceptions
1. Quand lever une exception ?
Le but d’une exception est d’indiquer que le programme ne s’est pas déroulé normalement. Il n’y a pas forcément de règles sur la façon dont un programme doit lever des exceptions.
Prenons l’exemple d’une fonction qui accepte deux nombres entiers strictement positifs en arguments, qui les additionne et qui renvoie le résultat. Puisque cette méthode attend des nombres strictement positifs en arguments, elle doit absolument vérifier que les arguments sont supérieurs à 0. Quand l’un des deux arguments est négatif ou nul, on trouve, suivant les programmeurs, plusieurs logiques. Certains lèveront une exception pour indiquer que l’un des paramètres ne remplit pas tous les prérequis, tandis que d’autres ne lèverent pas une exception, mais renvoient par exemple un résultat null ou négatif pour indiquer qu’une erreur s’est produite.
En programmation, les exceptions font souvent peur aux développeurs qui préfèrent éviter leur utilisation plutôt que de composer avec elles et de les utiliser dans leurs programmes.
2. Utiliser le mot-clé throw
Lever une exception est très simple puisqu’il suffit d’utiliser le mot-clé throw suivi d’une instance de l’exception à lever.
Revenons à la fonction qui accepte deux nombres entiers strictement positifs en arguments, qui les additionne et qui renvoie le résultat. Avant de procéder au calcul, cette fonction doit vérifier que les arguments sont strictement positifs. Si ce n’est pas le cas, une exception doit être levée.
Quand on souhaite lever une exception pour indiquer qu’un élément passé en argument d’une fonction ne correspond pas à ce qui est attendu, il convient de lever une exception de type IllegalArgumentException.
fun add(a: Int, b: Int): Int
{
if (a <= 0)
{
throw IllegalArgumentException("a <= 0")
}
if (b <= 0)
{
throw IllegalArgumentException("b <= 0")
}
return a + b
}
La chaîne...
Créer des exceptions personnalisées
Outre capturer ou lever des exceptions de types proposés dans le langage Kotlin, il est possible de créer des exceptions personnalisées propres à nos programmes.
C’est extrêmement simple dès lors que le principe de l’héritage est compris. En effet, pour créer une exception personnalisée, il convient généralement de créer une classe qui hérite de la classe Exception.
Reprenons la classe Dog :
data class Dog(val name: String)
{
fun countNameChars(): Int
{
check(name.isNotEmpty()) {"The name cannot be empty"}
return name.length
}
}
Nous allons faire évoluer cette classe pour remplacer l’utilisation de la fonction check par une condition qui lèvera une exception personnalisée si le nom du chien est vide.
En ce sens, créons une classe InvalidDogNameException qui hérite de la classe Exception et qui exploite le constructeur prenant un message comme argument :
class InvalidDogNameException(message: String)
: Exception(message)
Quand on crée une classe qui représente une exception, il convient généralement que son nom se termine par le mot Exception. Si l’on regarde les exceptions proposées...
En résumé
-
Lorsqu’un programme rencontre un problème, il peut lever une exception.
-
Une exception non capturée fait planter le programme et affiche généralement une stacktrace qui permet de comprendre le type de l’exception et la ligne de code où le problème est survenu.
-
Il est possible de capturer une exception à l’aide des blocs try et catch.
-
Il est possible de capturer plusieurs exceptions à l’aide de plusieurs blocs catch.
-
Il est possible d’exécuter du code systématiquement à la suite des blocs try et catch en définissant un troisième bloc finally.
-
Pour lever une exception, il convient d’utiliser le mot-clé throw suivi de l’instanciation d’une classe représentant une exception.
-
Les fonctions require et requireNotNull permettent de faire des vérifications sur les arguments d’une fonction et éventuellement de lever une exception de type IllegalArgumentException.
-
Les fonctions check et checkNotNull permettent de faire des vérifications sur les attributs ou des variables manipulées dans une fonction et éventuellement de lever une exception de type IllegalArgumentException.
-
Il est possible de créer des exceptions personnalisées en créant une classe qui hérite de la classe Exception.