Vous savez comment créer des erreurs, voyons maintenant comment les gérer !
Découvrez le mot-clé try
Nous allons enfin parler de ce fameux mot-clé try
que nous avons croisé plusieurs fois dans ce cours.
Copiez le code suivant dans votre Playground :
func payFruits() {
let order = Order()
order.items = [
Item(price: 2.40, description: "Melon"),
Item(price: 4, description: "Fraises"),
Item(price: 1.20, description: "Pomme")
]
let cb = PaymentMethod(isValid: true, maxAmount: 100)
let price = order.pay(with: cb)
print("Votre commande d'un montant de \(price)€ a bien été prise en compte.")
}
Vous devriez avoir une erreur du type :
Call can throw, but it is not marked with 'try' and the error is not handled.
Ce message signifie que l'appel de la fonction pay
peut lancer une erreur, mais que cette erreur n'est pas gérée.
En Swift, lorsqu'on fait appel à une fonction qui peut lancer une erreur, une fonction qui a donc la mention throws
dans sa déclaration, on est obligé de prendre des précautions.
Et ces précautions se traduisent concrètement par l'ajout du mot-clé try
avant l'appel de la fonction.
let price = try order.pay(with: cb)
try signifie essayer , en anglais. Donc l'idée, c'est de dire : "Essaie d'exécuter cette fonction. Je sais que tu ne vas peut-être pas y arriver, car cette fonction peut renvoyer une erreur".
OK je veux bien, mais en attendant, j'ai toujours une erreur dans la console :
Errors thrown from here are not handled.
C'est normal ! Il ne suffit pas d'écrire le mot-clé try
, il faut aussi gérer l'erreur, et il y a deux façons de le faire.
Gérez l'erreur avec do-catch
Pour gérer proprement les erreurs, il faut entourer l'appel de la fonction par l'instruction do-catch comme ceci :
do {
let price = try order.pay(with: cb)
print("Votre commande d'un montant de \(price)€ a bien été prise en compte.")
} catch {
}
Dans la partie do
, j'essaie d'exécuter ma fonction. Et si jamais elle renvoie une erreur, je vais gérer ça dans la partie catch
.
Voyons d'ailleurs comment on gère les erreurs avec catch
. Tout d'abord, nous allons récupérer une instance de l'erreur en contrôlant en même temps son type :
do {
let price = try order.pay(with: cb)
print("Votre commande d'un montant de \(price)€ a bien été prise en compte.")
} catch let error as Order.OrderError {
}
J'ai un peu l'impression que le prof est à la ramasse, j'ai encore un problème dans la console !
Errors thrown from here are not handled because the enclosing catch is not exhaustive.
D'une part, je trouve ça pas très agréable, et d'autre part, c'est encore normal ! Le catch doit être exhaustif. Or, il se peut que dans votre méthode pay
, vous lanciez d'autres types d'erreurs que des OrderError
. Il faut rajouter un bloc catch générique pour gérer les autres erreurs potentielles.
do {
let price = try order.pay(with: cb)
print("Votre commande d'un montant de \(price)€ a bien été prise en compte.")
} catch let error as Order.OrderError {
} catch {
print("Oups ! Quelque chose a dû mal se passer...")
}
On va maintenant gérer l'erreur dans le premier catch
. OrderError
est une énumération, donc nous allons gérer l'erreur avec un switch
.
do {
let price = try order.pay(with: cb)
print("Votre commande d'un montant de \(price)€ a bien été prise en compte.")
} catch let error as Order.OrderError {
switch error {
case .orderAlreadyPayed:
print("Vous avez déjà réglé cette commande.")
case .orderIsEmpty:
print("Votre panier est vide.")
case .invalidPaymentMethod:
print("Votre méthode de paiement est invalide.")
case .insufficientFundings:
print("Votre méthode de paiement a été refusée. Contrôlez vos plafonds.")
}
} catch {
print("Oups ! Quelque chose a dû mal se passer...")
}
Et voilà une très belle gestion d'erreur ! Votre utilisateur sait maintenant très clairement pourquoi sa commande n'a pas été passée, ce qui lui évite beaucoup de frustrations !
Certes, mais du coup ça fait beaucoup de code à rédiger !
Il ne faut pas être fainéant quand il s'agit de la gestion des erreurs ! Néanmoins je suis d'accord que parfois, on n'a pas besoin de donner autant de détails.
Dans ces cas-là, il existe une méthode plus rapide.
try! et try?
Comme pour le déballage des optionnels ou le contrôle des types, on peut accoler les ponctuations ! et ? au mot-clé try . Voyons d'abord le point d'exclamation :
let price = try! order.pay(with: cb)
print("Votre commande d'un montant de \(price)€ a bien été prise en compte.")
C'est pratique, mais risqué, car s'il y a malgré tout une erreur, votre code va planter. Donc c'est à n'utiliser que si vous êtes absolument sûr de vous, ou si vous souhaitez que votre code plante si jamais il y a une erreur.
Une autre façon, plus sûre, est d'utiliser le point d'interrogation :
let price = try? order.pay(with: cb)
Lorsqu'on utilise try?
, le code ne plante plus en cas d'erreur. Pour y arriver, il utilise les optionnels. En effet, avec le code ci-dessus, la fonction pay ne renvoie plus un Double
, mais un optionnel.
La constante price
est donc de type optionnel. Du coup, je peux la déballer comme je le fais avec n'importe quel optionnel :
if let price = try? order.pay(with: cb) {
print("Votre commande d'un montant de \(price)€ a bien été prise en compte.")
} else {
print("Oups ! Quelque chose a dû mal se passer...")
}
C'est certes moins précis qu'avec do-catch
, mais c'est un intermédiaire qui permet de faire un minimum de gestion d'erreur à moindre frais.
Identifiez qui utiliser et quand
Dès que vous avez le choix, se pose la question de que faire à quel moment. À vous de définir ou de trouver vos propres règles, mais voici ce que je vous propose :
Si votre logique ou votre expérience utilisateur dépend de la nature de l'erreur, utilisez try
avec do-catch
et un switch
. Dans les autres cas, utilisez try?
.
Ne réservez l'utilisation de try!
qu'à de rares exceptions.
En résumé
L'appel d'une fonction marquée avec
throws
doit être précédé detry
.try
ne peut pas être utilisé seul. Il a besoin :soit d'être entouré d'une instruction
do-catch
qui permet de gérer les erreurs précisément ;soit d'être suivi du
?
qui renvoie le retour de la fonction sous forme d'optionnel ;soit d'être suivi du
!
qui exécute la fonction quoi qu'il arrive, au risque de faire planter le code s'il rencontre une erreur.
Félicitations ! Ce cours n'était pas facile ! Vous verrez avec la pratique que la gestion des réseaux n'est pas la partie la plus évidente du travail de développeur iOS. Pourtant, vous venez de faire un grand pas vers la maîtrise de ce sujet !