CodeGym /Cours /Kotlin SELF /when avec enum — branches exhaustives et when comme expre...

when avec enum — branches exhaustives et when comme expression

Kotlin SELF
Niveau 34, Leçon 1
Disponible

1. Introduction

Quand vous commencez a utiliser enum dans du code reel, une question tres pratique apparait rapidement : « OK, j’ai ExpenseCategory.FOOD, et qu’est-ce que j’en fais ensuite ? » Afficher un joli libelle ? Choisir une limite ? Proposer une indication a l’utilisateur ? Mettre une « icone » en texte ? Et c’est la que les gens finissent souvent par glisser vers des if ou des « chaines magiques ». when est justement l’outil qui transforme le traitement des variantes d’un enum en une table de correspondance claire.

Imaginez que enum soit un ensemble de boutons sur une telecommande : il y a peu de boutons, ils sont etiquetes, et leur liste est connue a l’avance. when est votre instruction : « si tel bouton est presse, fais ceci ». Et si vous ajoutez plus tard un nouveau bouton, Kotlin peut vous rappeler : « He, tu n’as pas ecrit l’instruction pour le nouveau bouton. »

2. Rappel sur when : instruction et expression

En theorie, la difference sonne un peu pedante : une instruction « execute juste du code », une expression « renvoie une valeur ». Mais en pratique, cela determine litteralement a quel point votre code sera court et lisible. Quand when est utilise comme expression, on se debarrasse souvent des variables temporaires et des return repetes. Le code ressemble a un petit tableau propre : « voici l’entree → voici la sortie ».

En Kotlin, when peut s’ecrire des deux facons. Et le plus agreable : l’expression when peut etre affectee a une variable ou renvoyee par une fonction. C’est tres naturel justement pour enum : en entree, une des variantes fixes ; en sortie, par exemple, un texte pour l’interface ou un nombre pour un calcul.

Mini-exemple : when comme instruction

enum class Status { NEW, IN_PROGRESS, DONE }

fun printStatus(status: Status) {
    when (status) {
        Status.NEW -> println("Cree")              // Cree
        Status.IN_PROGRESS -> println("En cours")     // En cours
        Status.DONE -> println("Termine")              // Termine
    }
}

Ici, when se contente d’executer println() dans la bonne branche.

Mini-exemple : when comme expression

enum class Status { NEW, IN_PROGRESS, DONE }

fun statusLabel(status: Status): String =
    when (status) {
        Status.NEW -> "Cree"
        Status.IN_PROGRESS -> "En cours"
        Status.DONE -> "Termine"
    }

Remarquez : les branches renvoient des chaines, et tout le when renvoie une chaine. Pas de var label = ... ni trois return.

3. when exhaustif sur un enum

Voici l’un des moments les plus agreables en Kotlin : quand when travaille avec un enum, le compilateur connait la liste complete des variantes. Il peut donc verifier que vous avez traite chaque variante. On appelle cela un when exhaustif (exhaustive).

Psychologiquement, on a l’impression de « ne pas avoir oublie un cas ». Techniquement, c’est une protection contre les evolutions futures. Si vous ajoutez une nouvelle entree a l’enum, Kotlin mettra en evidence les endroits ou le traitement n’est plus complet.

Faisons un petit extrait pour notre application console d’entrainement (disons « suivi des depenses »). Supposons que nous ayons des categories de depenses :

enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }

Et nous voulons afficher joliment le nom de la categorie en francais.

enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }

fun categoryTitle(category: ExpenseCategory): String =
    when (category) {
        ExpenseCategory.FOOD -> "Nourriture"
        ExpenseCategory.TRANSPORT -> "Transport"
        ExpenseCategory.HOME -> "Maison"
        ExpenseCategory.FUN -> "Divertissements"
    }

Ici, il n’y a pas de else — et c’est une bonne chose. Pourquoi ? Parce que si demain vous ajoutez HEALTH, le compilateur dira : « Ah, la fonction categoryTitle() ne couvre plus toutes les variantes. » Et vous corrigerez cela non pas « un jour plus tard », mais tout de suite.

Pour sentir l’idee, on peut imaginer un when sur un enum comme un formulaire de rapport strict : tant que vous n’avez pas rempli toutes les lignes, le rapport n’est pas accepte.

4. Pourquoi else dans un when sur enum est souvent nuisible

Parfois, on est tente d’ecrire :

else -> "Inconnu"

Le probleme, c’est que pour un enum, « inconnu » signifie le plus souvent : « j’ai ajoute une nouvelle variante, et l’ancien code a avale ce changement en silence et a fait quelque chose d’etrange ». Oui, le code compile. Mais le prix, c’est la perte de la verification automatique de l’exhaustivite.

Il existe des situations ou else reste pertinent, par exemple si vous ne voulez volontairement pas distinguer les variantes et que vous definissez un comportement “default”. Mais pour une logique de « table de correspondance », il vaut presque toujours mieux tout enumerer explicitement.

Pour rendre la comparaison plus parlante, voici un petit tableau :

Approche Ce que l’on gagne Ce que l’on perd
when sur enum sans else Le compilateur verifie l’exhaustivite, plus facile a maintenir Il faut vraiment traiter toutes les variantes
when sur enum avec else Plus rapide a « poser », parfois pratique pour un prototype Le compilateur n’aide plus lorsque l’enum s’etend

5. Type du resultat d’une expression when

Quand when est une expression, Kotlin doit comprendre quel type elle renvoie. En general, c’est simple : toutes les branches renvoient un String — donc when renvoie un String. Mais parfois, les debutants sont surpris quand les branches renvoient des types differents, et que le compilateur commence a « chercher un type commun ».

Pour rester simple, retenez la regle : toutes les branches d’une expression when doivent renvoyer des valeurs du meme type (ou au moins compatibles). Pour un enum, c’est presque toujours naturel : vous produisez soit des chaines, soit des nombres, soit des objets bien precis.

Exemple : when renvoie un nombre

enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }

fun defaultLimit(category: ExpenseCategory): Int =
    when (category) {
        ExpenseCategory.FOOD -> 1500
        ExpenseCategory.TRANSPORT -> 800
        ExpenseCategory.HOME -> 2000
        ExpenseCategory.FUN -> 1000
    }

Exemple : le type commun devient trop general

enum class ExpenseCategory { FOOD, TRANSPORT }

fun weird(category: ExpenseCategory) =
    when (category) {
        ExpenseCategory.FOOD -> "nourriture"
        ExpenseCategory.TRANSPORT -> 123
    }

On peut ecrire cela, et Kotlin choisira un type commun (probablement Any). Mais ensuite, vous vous compliquez la vie : au lieu d’un String ou d’un Int clair, vous obtenez un Any, et il faudra comprendre ce qu’il contient. Dans un vrai projet, un when comme celui-ci est presque toujours un signal : « arrete, tu as melange deux taches differentes. »

6. Enum nullable (E?) : ajouter une branche null

Dans les applications, il arrive souvent qu’« une valeur ne soit pas encore choisie ». Par exemple, une categorie de depense n’est pas indiquee parce que l’utilisateur n’a saisi que le montant. Dans ce cas, le type peut etre ExpenseCategory?.

Et il y a une regle importante ici : si vous faites un when sur un enum nullable et que vous voulez un when exhaustif, il faut traiter null dans une branche separee.

enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }

fun categoryTitleOrUnknown(category: ExpenseCategory?): String =
    when (category) {
        ExpenseCategory.FOOD -> "Nourriture"
        ExpenseCategory.TRANSPORT -> "Transport"
        ExpenseCategory.HOME -> "Maison"
        ExpenseCategory.FUN -> "Divertissements"
        null -> "Sans categorie"
    }

Cela parait un peu plus long, mais c’est honnete. Et c’est tres transparent a la lecture : « si null, alors pas de categorie ».

7. Exemple : when sur enum dans une application console

Assemblons maintenant un mini-morceau de logique qu’on rencontre reellement dans les programmes CLI : les commandes utilisateur. Avant, on pouvait comparer des chaines ("add", "list", "exit"), mais les chaines sont faciles a mal taper et se refactorent mal. Un enum rend la liste des commandes explicite.

Declarons l’enum des commandes

enum class Command { ADD, LIST, HELP, EXIT }

when comme expression : produire une aide

enum class Command { ADD, LIST, HELP, EXIT }

fun commandHelp(command: Command): String =
    when (command) {
        Command.ADD -> "add — ajouter une depense"
        Command.LIST -> "list — afficher toutes les depenses"
        Command.HELP -> "help — afficher l’aide"
        Command.EXIT -> "exit — quitter le programme"
    }

C’est le cas classique de « table de correspondance » : enum → texte.

when comme instruction : executer une action

Pour l’instant, gardons des actions simples (on ne complique pas l’application ici, on montre seulement le style).

enum class Command { ADD, LIST, HELP, EXIT }

fun runCommand(command: Command) {
    when (command) {
        Command.ADD -> println("Ajout...")   // Ajout...
        Command.LIST -> println("Liste...")      // Liste...
        Command.HELP -> println("Aide...")     // Aide...
        Command.EXIT -> println("Sortie...")       // Sortie...
    }
}

Notez : c’est aussi exhaustif, et else n’est pas necessaire.

Comment une expression when elimine les duplications

Un probleme typique des debutants est de repeter la meme chose dans chaque branche. Par exemple, vous voulez afficher le resultat de la meme facon : println(result). Il est alors pratique de recuperer d’abord une chaine depuis when, puis de l’afficher une seule fois.

enum class Command { ADD, LIST, HELP, EXIT }

fun runCommandMessage(command: Command): String =
    when (command) {
        Command.ADD -> "OK, on ajoute une depense"
        Command.LIST -> "J’affiche la liste des depenses"
        Command.HELP -> "Voici l’aide sur les commandes"
        Command.EXIT -> "A bientot ! (mais le code tourne encore)"
    }

fun main() {
    val cmd = Command.HELP
    println(runCommandMessage(cmd)) // Voici l’aide sur les commandes
}

Ici, vous avez clairement separe : when choisit le texte, et println effectue l’affichage. En plus, c’est plus facile a tester mentalement.

Petit schema : when sur enum comme embranchement controle

Il est parfois utile de voir que when sur un enum est litteralement une bifurcation avec des routes fixes. Voici le diagramme de ce que fait une expression when (au niveau de l’idee) :

flowchart TD
    A[Obtention d’une valeur enum] --> B{"when(enum)"}
    B -->|Variante 1| C[Resultat/action #1]
    B -->|Variante 2| D[Resultat/action #2]
    B -->|Variante 3| E[Resultat/action #3]
    B -->|Variante N| F[Resultat/action #N]

Si l’enum a un ensemble fixe de variantes, alors le compilateur peut verifier que toutes les « routes » existent. Et si vous ajoutez plus tard une nouvelle route, le compilateur exigera d’ajouter aussi son traitement — pour que l’utilisateur ne parte pas dans le decor.

8. Erreurs typiques avec when et enum

Erreur n°1 : ajouter else « au cas ou » et perdre la verification d’exhaustivite.
Au debut, on a l’impression que else est une assurance. Mais pour un enum, cela devient souvent un piege de maintenance : vous etendez l’enum, et l’ancien code continue a compiler tout en se comportant differemment de ce qui etait prevu. Si vous voulez vraiment un “default”, il vaut mieux le nommer clairement : parfois c’est une variante d’enum a part entiere (par exemple UNKNOWN), plutot qu’un else silencieux.

Erreur n°2 : utiliser when comme expression, mais renvoyer des types differents selon les branches.
Kotlin choisira un type commun, et vous obtiendrez soudain Any, apres quoi commenceront des verifications et des conversions de types bizarres. Dans des exercices, cela ressemble a « ca marche pourtant », mais dans du code reel cela devient un contrat confus : on ne comprend pas ce que la fonction renvoie, ni comment l’utiliser.

Erreur n°3 : ecrire trop de code a l’interieur des branches de when.
when se lit tres bien quand les branches sont courtes : renvoyer une chaine, renvoyer un nombre, appeler une fonction claire. Quand, a l’interieur d’une branche, il y a cinq println, une boucle et encore un when, le cerveau demande des vacances. Bon style : when choisit quoi faire, et la logique complexe vit dans des fonctions separees.

Erreur n°4 : oublier null quand l’enum est nullable (E?).
Si vous passez au type ExpenseCategory? et que, dans when, vous n’avez pas prevu null, alors soit il faudra ecrire else, soit vous obtiendrez une expression when non exhaustive (et le compilateur commencera a se plaindre si vous voulez renvoyer une valeur). En Kotlin, null est une variante au meme titre que les autres, sauf qu’elle necessite une branche dediee.

Erreur n°5 : melanger « choix d’une valeur » et « affichage a l’ecran » dans chaque branche.
Souvent, les debutants font ainsi : dans chaque branche du when, ils affichent quelque chose, puis ils affichent encore une partie commune. Au final, la duplication grossit. En general, il est plus simple de faire de when une expression, de recuperer une chaine, puis d’appeler println une seule fois. Le code devient alors lineaire et previsible.

1
Mission
Kotlin SELF, niveau 34, leçon 1
Bloqué
Badge de commande
Badge de commande
1
Mission
Kotlin SELF, niveau 34, leçon 1
Bloqué
Limite d'appareils
Limite d'appareils
1
Mission
Kotlin SELF, niveau 34, leçon 1
Bloqué
Livraison non sélectionnée
Livraison non sélectionnée
1
Mission
Kotlin SELF, niveau 34, leçon 1
Bloqué
Emploi du temps scolaire
Emploi du temps scolaire
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION