Pour éviter toutes les réponses standard sur lesquelles j'aurais pu googler, je vais vous donner un exemple que vous pouvez tous attaquer à volonté.
C # et Java (et trop d’autres) ont plusieurs types de comportement de débordement que je n’aime pas du tout (par exemple, type.MaxValue + type.SmallestValue == type.MinValue
par exemple : int.MaxValue + 1 == int.MinValue
).
Mais vu ma nature vicieuse, j’ajouterai une insulte à cette blessure en élargissant ce comportement à, disons un type Overridden DateTime
. (Je sais que DateTime
est scellé dans .NET, mais pour cet exemple, j’utilise un pseudo langage qui ressemble exactement à C #, à l’exception du fait que DateTime n’est pas scellé).
La méthode Add
remplacée:
/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan.
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and
/// continue from DateTime.MinValue.
/// </returns>
public DateTime override Add(TimeSpan ts)
{
try
{
return base.Add(ts);
}
catch (ArgumentOutOfRangeException nb)
{
// calculate how much the MaxValue is exceeded
// regular program flow
TimeSpan saldo = ts - (base.MaxValue - this);
return DateTime.MinValue.Add(saldo)
}
catch(Exception anyOther)
{
// 'real' exception handling.
}
}
Bien sûr, un si pourrait résoudre ce problème tout aussi facilement, mais le fait demeure que je ne vois pas pourquoi je ne pouvais pas utiliser les exceptions (logiquement, je peux voir que lorsque les performances sont un problème qui, dans certains cas, doit être évité. ).
Je pense que dans de nombreux cas, elles sont plus claires que les structures if et ne rompent aucun contrat établi par la méthode.
À mon humble avis, la réaction que tout le monde semble avoir ne s'écoule pas comme si de rien n'était, alors que la force de cette réaction peut le justifier.
Ou est-ce que je me trompe?
J'ai lu d'autres articles traitant de toutes sortes de cas particuliers, mais ce que je veux dire, c'est qu'il n'y a rien de mal à cela si vous êtes tous les deux:
Tire-moi dessus.
Avez-vous déjà essayé de déboguer un programme en augmentant cinq exceptions par seconde dans le cours normal des opérations?
J'ai.
Le programme était assez complexe (c’était un serveur de calcul distribué), et une légère modification d’un côté du programme pouvait facilement casser quelque chose dans un endroit totalement différent.
J'aurais aimé pouvoir simplement lancer le programme et attendre que des exceptions se produisent, mais il y a eu environ 200 exceptions lors du démarrage dans le cours normal des opérations
Mon point: si vous utilisez des exceptions pour des situations normales, comment localisez-vous des situations inhabituelles (c'est-à-dire exceptionnelles al??
Bien sûr, il y a d'autres bonnes raisons de ne pas utiliser trop d'exceptions, notamment en termes de performances.
Les exceptions sont essentiellement des instructions non locales goto
avec toutes les conséquences de celles-ci. L'utilisation d'exceptions pour le contrôle de flux enfreint un principe de moindre surprise , rend les programmes difficiles à lire (rappelez-vous que les programmes sont écrits en premier pour les programmeurs).
De plus, ce n’est pas ce que les vendeurs de compilateurs attendent. Ils s'attendent à ce que des exceptions soient rarement lancées et ils laissent généralement le code throw
inefficace. Lancer des exceptions est l'une des opérations les plus coûteuses de .NET.
Cependant, certains langages (notamment Python) utilisent des exceptions en tant que constructions de contrôle de flux. Par exemple, les itérateurs génèrent une exception StopIteration
s'il n'y a plus d'éléments. Même les constructions de langage standard (telles que for
) en dépendent.
Ma règle de base est:
Le problème que je vois avec les exceptions est d'un point de vue purement syntaxique (je suis à peu près sûr que la surcharge de performances est minime). Je n'aime pas les blocs d'essai partout.
Prenons cet exemple:
try
{
DoSomeMethod(); //Can throw Exception1
DoSomeOtherMethod(); //Can throw Exception1 and Exception2
}
catch(Exception1)
{
//Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}
.. Un autre exemple pourrait être lorsque vous devez affecter quelque chose à un handle en utilisant une fabrique, et que cette fabrique peut lever une exception:
Class1 myInstance;
try
{
myInstance = Class1Factory.Build();
}
catch(SomeException)
{
// Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver(); // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(
Donc, personnellement, je pense que vous devriez garder des exceptions pour les rares conditions d'erreur (manque de mémoire, etc.) et utiliser returnvalues (valueclasses, structs ou enums) pour vérifier les erreurs à la place.
J'espère que j'ai bien compris votre question :)
Une première réaction à beaucoup de réponses:
vous écrivez pour les programmeurs et le principe de moindre étonnement
Bien sûr! Mais si ce n’est pas plus clair tout le temps.
Cela ne devrait pas être étonnant par exemple: diviser (1/x) la capture (divisionByZero) est plus claire que tout ce qui me semble (chez Conrad et autres). Le fait que ce type de programmation ne soit pas attendu est purement conventionnel et, en fait, toujours pertinent. Peut-être que dans mon exemple un si serait plus clair.
Mais DivisionByZero et FileNotFound sont plus clairs que ifs.
Bien sûr, si elle est moins performante et nécessite un zillion de temps par seconde, vous devriez bien sûr l’éviter, mais je n’ai toujours pas lu de bonnes raisons d’éviter la conception globale.
En ce qui concerne le principe de moindre étonnement: il y a un danger de raisonnement circulaire ici: supposons qu'une communauté entière utilise un mauvais design, ce design va devenir attendu! Par conséquent, le principe ne peut être un graal et doit être considéré avec soin.
exceptions pour les situations normales, comment localisez-vous les situations inhabituelles (c.-à-d. exceptionnelles)?
Dans beaucoup de réactions qc. comme ça brille au creux. Attrapez-les, non? Votre méthode doit être claire, bien documentée et respecter son contrat. Je ne comprends pas cette question, je dois l'avouer.
Débogage sur toutes les exceptions: idem, c'est parfois fait, car la conception pour ne pas utiliser d'exceptions est courante. Ma question était: pourquoi est-ce courant en premier lieu?
Avant les exceptions, en C, il y avait setjmp
et longjmp
qui pouvaient être utilisés pour effectuer un déroulement similaire du cadre de la pile.
Ensuite, la même construction a reçu un nom: "Exception". Et la plupart des réponses s'appuient sur la signification de ce nom pour argumenter sur son utilisation, affirmant que les exceptions sont destinées à être utilisées dans des conditions exceptionnelles. Ce n’était jamais l’intention du longjmp
original. Il y avait juste des situations où vous deviez diviser le flux de contrôle sur plusieurs cadres de pile.
Les exceptions sont légèrement plus générales en ce sens que vous pouvez également les utiliser dans le même cadre de pile. Cela soulève des analogies avec goto
que je crois erronées. Les gotos sont une paire étroitement couplée (tout comme setjmp
et longjmp
). Les exceptions découlent d'une publication/abonnement faiblement couplé beaucoup plus propre! Par conséquent, les utiliser dans le même cadre de pile est difficilement la même chose que d'utiliser goto
s.
La troisième source de confusion concerne le fait qu'il s'agisse d'exceptions contrôlées ou non contrôlées. Bien entendu, les exceptions non contrôlées semblent particulièrement terribles à utiliser pour le contrôle du flux et peut-être pour beaucoup d'autres choses.
Les exceptions cochées sont toutefois excellentes pour le contrôle du flux, une fois que vous avez surmonté tous les problèmes de l'époque victorienne et que vous vivez un peu.
Mon utilisation préférée est une séquence de throw new Success()
dans un long fragment de code qui tente une chose après l’autre jusqu’à ce qu’elle trouve ce qu’elle cherche. Chaque élément - chaque élément logique - peut avoir une imbrication arbitraire afin que break
soit exclu, ainsi que tout type de test de condition. Le modèle if-else
Est fragile. Si j'édite un else
ou que je dérange la syntaxe d'une autre manière, alors il y a un bogue poilu.
Utilisation de throw new Success()
linéarise le flux de code. J'utilise Success
classes définies localement - vérifiées - de sorte que si j'oublie de l'attraper, le code ne sera pas compilé. Et je n'attrape pas Success
es d'une autre méthode.
Parfois, mon code vérifie une chose après l'autre et ne réussit que si tout va bien. Dans ce cas, j'ai une linéarisation similaire en utilisant throw new Failure()
.
L'utilisation d'une fonction distincte gêne le niveau naturel de compartimentation. La solution return
n'est donc pas optimale. Je préfère avoir une page ou deux de code au même endroit pour des raisons cognitives. Je ne crois pas au code ultra-finement divisé.
Ce que les machines virtuelles ou les compilateurs font est moins pertinent pour moi, sauf s'il existe un hotspot. Je ne peux pas croire qu'il existe une raison fondamentale pour que les compilateurs ne détectent pas les exceptions localisées et capturées localement et les traitent simplement comme très efficaces goto
s au niveau du code machine.
En ce qui concerne leur utilisation à travers les fonctions du flux de contrôle - i. e. pour les cas courants plutôt que pour les cas exceptionnels - je ne vois pas en quoi ils seraient moins efficaces que les tests de rupture, de condition et de retour multiples pour parcourir trois cadres de pile au lieu de simplement restaurer le pointeur de pile.
Personnellement, je n'utilise pas le motif sur les cadres de pile et je peux voir à quel point il faudrait une sophistication de conception pour le faire avec élégance. Mais utilisé avec parcimonie cela devrait aller.
Enfin, s’agissant de surprenants programmeurs vierges, ce n’est pas une raison impérieuse. Si vous les initiez doucement à la pratique, ils apprendront à l'aimer. Je me souviens que C++ avait l'habitude de surprendre et de faire peur aux programmeurs C.
La réponse standard est que les exceptions ne sont pas régulières et doivent être utilisées dans des cas exceptionnels.
Une des raisons, qui est importante pour moi, est que lorsque je lis un try-catch
_ structure de contrôle dans un logiciel que je maintiens ou débogue, je cherche à savoir pourquoi le codeur d’origine a utilisé une gestion des exceptions au lieu d’un if-else
structure. Et je m'attends à trouver une bonne réponse.
N'oubliez pas que vous écrivez du code non seulement pour l'ordinateur, mais également pour d'autres codeurs. Il existe une sémantique associée à un gestionnaire d'exceptions que vous ne pouvez pas supprimer simplement parce que la machine ne vous dérange pas.
Qu'en est-il de la performance? Lors du test de charge d'une application Web .NET, nous avons dépassé les 100 utilisateurs simulés par serveur Web jusqu'à ce que nous résolvions une exception commune et que ce nombre passe à 500 utilisateurs.
Josh Bloch traite ce sujet de manière approfondie dans Effective Java. Ses suggestions sont éclairantes et devraient s’appliquer également à .Net (à l’exception des détails).
En particulier, les exceptions doivent être utilisées dans des circonstances exceptionnelles. Les raisons en sont liées à l'utilisabilité, principalement. Pour qu'une méthode donnée soit utilisable au maximum, ses conditions d'entrée et de sortie doivent être contraintes au maximum.
Par exemple, la seconde méthode est plus facile à utiliser que la première:
/**
* Adds two positive numbers.
*
* @param addend1 greater than zero
* @param addend2 greater than zero
* @throws AdditionException if addend1 or addend2 is less than or equal to zero
*/
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
if( addend1 <= 0 ){
throw new AdditionException("addend1 is <= 0");
}
else if( addend2 <= 0 ){
throw new AdditionException("addend2 is <= 0");
}
return addend1 + addend2;
}
/**
* Adds two positive numbers.
*
* @param addend1 greater than zero
* @param addend2 greater than zero
*/
public int addPositiveNumbers(int addend1, int addend2) {
if( addend1 <= 0 ){
throw new IllegalArgumentException("addend1 is <= 0");
}
else if( addend2 <= 0 ){
throw new IllegalArgumentException("addend2 is <= 0");
}
return addend1 + addend2;
}
Dans les deux cas, vous devez vous assurer que l'appelant utilise votre API de manière appropriée. Mais dans le second cas, vous en avez besoin (implicitement). Les exceptions logicielles seront toujours levées si l'utilisateur n'a pas lu le javadoc, mais:
- Vous n'avez pas besoin de le documenter.
- Vous n'avez pas besoin de le tester (en fonction de l'agressivité de votre stratégie de tests unitaires).
- Vous n'avez pas besoin que l'appelant gère trois cas d'utilisation.
Le point fondamental est que les exceptions devraient non être utilisées comme codes de retour, en grande partie parce que vous avez compliqué non seulement VOTRE API, mais également celle de l'appelant.
Faire la bonne chose a un coût, bien sûr. Tout le monde doit comprendre qu'il est nécessaire de lire et de suivre la documentation. J'espère que c'est le cas de toute façon.
Je pense que vous pouvez utiliser des exceptions pour le contrôle de flux. Il y a cependant un revers de cette technique. La création d'exceptions est une opération coûteuse, car elles doivent créer une trace de pile. Par conséquent, si vous souhaitez utiliser Exceptions plus souvent que pour simplement signaler une situation exceptionnelle, vous devez vous assurer que la création des traces de la pile n'influence pas négativement vos performances.
Le meilleur moyen de réduire les coûts de création d'exceptions consiste à remplacer la méthode fillInStackTrace () comme suit:
public Throwable fillInStackTrace() { return this; }
Une telle exception n'aura pas de stacktraces renseignés.
Je ne vois pas vraiment comment vous contrôlez le déroulement du programme dans le code que vous avez cité. Vous ne verrez jamais d'autre exception que l'exception ArgumentOutOfRange. (Votre seconde clause de capture ne sera donc jamais touchée). Tout ce que vous faites est d'utiliser un jet extrêmement coûteux pour imiter une déclaration if.
De plus, vous ne réalisez pas la plus sinistre des opérations dans lesquelles vous lancez une exception uniquement pour qu'elle soit capturée ailleurs pour effectuer le contrôle de flux. Vous êtes en train de gérer un cas exceptionnel.
Outre les raisons indiquées, l'une des raisons de ne pas utiliser d'exceptions pour le contrôle de flux est qu'elle peut grandement compliquer le processus de débogage.
Par exemple, lorsque j'essaie de localiser un bogue dans VS, j'active généralement "interrompre toutes les exceptions". Si vous utilisez des exceptions pour le contrôle de flux, je vais utiliser régulièrement le débogueur et je devrai continuer à ignorer ces exceptions non exceptionnelles jusqu'à ce que j'arrive au vrai problème. Cela risque de rendre fou quelqu'un !!
Comme le code est difficile à lire, vous aurez peut-être du mal à le déboguer, vous introduirez de nouveaux bogues lorsque vous corrigerez les bogues après un long moment, cela coûtera plus cher en ressources et en temps, et vous gênera si vous déboguez votre code et le débogueur s'arrête à l'apparition de chaque exception;)
Voici les meilleures pratiques que j'ai décrites dans mon article de blog :
Vous pouvez utiliser la griffe d'un marteau pour tourner une vis, tout comme vous pouvez utiliser des exceptions pour contrôler le flux. Cela ne signifie pas que c'est l'utilisation prévue de la fonctionnalité. L'instruction if
exprime des conditions dont l'utilisation prévue est le flux de contrôle.
Si vous utilisez une fonctionnalité de manière non intentionnelle tout en choisissant de ne pas utiliser la fonctionnalité conçue à cet effet, un coût sera associé. Dans ce cas, la clarté et la performance souffrent sans aucune valeur ajoutée réelle. Qu'est-ce que l'utilisation d'exceptions vous achète par rapport à l'instruction if
largement acceptée?
Dit autrement: ce n'est pas parce que vous pouvez que vous devriez.
Supposons que vous ayez une méthode qui effectue certains calculs. Il y a beaucoup de paramètres d'entrée à valider, puis à renvoyer un nombre supérieur à 0.
Utiliser les valeurs de retour pour signaler une erreur de validation est simple: si la méthode renvoie un nombre inférieur à 0, une erreur se produit. Comment savoir alors que qui le paramètre n'a pas été validé?
Je me souviens de mes jours C beaucoup de fonctions renvoyaient des codes d'erreur comme ceci:
-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY
etc.
Est-ce vraiment moins lisible que de lancer et d'attraper une exception?
Vous voudrez peut-être jeter un coup d'œil au système de conditions de Common LISP, qui est une sorte de généralisation des exceptions bien faites. Parce que vous pouvez dérouler la pile ou non de manière contrôlée, vous obtenez également des "redémarrages", qui sont extrêmement pratiques.
Cela n’a rien à voir avec les meilleures pratiques dans d’autres langues, mais il montre ce qui peut être fait avec une conception pensée dans la direction (approximativement) que vous envisagez.
Bien sûr, il y a toujours des considérations de performance si vous sautez dans la pile comme un yo-yo, mais c'est une idée beaucoup plus générale que "oh merde, cautionne" le genre d'approche que la plupart des systèmes d'exception attrape/lance incarnent.
Une raison esthétique:
Un essai vient toujours avec une attrape, alors qu'un si ne doit pas nécessairement venir avec un autre.
if (PerformCheckSucceeded())
DoSomething();
Avec try/catch, cela devient beaucoup plus verbeux.
try
{
PerformCheckSucceeded();
DoSomething();
}
catch
{
}
C'est 6 lignes de code de trop.
Je ne pense pas qu'il y ait quelque chose de mal à utiliser Exceptions pour le contrôle de flux. Les exceptions ressemblent un peu aux continuations et dans les langages statiques, elles sont plus puissantes que les continuations. Par conséquent, si vous avez besoin de continuations mais que votre langage ne les contient pas, vous pouvez utiliser Exceptions pour les implémenter.
En fait, si vous avez besoin de poursuites et que votre langue ne les a pas, vous avez choisi la mauvaise langue et vous devriez plutôt en utiliser une autre. Mais parfois, vous n'avez pas le choix: la programmation Web côté client est l'exemple principal - il n'y a tout simplement pas moyen de contourner JavaScript.
Un exemple: Microsoft Volta est un projet permettant d'écrire des applications Web en .NET simple et de laisser le cadre s'occuper de déterminer quels bits doivent être exécutés où. Conséquence: Volta doit pouvoir compiler CIL en JavaScript pour pouvoir exécuter du code sur le client. Cependant, il y a un problème: .NET a le multithreading, pas JavaScript. Ainsi, Volta implémente des continuations dans JavaScript à l'aide d'exceptions JavaScript, puis implémente des threads .NET à l'aide de ces continuations. De cette façon, les applications Volta qui utilisent des threads peuvent être compilées pour être exécutées dans un navigateur non modifié - sans Silverlight.
Comme d'autres l'ont déjà mentionné à maintes reprises, principe de moindre surprise vous interdira d'utiliser des exceptions de manière excessive à des fins de contrôle de flux uniquement. D'un autre côté, aucune règle n'est correcte à 100%, et il y a toujours des cas où une exception est "juste le bon outil" - un peu comme goto
lui-même, d'ailleurs, qui est livré sous la forme de break
et continue
dans des langages comme Java, qui sont souvent le moyen idéal pour sortir de boucles fortement imbriquées, qui ne sont pas toujours évitables.
L'article suivant décrit un cas d'utilisation assez complexe mais également intéressant pour un non-local ControlFlowException
:
Il explique comment, à l'intérieur de jOOQ (une bibliothèque d'abstraction SQL pour Java) , de telles exceptions sont parfois utilisées pour interrompre le processus de rendu SQL plus tôt lorsque certaines conditions "rares" sont remplies.
Trop de valeurs de liaison sont rencontrées. Certaines bases de données ne prennent pas en charge les nombres arbitraires de valeurs de liaison dans leurs instructions SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). Dans ces cas, jOOQ abandonne la phase de rendu SQL et rend à nouveau l'instruction SQL avec des valeurs de liaison incorporées. Exemple:
// Pseudo-code attaching a "handler" that will
// abort query rendering once the maximum number
// of bind values was exceeded:
context.attachBindValueCounter();
String sql;
try {
// In most cases, this will succeed:
sql = query.render();
}
catch (ReRenderWithInlinedVariables e) {
sql = query.renderWithInlinedBindValues();
}
Si nous extrayions explicitement les valeurs de liaison de la requête AST pour les compter à chaque fois, nous gaspillerions des cycles de processeur précieux pour ces 99,9% des requêtes qui ne souffrent pas de ce problème.
Certaines logiques ne sont disponibles qu'indirectement via une API que nous voulons exécuter seulement "partiellement". La méthode UpdatableRecord.store()
génère une instruction INSERT
ou UPDATE
, en fonction des indicateurs internes de Record
. De "l'extérieur", nous ne savons pas quel type de logique est contenu dans store()
(verrouillage optimiste, traitement de l'écouteur d'événements, etc.), nous ne voulons donc pas répéter cette logique lorsque nous stockons plusieurs enregistrements dans une instruction batch, où nous aimerions que store()
ne génère que l'instruction SQL, et ne l'exécute pas réellement. Exemple:
// Pseudo-code attaching a "handler" that will
// prevent query execution and throw exceptions
// instead:
context.attachQueryCollector();
// Collect the SQL for every store operation
for (int i = 0; i < records.length; i++) {
try {
records[i].store();
}
// The attached handler will result in this
// exception being thrown rather than actually
// storing records to the database
catch (QueryCollectorException e) {
// The exception is thrown after the rendered
// SQL statement is available
queries.add(e.query());
}
}
Si nous avions externalisé la logique store()
dans une API "réutilisable" pouvant être personnalisée pour éventuellement ne pas exécuter le code SQL, Je chercherais à créer une API plutôt difficile à maintenir, à peine réutilisable.
En substance, notre utilisation de ces goto
s non locales va dans le sens de ce que [Mason Wheeler] [5] a dit dans sa réponse:
"Je viens de rencontrer une situation que je ne peux pas gérer correctement à ce stade, parce que je n’ai pas assez de contexte pour le gérer, mais la routine qui m’a appelé (ou quelque chose plus haut dans la pile d’appel) doit savoir comment le gérer. . "
Les deux utilisations de ControlFlowExceptions
étaient plutôt faciles à mettre en œuvre par rapport à leurs alternatives, ce qui nous permettait de réutiliser un large éventail de logiques sans le refactoriser à partir des éléments internes pertinents.
Mais le sentiment d'être un peu surprenant pour les futurs responsables de la maintenance demeure. Le code est plutôt délicat et bien que ce fût le bon choix dans ce cas, nous préférerions toujours ne pas utiliser d’exceptions pour le flux de contrôle local , où Il est facile d’éviter d’utiliser des branches ordinaires via if - else
.
En règle générale, il n’ya rien de mal en soi à gérer une exception à un niveau bas. Une exception IS un message valide qui fournit beaucoup de détails sur la raison pour laquelle une opération ne peut pas être effectuée. Et si vous pouvez la gérer, vous devriez le faire.
En général, si vous savez que la probabilité d'échec est élevée et que vous pouvez vérifier ... vous devez effectuer la vérification ... c'est-à-dire si (obj! = Null) obj.method ()
Dans votre cas, je ne connais pas suffisamment la bibliothèque C # pour savoir si date-heure est un moyen facile de vérifier si un horodatage est hors limites. Si tel est le cas, appelez simplement si (.isvalid (ts)), sinon votre code est correct.
Donc, en gros, tout se résume à la manière dont le code est plus propre ... si l'opération de protection contre une exception attendue est plus complexe que la simple gestion de l'exception; que vous avez ma permission pour gérer l'exception au lieu de créer des gardes complexes partout.
Il existe quelques mécanismes généraux par lesquels une langue pourrait permettre à une méthode de quitter sans renvoyer de valeur et de se dérouler au bloc "catch" suivant:
Demandez à la méthode d’examiner le cadre de pile pour déterminer le site d’appel et d’utiliser les métadonnées du site d’appel pour rechercher des informations sur un bloc try
dans la méthode d’appel ou sur l’emplacement où la méthode a stocké l’adresse de son appelant; dans ce dernier cas, examinez les métadonnées de l'appelant pour déterminer de la même manière que l'appelant immédiat, en répétant jusqu'à ce que vous trouviez un bloc try
ou que la pile soit vide. Cette approche ajoute très peu de charge au cas d’absence d’exception (elle exclut certaines optimisations) mais est coûteuse en cas d’exception.
Demandez à la méthode de renvoyer un indicateur "masqué" qui distingue un retour normal d'une exception et demandez à l'appelant de vérifier cet indicateur et de passer à une routine "d'exception" si elle est définie. Cette routine ajoute 1 à 2 instructions au cas d’exception sans exception, mais une surcharge relativement faible en cas d’exception.
Demandez à l'appelant de placer les informations ou le code de gestion des exceptions à une adresse fixe par rapport à l'adresse de retour empilée. Par exemple, avec l’ARM, au lieu d’utiliser l’instruction "sous-routine BL", on pourrait utiliser la séquence suivante:
adr lr,next_instr
b subroutine
b handle_exception
next_instr:
Pour sortir normalement, le sous-programme ferait simplement bx lr
ou pop {pc}
; en cas de sortie anormale, le sous-programme soustrait 4 de LR avant d'effectuer le retour ou utilise sub lr,#4,pc
(en fonction de la ARM, mode d'exécution, etc.). Cette approche fonctionnera très mal si l'appelant n'est pas conçu pour y répondre.
Un langage ou une structure qui utilise des exceptions vérifiées peut bénéficier de la gestion de celles-ci avec un mécanisme tel que # 2 ou # 3 ci-dessus, tandis que les exceptions non contrôlées sont gérées avec # 1. Bien que la mise en oeuvre des exceptions vérifiées dans Java soit plutôt gênante, elles ne seraient pas un mauvais concept s’il existait un moyen par lequel un site d’appel pourrait dire, essentiellement: "Cette méthode est déclarée comme jetée". XX, mais je ne m'attends pas à ce qu'il le soit jamais; s'il le fait, revenez en tant qu'exception "non contrôlée". Dans un cadre où les exceptions vérifiées étaient traitées de cette manière, elles pourraient constituer un moyen efficace de contrôle du flux, par exemple. méthodes d'analyse qui, dans certains contextes, peuvent avoir une forte probabilité d'échec, mais où l'échec devrait renvoyer des informations fondamentalement différentes de la réussite. Cependant, je ne suis au courant d'aucun cadre utilisant un tel modèle. première approche ci-dessus (coût minimal pour le cas d’exception sans exception, mais coût élevé lorsque des exceptions sont levées) pour toutes les exceptions.
Je pense qu'il n'y a rien de mal avec votre exemple. Au contraire, ignorer l'exception déclenchée par la fonction appelée serait un péché.
Dans la machine virtuelle Java, le lancement d'une exception n'est pas si coûteux, il ne crée que l'exception avec la nouvelle exception xyzException (...), car cette dernière implique une visite de pile. Donc, si vous avez des exceptions créées à l'avance, vous pouvez les lancer plusieurs fois sans frais. Bien sûr, de cette façon, vous ne pouvez pas transmettre de données avec l'exception, mais je pense que c'est une mauvaise chose à faire de toute façon.
Mais vous ne saurez pas toujours ce qui se passe dans les méthodes que vous appelez. Vous ne saurez pas exactement où l'exception a été levée. Sans examiner l'objet exception plus en détail ....