La façon classique de programmer est avec try ... catch
. Quand est-il approprié d'utiliser try
sans catch
?
En Python, ce qui suit semble légal et peut avoir un sens:
try:
#do work
finally:
#do something unconditional
Cependant, le code n'a catch
rien. De même, on pourrait penser en Java que:
try {
//for example try to get a database connection
}
finally {
//closeConnection(connection)
}
Ça a l'air bien et du coup je n'ai plus à m'inquiéter des types d'exceptions, etc. Si c'est une bonne pratique, quand l'est-il? Sinon, quelles sont les raisons pour lesquelles ce n'est pas une bonne pratique ou n'est pas légal? (Je n'ai pas compilé le source. Je pose la question car cela pourrait être une erreur de syntaxe pour Java. J'ai vérifié que le Python compile sûrement.)
Un problème connexe que j'ai rencontré est le suivant: je continue à écrire la fonction / méthode, à la fin de laquelle il doit renvoyer quelque chose. Cependant, il se peut que ce soit dans un endroit qui ne devrait pas être atteint et qui doit être un point de retour. Ainsi, même si je gère les exceptions ci-dessus, je renvoie toujours NULL
une chaîne vide dans le code qui ne doit pas être atteint, souvent la fin de la méthode / fonction. J'ai toujours réussi à restructurer le code afin qu'il n'en soit pas obligé return NULL
, car cela ne semble absolument pas être une bonne pratique.
la source
try/catch
n'est pas "la manière classique de programmer." C'est la manière classique de programmer en C ++ , car le C ++ ne dispose pas d'une construction try / finally appropriée, ce qui signifie que vous devez implémenter des changements d'état réversibles garantis à l'aide de piratages laids impliquant RAII. Mais les langues OO décentes ne rencontrent pas ce problème, car elles fournissent try / finally. Il est utilisé dans un but très différent de try / catch.Réponses:
Cela dépend si vous pouvez traiter des exceptions qui peuvent être soulevées à ce stade ou non.
Si vous pouvez gérer les exceptions localement, vous devriez le faire, et il est préférable de gérer l'erreur aussi près que possible de l'endroit où elle est levée.
Si vous ne pouvez pas les gérer localement, le simple fait de
try / finally
bloquer est tout à fait raisonnable - en supposant qu'il y ait du code que vous devez exécuter, que la méthode ait réussi ou non. Par exemple (du commentaire de Neil ), ouvrir un flux, puis passer ce flux à une méthode interne à charger est un excellent exemple du moment où vous auriez besointry { } finally { }
, en utilisant la clause finally pour vous assurer que le flux est fermé indépendamment du succès ou de la perte. échec de la lecture.Cependant, vous aurez toujours besoin d'un gestionnaire d'exceptions quelque part dans votre code - à moins que vous ne souhaitiez que votre application plante complètement, bien sûr. Cela dépend de l'architecture de votre application et de l'emplacement exact de ce gestionnaire.
la source
try { } finally { }
. Profitez de la clause finally pour vous assurer que le flux est finalement fermé, quel que soit le succès / échec.Le
finally
bloc est utilisé pour le code qui doit toujours être exécuté, qu’une condition d’erreur (exception) se soit produite ou non.Le code du
finally
bloc est exécuté après la fin dutry
bloc et, si une exception interceptée se produit, après la fin ducatch
bloc correspondant . Il est toujours exécuté, même si une exception non interceptée s'est produite dans le bloctry
oucatch
.Le
finally
bloc est généralement utilisé pour la fermeture de fichiers, de connexions réseau, etc. ouverts dans letry
bloc. La raison en est que la connexion de fichier ou de réseau doit être fermée, que l'opération utilisant cette connexion de fichier ou de réseau ait abouti ou non.Il faut veiller à ce que le
finally
bloc ne lance pas une exception. Par exemple, assurez-vous de vérifier toutes les variables pournull
, etc.la source
try-finally
peuvent être remplacées par unewith
déclaration.try/finally
construction. C # ausing
, Python awith
, etc.using
ettry-finally
, car laDispose
méthode ne sera pas appelée par leusing
bloc si une exception se produit dans leIDisposable
constructeur de l' objet.try-finally
vous permet d'exécuter du code même si le constructeur de l'objet lève une exception.Un exemple où essayer ... enfin sans clause catch est approprié (et encore plus idiomatique ) en Java est l'utilisation de Lock dans le package de verrous d'utilitaires simultanés.
la source
l.lock()
intérieur essayer?try{ l.lock(); }finally{l.unlock();}
try
dans cet extrait est destiné à envelopper l'accès aux ressources, pourquoi le polluer avec quelque chose quil.lock()
échec, lefinally
bloc sera toujours exécuté s'il sel.lock()
trouve à l'intérieur dutry
bloc. Si vous le faites comme le suggère gnat, lefinally
blocage ne fonctionnera que si nous savons que le verrou a été acquis.Au niveau de base
catch
etfinally
résoudre deux problèmes liés mais différents:catch
est utilisé pour gérer un problème signalé par le code que vous avez appeléfinally
est utilisé pour nettoyer les données / ressources créées / modifiées par le code actuel, peu importe si un problème survient ou nonDonc, les deux sont liés d'une manière ou d'une autre à des problèmes (exceptions), mais c'est à peu près tout ce qu'ils ont en commun.
Une différence importante est que le
finally
bloc doit être dans la même méthode où les ressources ont été créées (pour éviter les fuites de ressources) et ne peut pas être placé à un niveau différent dans la pile d'appels.Le
catch
est cependant une autre affaire: le bon endroit pour cela dépend où vous pouvez réellement gérer l'exception. Il est inutile de capturer une exception dans un endroit où vous ne pouvez rien y faire. Il est donc parfois préférable de la laisser simplement tomber.la source
finally
une instruction try englobante (de manière statique ou dynamique) ... tout en restant étanche à 100%.@yfeldblum a la bonne réponse: essayer-enfin sans instruction catch devrait généralement être remplacé par une construction de langage appropriée.
En C ++, il utilise RAII et les constructeurs / destructeurs; en Python c'est une
with
déclaration; et en C #, c'est uneusing
déclaration.Celles-ci sont presque toujours plus élégantes parce que les codes d'initialisation et de finalisation se trouvent à un seul endroit (l'objet abstrait) plutôt qu'à deux endroits.
la source
Dans de nombreuses langues, une
finally
instruction est également exécutée après l'instruction return. Cela signifie que vous pouvez faire quelque chose comme:Qui libère les ressources, quelle que soit la manière dont la méthode a été terminée, avec une exception ou une instruction return classique.
Que cela soit bon ou mauvais fait l’objet d’un débat, mais
try {} finally {}
ne se limite pas toujours à la gestion des exceptions.la source
Je pourrais invoquer la colère des pythonistes (je ne sais pas car je n’utilise pas beaucoup Python) ou des programmeurs d’autres langages avec cette réponse, mais à mon avis, la plupart des fonctions ne devraient pas être
catch
bloquées, idéalement. Pour montrer pourquoi, permettez-moi de mettre cela en contraste avec la propagation manuelle du code d’erreur du type que j’avais dû faire lorsque je travaillais avec Turbo C à la fin des années 80 et au début des années 90.Supposons donc que nous ayons une fonction pour charger une image ou quelque chose comme ça en réponse à un utilisateur sélectionnant un fichier image à charger, et ceci est écrit en C et en assembleur:
J'ai omis certaines fonctions de bas niveau, mais nous pouvons voir que j'ai identifié différentes catégories de fonctions, codées par couleur, en fonction de leurs responsabilités en ce qui concerne le traitement des erreurs.
Point de défaillance et de récupération
Maintenant, il n’a jamais été difficile d’écrire les catégories de fonctions que j’appelle le "point de défaillance possible" (celles qui
throw
, par exemple) et les fonctions "récupération et rapport d’erreur" (celles quicatch
, par exemple).Il était toujours facile d’écrire correctement avant la gestion des exceptions pour ces fonctions, puisqu’une fonction pouvant rencontrer un échec externe, comme l’absence d’allocation de mémoire, peut simplement renvoyer un
NULL
ou0
ou-1
ou définir un code d’erreur global ou quelque chose de similaire . Et la récupération / le signalement des erreurs était toujours facile car une fois que vous avez parcouru la pile d’appels jusqu’à un point où il était logique de récupérer et de signaler les échecs, il vous suffit de prendre le code et / ou le message d’erreur et de le signaler à l’utilisateur. Et naturellement, une fonction à la feuille de cette hiérarchie qui ne peut jamais, jamais échouer, peu importe la façon dont elle a été modifiée à l'avenir (Convert Pixel
) est extrêmement simple à écrire correctement (du moins en ce qui concerne la gestion des erreurs).Propagation d'erreur
Cependant, les fonctions fastidieuses sujettes aux erreurs humaines étaient les propagateurs d'erreurs , celles qui ne rencontraient pas directement l'échec mais appelaient des fonctions susceptibles d'échouer quelque part plus profondément dans la hiérarchie. À ce moment - là,
Allocate Scanline
pourrait avoir à gérer un échec demalloc
puis retourner une erreur àConvert Scanlines
, puisConvert Scanlines
devra vérifier cette erreur et le transmettre àDecompress Image
, puisDecompress Image->Parse Image
, etParse Image->Load Image
, etLoad Image
à la commande utilisateur final où l'erreur est finalement rapporté .C’est là que beaucoup d’êtres humains commettent des erreurs puisqu’il suffit d’un propagateur d’erreur pour vérifier et transmettre l’erreur pour que toute la hiérarchie des fonctions s’effondre lorsqu’il s’agit de gérer correctement l’erreur.
De plus, si des fonctions retournent des codes d’erreur, nous perdons la capacité, par exemple, de 90% de notre base de code, de renvoyer des valeurs d’intérêt en cas de succès, car de nombreuses fonctions devraient réserver leur valeur de retour pour renvoyer un code d’erreur sur échec .
Réduction des erreurs humaines: codes d'erreur globaux
Alors, comment pouvons-nous réduire le risque d'erreur humaine? Ici, je pourrais même invoquer la colère de certains programmeurs C, mais une amélioration immédiate à mon avis consiste à utiliser des codes d'erreur globaux , comme OpenGL avec
glGetError
. Cela libère au moins les fonctions pour renvoyer des valeurs d’intérêt significatives en cas de succès. Il existe des moyens de rendre ce thread-safe et efficace lorsque le code d'erreur est localisé dans un thread.Il existe également des cas où une fonction peut rencontrer une erreur mais il est relativement inoffensif de continuer un peu plus longtemps avant de revenir prématurément à la suite de la découverte d'une erreur précédente. Cela permet qu'une telle chose se produise sans qu'il soit nécessaire de vérifier les erreurs par rapport à 90% des appels de fonction effectués dans chaque fonction. Elle peut donc permettre une gestion correcte des erreurs sans être aussi minutieuse.
Réduire l'erreur humaine: traitement des exceptions
Cependant, la solution ci-dessus nécessite toujours autant de fonctions pour traiter l’aspect flux de contrôle de la propagation manuelle des erreurs, même si elle aurait pu réduire le nombre de lignes
if error happened, return error
de code de type manuel . Cela ne l'éliminerait pas complètement car il faudrait toujours au moins un endroit pour rechercher une erreur et revenir pour presque chaque fonction de propagation d'erreur. C'est donc à ce moment-là que la gestion des exceptions entre en scène pour sauver la journée (sorta).Mais l'intérêt de la gestion des exceptions ici est de libérer le besoin de traiter de l'aspect de flux de contrôle de la propagation d'erreur manuelle. Cela signifie que sa valeur est liée à la possibilité d’éviter d’écrire un grand nombre de
catch
blocs dans votre base de code. Dans le diagramme ci-dessus, le seul endroit qui devrait avoir uncatch
bloc est l'Load Image User Command
endroit où l'erreur est rapportée. Dans l'idéal, rien d'autre ne devrait avoircatch
quoi que ce soit car sinon, cela commence à devenir aussi fastidieux et aussi source d'erreurs que la gestion du code d'erreur.Donc, si vous me demandez si vous avez une base de code qui profite vraiment de la gestion des exceptions de manière élégante, elle devrait avoir un nombre minimum de
catch
blocs (minimum, je ne veux pas dire zéro, mais plutôt un pour chaque haute unique opération de l'utilisateur final qui pourrait échouer, voire même moins si toutes les opérations d'utilisateur supérieur sont appelées via un système de commande central).Nettoyage des ressources
Cependant, la gestion des exceptions ne résout que la nécessité d'éviter de traiter manuellement les aspects de la propagation des erreurs liés au flux de contrôle dans des chemins exceptionnels distincts des flux d'exécution normaux. Souvent, une fonction servant de propagateur d’erreur, même si elle le fait automatiquement maintenant avec EH, peut encore acquérir certaines ressources qu’elle doit détruire. Par exemple, une telle fonction peut ouvrir un fichier temporaire qu’elle doit fermer avant de quitter quoi que ce soit, ou verrouiller un mutex qu’elle doit déverrouiller quoi qu’il en soit.
Pour cela, je pourrais appeler la colère de beaucoup de programmeurs de toutes sortes de langages, mais je pense que l’approche C ++ est idéale. Le langage introduit des destructeurs qui sont invoqués de manière déterministe à la sortie d’un objet. Pour cette raison, le code C ++ qui, par exemple, verrouille un mutex via un objet mutex couvert avec un destructeur n'a pas besoin de le déverrouiller manuellement, car il sera automatiquement déverrouillé une fois que l'objet est hors de portée, peu importe ce qui se produit (même s'il s'agit d'une exception). rencontré). Il n’ya donc vraiment pas besoin d’un code C ++ bien écrit pour traiter le nettoyage des ressources locales.
Dans les langues dépourvues de destructeurs, il peut être nécessaire d'utiliser un
finally
bloc pour nettoyer manuellement les ressources locales. Cela dit, il vaut mieux battre son code avec la propagation manuelle des erreurs, à condition de ne pas avoir à faire d'catch
exception dans tous les cas.Inverser les effets secondaires externes
C'est le problème conceptuel le plus difficile à résoudre. Si une fonction, qu’il s’agisse d’un propagateur d’erreur ou d’un point d’échec, provoque des effets secondaires externes, elle doit alors annuler ou annuler ces effets secondaires pour rétablir le système dans un état identique à celui de l’opération, au lieu de " semi-valide "état où l'opération à mi-parcours a réussi. Je ne connais aucun langage qui rende ce problème conceptuel beaucoup plus facile, à l'exception des langages qui réduisent au minimum le besoin pour la plupart des fonctions de provoquer des effets secondaires externes, tels que les langages fonctionnels qui gravitent autour de l'immuabilité et des structures de données persistantes.
C’est
finally
l’une des solutions les plus élégantes au problème des langages axés sur la mutabilité et les effets secondaires, car ce type de logique est très spécifique à une fonction particulière et ne correspond pas si bien au concept de «nettoyage des ressources». ". Et je vous recommande d'utiliserfinally
libéralement dans ces cas-là pour vous assurer que votre fonction annule les effets secondaires dans les langues qui le supportent, que vous ayez besoin d'uncatch
bloc ou non (et encore, si vous me demandez, le code bien écrit devrait avoir le nombre minimum decatch
blocs, et tous lescatch
blocs doivent être à des endroits où cela a le plus de sens comme dans le diagramme ci-dessusLoad Image User Command
).Langue de rêve
Cependant, IMO
finally
est proche de l’idéal pour le renversement des effets secondaires mais pas tout à fait. Nous devons introduire uneboolean
variable pour annuler efficacement les effets secondaires dans le cas d'une sortie prématurée (d'une exception levée ou autre), comme suit:Si je pouvais un jour concevoir un langage, la méthode de rêve pour résoudre ce problème serait d'automatiser le code ci-dessus:
... avec des destructeurs pour automatiser le nettoyage des ressources locales, nous en avons donc seulement besoin
transaction
,rollback
etcatch
(bien que je puisse encore vouloir ajouterfinally
pour, par exemple, travailler avec des ressources C qui ne se nettoient pas elles-mêmes). Cependant,finally
avec uneboolean
variable est la chose la plus proche de rendre cela simple que j'ai trouvé jusqu'à présent manquant de la langue de mes rêves. La deuxième solution la plus simple que j'ai trouvée pour cela est celle des gardes de la portée dans des langages tels que C ++ et D, mais j'ai toujours trouvé les gardes de la portée un peu gênants sur le plan conceptuel, car cela brouille l'idée de "nettoyage des ressources" et de "retournement des effets secondaires". À mon avis, ce sont des idées très distinctes qui doivent être abordées différemment.Mon petit rêve de langage s’articulerait également autour de l’immuabilité et des structures de données persistantes, ce qui faciliterait beaucoup, bien que cela ne soit pas nécessaire, l’écriture de fonctions efficaces sans avoir à copier en profondeur d’immenses structures de données dans leur intégralité, même si la fonction pas d'effets secondaires.
Conclusion
Quoi qu'il en soit, à part mes divagations, je pense que votre
try/finally
code pour fermer le socket est bon et bon, vu que Python n'a pas l'équivalent C ++ des destructeurs, et je pense personnellement que vous devriez l'utiliser de manière libérale pour les endroits qui doivent inverser leurs effets secondaires. et minimisez le nombre d'endroits où vous devez vous rendrecatch
à des endroits où cela semble le plus logique.la source
Il est vivement recommandé de corriger les erreurs / exceptions et de les traiter de manière soignée, même si cela n’est pas obligatoire.
La raison pour laquelle je dis cela est parce que je crois que chaque développeur doit connaître le comportement de sa candidature et s'y attaquer, sinon il n'a pas terminé son travail correctement. Il n'y a pas de situation pour laquelle un bloc try-finally remplace le bloc try-catch-finally.
Je vais vous donner un exemple simple: Supposons que vous ayez écrit le code permettant de télécharger des fichiers sur le serveur sans intercepter des exceptions. Désormais, si le téléchargement échoue pour une raison quelconque, le client ne saura jamais ce qui ne va pas. Mais, si vous avez intercepté l'exception, vous pouvez afficher un message d'erreur clair expliquant ce qui ne va pas et comment l'utilisateur peut y remédier.
Règle d'or: attrapez toujours l'exception, car deviner prend du temps
la source
catch
clause devrait toujours être présente chaque fois qu'il y a untry
. Les contributeurs plus expérimentés, y compris moi-même, estiment qu'il s'agit d'un mauvais conseil. Votre raisonnement est imparfait. La méthode contenant letry
n'est pas le seul endroit possible où l'exception peut être interceptée. Il est souvent plus simple et préférable d'autoriser les captures et de signaler les exceptions du plus haut niveau, plutôt que de dupliquer les clauses de capture dans le code.