Bon usage des blocs catch catch?


15

Je me retrouve toujours aux prises avec cela ... essayant de trouver le bon équilibre entre try / catching et le code ne devenant pas ce désordre obscène de tabulations, de crochets et d'exceptions renvoyés dans la pile d'appels comme une patate chaude. Par exemple, j'ai une application en cours de développement qui utilise SQLite. J'ai une interface de base de données qui résume les appels SQLite, et un modèle qui accepte les choses à entrer / sortir de la base de données ... Donc, si / quand une exception SQLite se produit, elle doit être lancée vers le modèle (qui l'a appelée ), qui doit le transmettre à celui qui a appelé AddRecord / DeleteRecord / peu importe ...  

Je suis un fan d'exceptions plutôt que de renvoyer des codes d'erreur car les codes d'erreur peuvent être ignorés, oubliés, etc., alors qu'une exception doit essentiellement être gérée (d'accord, je pourrais attraper et passer immédiatement ...) certain qu'il doit y avoir un meilleur moyen que ce que je fais en ce moment.

Edit:  j'aurais dû formuler cela un peu différemment. Je comprends de relancer comme différents types et tels, je l'ai mal formulé et c'est ma faute. Ma question est ... comment peut-on garder le code propre en le faisant? Cela commence juste à me sembler extrêmement encombré après un certain temps.


Quel langage de programmation?
Apalala

2
C # pour le moment, mais j'essaie de penser en général.
trycatch

C # ne force pas la déclaration des exceptions levées, ce qui facilite le traitement des exceptions lorsque cela est raisonnable et évite aux programmeurs d'être tentés de les intercepter sans les manipuler. Anders Hejlsberg, concepteur de C #, plaide
Apalala

Réponses:


14

Pensez-y en termes de typage fort, même si vous n'utilisez pas un langage fortement typé - si votre méthode ne peut pas retourner le type auquel vous vous attendiez, elle devrait lever une exception.

De plus, plutôt que de lancer l'exception SQLException jusqu'au modèle (ou pire, l'interface utilisateur), chaque couche doit intercepter les exceptions connues et les envelopper / muter / les remplacer par des exceptions adaptées à cette couche:

Layer      Handles Exception
----------------------------
UI         DataNotFoundException
Model      DatabaseRetrievalException
DAO        SQLException

Cela devrait aider à limiter le nombre d'exceptions que vous recherchez dans chaque couche et vous aider à maintenir un système d'exceptions organisé.


J'ai fait quelques modifications, j'ai mal formulé le Q original .. ma question est plus sur la façon de garder le code propre tout en gérant tous les essais et captures et ainsi de suite. Cela commence à se sentir très en désordre lorsque les blocs try catch sont partout ...
trycatch

1
@Ed ne vous dérange-t-il pas que votre interface utilisateur ou votre modèle attrape "SQLException"? Pour moi, ce n'est pas très pertinent. À chacun, je suppose.
Nicole

1
@Ed, cela pourrait être en quelque sorte OK dans les langues qui n'ont pas d'exceptions vérifiées, mais dans les langues avec des exceptions vérifiées, c'est vraiment moche d'avoir throws SQLExceptionune méthode qui n'implique pas que SQL soit même impliqué. Et que se passe-t-il si vous décidez que certaines opérations doivent aller dans un magasin de fichiers? Maintenant, vous devez déclarer throws SQLException, IOException, etc. Cela va devenir incontrôlable.
Mike Daniels

1
@Ed pour l'utilisateur final SQLException ne signifie pas grand-chose, surtout si votre système peut prendre en charge plusieurs types de persistance en dehors des bases de données relationnelles. En tant que programmeur gui, je préfère de loin traiter DataNotFoundException que tout un ensemble d'exceptions de bas niveau. Très souvent, une exception à une bibliothèque de bas niveau fait juste partie de la vie normale ci-dessus, dans ce cas, elles sont beaucoup plus faciles à gérer lorsque leur niveau d'abstraction correspond à celui de l'application.
Newtopian

1
@Newtopian - Je n'ai jamais dit de présenter l'exception brute à l'utilisateur final. Pour les utilisateurs finaux, un simple message `` Le programme a cessé de fonctionner '' est suffisant lors de la journalisation de l'exception quelque part utile pour la prise en charge du support. En mode débogage, il est utile d'afficher l'exception. Vous ne devriez pas vous soucier de toutes les exceptions possibles qu'une API pourrait lancer, sauf si vous avez un gestionnaire spécifique pour de telles exceptions; laissez simplement votre attrapeur d'exceptions non géré gérer tout cela.
Ed James

9

Les exceptions permettent d'écrire du code plus propre car la majeure partie de celui-ci s'occupe du cas normal, et les cas exceptionnels peuvent être traités plus tard, même dans un contexte différent.

La règle de gestion (capture) des exceptions est que cela doit être fait par un contexte qui peut réellement faire quelque chose. Mais cela a une exception:

Les exceptions doivent être interceptées aux limites des modules (spécialement les limites des couches) et même si ce n'est que pour les enfermer, lever une exception de niveau supérieur qui a un sens pour l'appelant.Chaque module et couche doit masquer ses détails d'implémentation même en ce qui concerne les exceptions (un module de segment de mémoire peut renvoyer HeapFull mais jamais ArrayIndexOutOfBounds).

Dans votre exemple, il est peu probable que les couches supérieures puissent faire quoi que ce soit à propos d'une exception SQLite (si elles le font, alors tout est si couplé à SQLite que vous ne pourrez pas basculer la couche de données vers autre chose). Il y a une poignée de raisons prévisibles pour des choses comme Add / Delete / Update à échouer, et certaines d'entre elles (changements incompatibles dans les transactions simultanées) sont impossibles à récupérer même dans la couche données / persistance (violation des règles d'intégrité, fi). La couche de persistance doit traduire les exceptions en quelque chose de significatif dans les termes de la couche modèle afin que les couches supérieures puissent décider de réessayer ou d'échouer correctement.


Je suis d'accord et j'ai modifié ma question pour refléter celle là où je l'avais mal formulée. Je voulais que ce soit plus une question de savoir comment empêcher l'essayage et le rattrapage.
trycatch

Vous pouvez appliquer les principes @Ed James et moi mentionnés dans une couche ou un module. Au lieu d'appeler SQLite directement de partout, ayez quelques méthodes / fonctions qui parlent à SQLite et récupèrent des exceptions ou les traduisent en plus génériques. Si une transaction implique plusieurs requêtes et mises à jour, vous n'avez pas besoin de gérer toutes les exceptions possibles dans chacune: un seul catch-catch externe peut traduire les exceptions, et un interne peut prendre en charge les mises à jour partielles avec un rollback. Vous pouvez également déplacer les mises à jour vers leur propre fonction pour une gestion des exceptions plus simple.
Apalala

1
ce truc serait plus facile à comprendre avec des exemples de code ..
Cliquez Upvote

C'était probablement une question mieux adaptée au stackoverflow dès le départ.
Apalala

5

En règle générale, vous ne devez intercepter que des exceptions spécifiques (par exemple IOException), et uniquement si vous avez quelque chose de spécifique à faire une fois que vous avez intercepté l'exception.

Sinon, il est souvent préférable de laisser les exceptions remonter à la surface afin qu'elles puissent être exposées et traitées. Certains appellent cela à toute épreuve.

Vous devriez avoir une sorte de gestionnaire à la racine de votre application pour détecter les exceptions non gérées qui se sont propagées par le bas. Cela vous donne la possibilité de présenter, signaler ou gérer l'exception de manière appropriée.

L'encapsulation d'exceptions est utile lorsque vous devez lancer une exception sur un système distribué et que le client n'a pas la définition de l'erreur côté serveur.


Attraper et faire taire les exceptions est une chose terrible à faire. Si le programme est cassé, il devrait se bloquer avec une exception et un retraçage. Il devrait s'embrouiller tout en enregistrant les choses en silence mais en gâchant les données.
S.Lott

1
@ S.Lott Je suis d'accord qu'il ne faut pas faire taire l'exception car ils les trouvent ennuyeux mais planter une application est un peu extrême. Il existe de nombreux cas où il est possible d'attraper l'exception et de réinitialiser le système dans un état connu, dans de tels cas, le gestionnaire global de tout est très utile. Si, cependant, le processus de réinitialisation échoue à son tour d'une manière qui ne peut pas être traitée en toute sécurité, alors oui, laissez-le s'écraser est bien mieux que de vous mettre la tête dans le sable.
Newtopian

2
encore une fois, tout dépend de ce que vous construisez, mais je ne suis généralement pas d'accord avec le fait de les laisser bouillonner car cela crée une fuite d'abstraction. Lorsque j'utilise une API, je préfère de loin qu'elle expose des exceptions qui correspondent au modèle de l'API. Je déteste également les surprises, donc je déteste lorsqu'une API laisse juste une exception liée à son implémentation apparaître à l'improviste. Si je n'ai aucune idée de ce qui m'arrive, comment puis-je éventuellement réagir! Je me retrouve trop souvent à utiliser des filets de capture beaucoup plus larges pour éviter que des surprises ne plantent mon application à l'improviste.
Newtopian

@Newtopian: les exceptions "Wrapping" et "Rewriting" réduisent les fuites d'abstractions. Ils bouillonnent toujours de manière appropriée. "Si je n'ai aucune idée de ce qui m'arrive, comment puis-je réagir! Je me retrouve trop souvent à utiliser des filets de capture beaucoup plus larges" C'est la chose que nous vous suggérons d'arrêter de faire. Vous n'avez pas besoin de tout attraper. 80% du temps, la bonne chose est de ne rien attraper. 20% du temps, il y a une réponse significative.
S.Lott

1
@Newtopian, je pense que nous devons faire la distinction entre les exceptions qui seront normalement levées par un objet et devraient donc être encapsulées, et les exceptions qui surviennent en raison de bogues dans le code de l'objet et ne doivent pas être encapsulées.
Winston Ewert

4

Imaginez que vous écrivez une classe de pile. Vous ne placez aucun code de gestion des exceptions dans la classe, car cela pourrait produire les exceptions suivantes.

  1. ArrayIndexError - déclenché lorsque l'utilisateur tente de sortir d'une pile vide
  2. NullPtrException - levée en raison d'un bogue dans l'implémentation provoquant une tentative de référence à une référence nulle

Une approche simpliste pour encapsuler les exceptions pourrait décider d'encapsuler ces deux exceptions dans une classe d'exception StackError. Cependant, cela manque vraiment le point d'envelopper les exceptions. Si un objet lève une exception de bas niveau, cela devrait signifier que l'objet est cassé. Cependant, il y a un cas où cela est acceptable: lorsque l'objet est en fait cassé.

Le point d'encapsuler les exceptions est que l'objet doit donner des exceptions appropriées pour les erreurs normales. La pile doit soulever StackEmpty et non ArrayIndexError lors de l'éclatement d'une pile vide. L'intention n'est pas d'éviter de lever d'autres exceptions si l'objet ou le code est cassé.

Ce que nous avons vraiment voulons éviter, c'est d'attraper des exceptions de bas niveau qui ont été passées à travers des objets de haut niveau. Une classe de pile qui lève une ArrayIndexError lors de l'éclatement d'une pile vide est un problème mineur. Si vous attrapez réellement cette ArrayIndexError, nous avons un problème sérieux. La propagation des erreurs de bas niveau est un péché beaucoup moins grave que de les attraper.

Pour ramener cela à votre exemple d'une SQLException: pourquoi obtenez-vous des SQLExceptions? L'une des raisons est que vous passez des requêtes non valides. Cependant, si votre couche d'accès aux données génère de mauvaises requêtes, elle est cassée. Il ne doit pas tenter de reconditionner sa rupture dans une exception DataAccessFailure.

Cependant, une exception SQLException peut également survenir en raison d'une perte de connexion à la base de données. Ma stratégie sur ce point consiste à intercepter l'exception à la dernière ligne de défense, à signaler à l'utilisateur que la connectivité à la base de données a été perdue et à l'arrêt. Étant donné que l'application a un accès à perte à la base de données, il n'y a vraiment pas beaucoup plus à faire.

Je ne sais pas à quoi ressemble votre code. Mais il semble que vous traduisiez aveuglément toutes les exceptions en exceptions de niveau supérieur. Vous ne devriez le faire que dans un nombre relativement restreint de cas. La plupart des exceptions de niveau inférieur indiquent des bogues dans votre code. Les attraper et les reconditionner est contre-productif.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.