Motif pour éviter les blocs try catch imbriqués?

114

Prenons une situation dans laquelle j'ai trois (ou plus) façons d'effectuer un calcul, chacune pouvant échouer avec une exception. Afin d'essayer chaque calcul jusqu'à ce que nous trouvions celui qui réussit, j'ai fait ce qui suit:

double val;

try { val = calc1(); }
catch (Calc1Exception e1)
{ 
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        try { val = calc3(); }
        catch (Calc3Exception e3)
        {
            throw new NoCalcsWorkedException();
        }
    }
}

Existe-t-il un modèle accepté qui permet d'atteindre cet objectif d'une manière plus agréable? Bien sûr, je pourrais envelopper chaque calcul dans une méthode d'assistance qui renvoie null en cas d'échec, puis utiliser simplement l' ??opérateur, mais y a-t-il un moyen de le faire plus généralement (c'est-à-dire sans avoir à écrire une méthode d'assistance pour chaque méthode que je veux utiliser )? J'ai pensé à écrire une méthode statique utilisant des génériques qui encapsule une méthode donnée dans un try / catch et retourne null en cas d'échec, mais je ne suis pas sûr de la façon dont je procéderais. Des idées?

jjoelson
la source
Pouvez-vous inclure quelques détails sur le calcul?
James Johnson
2
Ce sont essentiellement des méthodes différentes pour résoudre / approcher un PDE. Ils proviennent d'une bibliothèque tierce, je ne peux donc pas les modifier pour renvoyer des codes d'erreur ou null. Le mieux que je puisse faire est d'envelopper chacun individuellement dans une méthode.
jjoelson
Les méthodes de calcul font-elles partie de votre projet (au lieu d'une bibliothèque tierce)? Si tel est le cas, vous pouvez extraire la logique qui lève des exceptions et l'utiliser pour décider quelle méthode calc doit être appelée.
Chris
1
Il y a un autre cas d'utilisation pour cela que j'ai rencontré en Java - je dois analyser un Stringen Dateutilisant SimpleDateFormat.parseet je dois essayer plusieurs formats différents dans l'ordre, passant au suivant quand on lance une exception.
Misérable Variable

Réponses:

125

Dans la mesure du possible, n'utilisez pas d'exceptions pour le flux de contrôle ou des circonstances non exceptionnelles.

Mais pour répondre directement à votre question (en supposant que tous les types d'exceptions sont identiques):

Func<double>[] calcs = { calc1, calc2, calc3 };

foreach(var calc in calcs)
{
   try { return calc(); }
   catch (CalcException){  }
} 

throw new NoCalcsWorkedException();
Ani
la source
15
Cela suppose que Calc1Exception, Calc2Exceptionet Calc3Exceptionpartager une classe de base commune.
Wyzard
3
En plus, il suppose une signature commune - ce qui n'est pas vraiment si loin. Bonne réponse.
TomTom
1
De plus, j'ai ajouté continuedans le bloc catch et breakaprès le bloc catch pour que la boucle se termine lorsqu'un calcul fonctionne (merci à Lirik pour ce bit)
jjoelson
6
+1 uniquement parce qu'il dit "Ne pas utiliser d'exceptions pour le flux de contrôle" bien que j'aurais utilisé "JAMAIS" au lieu de "Autant que possible".
Bill K
1
@jjoelson: Une breakdéclaration qui suit calc();dans le try(et aucune continueinstruction du tout) pourrait être un peu plus idiomatique.
Adam Robinson
38

Juste pour proposer une alternative "hors des sentiers battus", que diriez-vous d'une fonction récursive ...

//Calling Code
double result = DoCalc();

double DoCalc(int c = 1)
{
   try{
      switch(c){
         case 1: return Calc1();
         case 2: return Calc2();
         case 3: return Calc3();
         default: return CalcDefault();  //default should not be one of the Calcs - infinite loop
      }
   }
   catch{
      return DoCalc(++c);
   }
}

REMARQUE: je ne dis en aucun cas que c'est la meilleure façon de faire le travail, juste une manière différente

musefan
la source
6
J'ai dû implémenter "On Error Resume Next" une fois dans une langue, et le code que j'ai généré ressemblait beaucoup à ceci.
Jacob Krall
4
Veuillez ne jamais utiliser une instruction switch pour créer une boucle for.
Jeff Ferland
3
Il n'est pas maintenable d'avoir une instruction switch pour la boucle
Mohamed Abed
1
Je sais que ma réponse n'est pas le code le plus efficace, mais encore une fois, utiliser des blocs try / catch pour ce genre de chose n'est pas la meilleure façon de procéder de toute façon. Malheureusement, l'OP utilise une bibliothèque tierce et doit faire de son mieux pour assurer son succès. Idéalement, l'entrée pourrait être validée en premier et la fonction de calcul correcte choisie pour s'assurer qu'elle n'échouera pas - bien sûr, vous pouvez ensuite mettre tout cela dans un essai / capture juste pour être sûr;)
musefan
1
return DoCalc(c++)équivaut à return DoCalc(c)- la valeur post-incrémentée ne sera pas transmise plus profondément. Pour le faire fonctionner (et introduire plus d'obscurité), cela pourrait ressembler davantage à return DoCalc((c++,c)).
Artur Czajka
37

Vous pouvez aplatir l'imbrication en la mettant dans une méthode comme celle-ci:

private double calcStuff()
{
  try { return calc1(); }
  catch (Calc1Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc2(); }
  catch (Calc2Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc3(); }
  catch (Calc3Exception e1)
  {
    // Continue on to the code below
  }

  throw new NoCalcsWorkedException();
}

Mais je soupçonne que le vrai problème de conception est l'existence de trois méthodes différentes qui font essentiellement la même chose (du point de vue de l'appelant) mais lancent des exceptions différentes et sans rapport.

Cela suppose que les trois exceptions ne sont pas liées. S'ils ont tous une classe de base commune, il serait préférable d'utiliser une boucle avec un seul bloc catch, comme l'a suggéré Ani.

Wyzard
la source
1
+1: C'est la solution la plus propre et la plus simple au problème. Les autres solutions que je vois ici essaient juste d'être mignon, OMI. Comme l'a dit l'OP, il n'a pas écrit l'API, il est donc coincé avec les exceptions levées.
Nate CK
19

Essayez de ne pas contrôler la logique basée sur des exceptions; notez également que des exceptions ne devraient être levées que dans des cas exceptionnels. Dans la plupart des cas, les calculs ne doivent pas lever d'exceptions, sauf s'ils accèdent à des ressources externes ou analysent des chaînes ou quelque chose. Quoi qu'il en soit, dans le pire des cas, suivez le style TryMethod (comme TryParse ()) pour encapsuler la logique d'exception et rendre votre flux de contrôle maintenable et propre:

bool TryCalculate(out double paramOut)
{
  try
  {
    // do some calculations
    return true;
  }
  catch(Exception e)
  { 
     // do some handling
    return false;
  }

}

double calcOutput;
if(!TryCalc1(inputParam, out calcOutput))
  TryCalc2(inputParam, out calcOutput);

Une autre variante utilisant le modèle Try et combinant la liste de méthodes au lieu d'imbriquer si:

internal delegate bool TryCalculation(out double output);

TryCalculation[] tryCalcs = { calc1, calc2, calc3 };

double calcOutput;
foreach (var tryCalc in tryCalcs.Where(tryCalc => tryCalc(out calcOutput)))
  break;

et si le foreach est un peu compliqué, vous pouvez le préciser:

        foreach (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }
Mohamed Abed
la source
Honnêtement, je pense que cela ne cause que des abstractions inutiles. Ce n'est pas une solution horrible, mais je ne l'utiliserais pas dans la plupart des cas.
user606723
Si vous ne vous souciez pas du type d'exception, et que vous voulez juste gérer le code conditionnel ... il est donc certainement préférable en termes d'abstraction et de maintenabilité de le convertir en une méthode conditionnelle avec retour, qu'il réussisse ou non, de cette façon vous masquez la syntaxe désordonnée de gestion des exceptions avec une méthode descriptive claire .. alors votre code la gérera car c'est une méthode conditionnelle régulière.
Mohamed Abed
Je connais les points, et ils sont valables. Cependant, lorsque vous utilisez ce type d'abstraction (cacher le désordre / la complexité) presque partout, cela devient ridicule et comprendre un logiciel pour ce qu'il est devient beaucoup plus difficile. Comme je l'ai dit, ce n'est pas une solution terrible, mais je ne l'utiliserais pas à la légère.
user606723
9

Créez une liste de délégués à vos fonctions de calcul, puis disposez d'une boucle while pour les parcourir:

List<Func<double>> calcMethods = new List<Func<double>>();

// Note: I haven't done this in a while, so I'm not sure if
// this is the correct syntax for Func delegates, but it should
// give you an idea of how to do this.
calcMethods.Add(new Func<double>(calc1));
calcMethods.Add(new Func<double>(calc2));
calcMethods.Add(new Func<double>(calc3));

double val;
for(CalcMethod calc in calcMethods)
{
    try
    {
        val = calc();
        // If you didn't catch an exception, then break out of the loop
        break;
    }
    catch(GenericCalcException e)
    {
        // Not sure what your exception would be, but catch it and continue
    }

}

return val; // are you returning the value?

Cela devrait vous donner une idée générale de la façon de le faire (c'est-à-dire que ce n'est pas une solution exacte).

Kiril
la source
1
Sauf bien sûr que vous ne devriez normalement jamais attraper Exceptionalors. ;)
DeCaf
@DeCaf comme je l'ai dit: cela "devrait vous donner une idée générale de la façon de le faire (c'est-à-dire pas une solution exacte)." Ainsi, l'OP peut attraper toute exception appropriée ... pas besoin d'attraper le générique Exception.
Kiril
Ouais, désolé, j'ai juste ressenti le besoin de faire sortir ça.
DeCaf
1
@DeCaf, c'est une clarification valable pour ceux qui ne sont peut-être pas familiers avec les meilleures pratiques. Merci :)
Kiril
9

Cela ressemble à un travail pour ... MONADS! Plus précisément, la monade peut-être. Commencez par la monade Maybe comme décrit ici . Ajoutez ensuite quelques méthodes d'extension. J'ai écrit ces méthodes d'extension spécifiquement pour le problème tel que vous l'avez décrit. La bonne chose à propos des monades est que vous pouvez écrire les méthodes d'extension exactes nécessaires à votre situation.

public static Maybe<T> TryGet<T>(this Maybe<T> m, Func<T> getFunction)
{
    // If m has a value, just return m - we want to return the value
    // of the *first* successful TryGet.
    if (m.HasValue)
    {
        return m;
    }

    try
    {
        var value = getFunction();

        // We were able to successfully get a value. Wrap it in a Maybe
        // so that we can continue to chain.
        return value.ToMaybe();
    }
    catch
    {
        // We were unable to get a value. There's nothing else we can do.
        // Hopefully, another TryGet or ThrowIfNone will handle the None.
        return Maybe<T>.None;
    }
}

public static Maybe<T> ThrowIfNone<T>(
    this Maybe<T> m,
    Func<Exception> throwFunction)
{
    if (!m.HasValue)
    {
        // If m does not have a value by now, give up and throw.
        throw throwFunction();
    }

    // Otherwise, pass it on - someone else should unwrap the Maybe and
    // use its value.
    return m;
}

Utilisez-le comme ceci:

[Test]
public void ThrowIfNone_ThrowsTheSpecifiedException_GivenNoSuccessfulTryGet()
{
    Assert.That(() =>
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Throws.TypeOf<NoCalcsWorkedException>());
}

[Test]
public void Value_ReturnsTheValueOfTheFirstSuccessfulTryGet()
{
    Assert.That(
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => 0)
            .TryGet(() => 1)
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Is.EqualTo(0));
}

Si vous faites souvent ce genre de calculs, peut-être que la monade devrait réduire la quantité de code standard que vous devez écrire tout en augmentant la lisibilité de votre code.

fre0n
la source
2
J'adore cette solution. Cependant, il est assez opaque pour quiconque n'a jamais été exposé à des monades, ce qui signifie que c'est très loin d'être idiomatique en c #. Je ne voudrais pas qu'un de mes collègues apprenne les monades juste pour modifier ce morceau de code idiot à l'avenir. C'est parfait pour référence future, cependant.
jjoelson
1
+1 pour le sens de l'humour, pour écrire la solution la plus obtuse et verbeuse possible à ce problème et dire ensuite que cela "réduira la quantité de code standard que vous devez écrire tout en augmentant la lisibilité de votre code".
Nate CK
1
Hé, nous ne nous plaignons pas des énormes quantités de code qui se cachent dans System.Linq, et utilisons volontiers ces monades toute la journée. Je pense que @ fre0n signifie simplement que si vous êtes prêt à mettre la monade Maybe dans votre boîte à outils, ces types d'évaluations chaînées deviennent plus faciles à regarder et à raisonner. Il y a plusieurs implemntations qui sont faciles à saisir.
Sebastian Good
Ce Mayben'est pas parce qu'il utilise une solution monadique; il utilise zéro des propriétés monadiques de Maybeet peut donc tout aussi bien l'utiliser null. En outre, utilisé "monadiquement", ce serait l' inverse du Maybe. Une vraie solution monadique devrait utiliser une monade d'État qui conserve la première valeur non exceptionnelle comme état, mais ce serait exagéré lorsque l'évaluation en chaîne normale fonctionne.
Dax Fohl
7

Une autre version de l' approche de la méthode try . Celui-ci autorise les exceptions typées, car il existe un type d'exception pour chaque calcul:

    public bool Try<T>(Func<double> func, out double d) where T : Exception
    {
      try
      {
        d = func();
        return true;
      }
      catch (T)
      {
        d = 0;
        return false;
      }
    }

    // usage:
    double d;
    if (!Try<Calc1Exception>(() = calc1(), out d) && 
        !Try<Calc2Exception>(() = calc2(), out d) && 
        !Try<Calc3Exception>(() = calc3(), out d))

      throw new NoCalcsWorkedException();
    }
Stefan
la source
Vous pouvez en fait éviter les if imbriqués en utilisant à la place &&entre chaque condition.
DeCaf
4

En Perl, vous pouvez faire foo() or bar(), qui s'exécutera en bar()cas d' foo()échec. En C #, nous ne voyons pas cette construction "en cas d'échec, alors", mais il existe un opérateur que nous pouvons utiliser à cette fin: l'opérateur null-coalesce ??, qui ne continue que si la première partie est nulle.

Si vous pouvez modifier la signature de vos calculs et si vous encapsulez leurs exceptions (comme indiqué dans les articles précédents) ou les réécrivez pour les renvoyer à la nullplace, votre chaîne de code devient de plus en plus brève et toujours facile à lire:

double? val = Calc1() ?? Calc2() ?? Calc3() ?? Calc4();
if(!val.HasValue) 
    throw new NoCalcsWorkedException();

J'ai utilisé les remplacements suivants pour vos fonctions, ce qui aboutit à la valeur 40.40en val.

static double? Calc1() { return null; /* failed */}
static double? Calc2() { return null; /* failed */}
static double? Calc3() { return null; /* failed */}
static double? Calc4() { return 40.40; /* success! */}

Je me rends compte que cette solution ne sera pas toujours applicable, mais vous avez posé une question très intéressante et je crois, même si le fil est relativement ancien, qu’il s’agit d’un modèle à prendre en compte lorsque vous pouvez faire amende honorable.

Abel
la source
1
Je veux juste dire "Merci". J'ai essayé de mettre en œuvre ce dont vous parliez . J'espère que je l'ai bien compris.
AlexMelw
3

Étant donné que les méthodes de calcul ont la même signature sans paramètre, vous pouvez les enregistrer dans une liste, parcourir cette liste et exécuter les méthodes. Il serait probablement encore mieux pour vous d'utiliser le Func<double>sens "une fonction qui renvoie un résultat de type double".

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
  class CalculationException : Exception { }
  class Program
  {
    static double Calc1() { throw new CalculationException(); }
    static double Calc2() { throw new CalculationException(); }
    static double Calc3() { return 42.0; }

    static void Main(string[] args)
    {
      var methods = new List<Func<double>> {
        new Func<double>(Calc1),
        new Func<double>(Calc2),
        new Func<double>(Calc3)
    };

    double? result = null;
    foreach (var method in methods)
    {
      try {
        result = method();
        break;
      }
      catch (CalculationException ex) {
        // handle exception
      }
     }
     Console.WriteLine(result.Value);
   }
}
Marcin Seredynski
la source
3

Vous pouvez utiliser un Task / ContinueWith et rechercher l'exception. Voici une belle méthode d'extension pour aider à la rendre jolie:

    static void Main() {
        var task = Task<double>.Factory.StartNew(Calc1)
            .OrIfException(Calc2)
            .OrIfException(Calc3)
            .OrIfException(Calc4);
        Console.WriteLine(task.Result); // shows "3" (the first one that passed)
    }

    static double Calc1() {
        throw new InvalidOperationException();
    }

    static double Calc2() {
        throw new InvalidOperationException();
    }

    static double Calc3() {
        return 3;
    }

    static double Calc4() {
        return 4;
    }
}

static class A {
    public static Task<T> OrIfException<T>(this Task<T> task, Func<T> nextOption) {
        return task.ContinueWith(t => t.Exception == null ? t.Result : nextOption(), TaskContinuationOptions.ExecuteSynchronously);
    }
}
Dax Fohl
la source
1

Si le type réel de l'exception levée n'a pas d'importance, vous pouvez simplement utiliser un bloc catch sans type:

var setters = new[] { calc1, calc2, calc3 };
bool succeeded = false;
foreach(var s in setters)
{
    try
    {
            val = s();
            succeeded = true;
            break;
    }
    catch { /* continue */ }
}
if (!suceeded) throw new NoCalcsWorkedException();
Jacob Krall
la source
Cela n'appelle-t-il pas toujours toutes les fonctions de la liste? Je pourrais vouloir lancer (jeu de mots non prévu) dans quelque chose commeif(succeeded) { break; } post-catch.
un CVn
1
using System;

namespace Utility
{
    /// <summary>
    /// A helper class for try-catch-related functionality
    /// </summary>
    public static class TryHelper
    {
        /// <summary>
        /// Runs each function in sequence until one throws no exceptions;
        /// if every provided function fails, the exception thrown by
        /// the final one is left unhandled
        /// </summary>
        public static void TryUntilSuccessful( params Action[] functions )
        {
            Exception exception = null;

            foreach( Action function in functions )
            {
                try
                {
                    function();
                    return;
                }
                catch( Exception e )
                {
                    exception   = e;
                }
            }

            throw exception;
        }
    }
}

Et utilisez-le comme ceci:

using Utility;

...

TryHelper.TryUntilSuccessful(
    () =>
    {
        /* some code */
    },
    () =>
    {
        /* more code */
    },
    calc1,
    calc2,
    calc3,
    () =>
    {
        throw NotImplementedException();
    },
    ...
);
Ryan Lester
la source
1

Il semble que l'intention du PO était de trouver un bon modèle pour résoudre son problème et résoudre le problème actuel avec lequel il luttait à ce moment-là.

OP: "Je pourrais encapsuler chaque calcul dans une méthode d'assistance qui renvoie null en cas d'échec, puis utiliser simplement l' ??opérateur, mais y a-t-il un moyen de le faire plus généralement (c'est-à-dire sans avoir à écrire une méthode d'assistance pour chaque méthode que je veux use)? J'ai pensé à écrire une méthode statique utilisant des génériques qui encapsule une méthode donnée dans un try / catch et renvoie null en cas d'échec, mais je ne suis pas sûr de la manière dont je procéderais. Des idées? "

J'ai vu beaucoup de bons modèles qui évitent les blocs try catch imbriqués , publiés dans ce flux, mais n'ai pas trouvé de solution au problème cité ci-dessus. Alors, voici la solution:

Comme OP mentionné ci-dessus, il voulait créer un objet wrapper qui retourne nullen cas d'échec . Je l'appellerais un pod ( pod sans danger pour les exceptions ).

public static void Run()
{
    // The general case
    // var safePod1 = SafePod.CreateForValueTypeResult(() => CalcX(5, "abc", obj));
    // var safePod2 = SafePod.CreateForValueTypeResult(() => CalcY("abc", obj));
    // var safePod3 = SafePod.CreateForValueTypeResult(() => CalcZ());

    // If you have parameterless functions/methods, you could simplify it to:
    var safePod1 = SafePod.CreateForValueTypeResult(Calc1);
    var safePod2 = SafePod.CreateForValueTypeResult(Calc2);
    var safePod3 = SafePod.CreateForValueTypeResult(Calc3);

    var w = safePod1() ??
            safePod2() ??
            safePod3() ??
            throw new NoCalcsWorkedException(); // I've tested it on C# 7.2

    Console.Out.WriteLine($"result = {w}"); // w = 2.000001
}

private static double Calc1() => throw new Exception("Intentionally thrown exception");
private static double Calc2() => 2.000001;
private static double Calc3() => 3.000001;

Mais que faire si vous souhaitez créer un pod sécurisé pour un résultat de type de référence retourné par les fonctions / méthodes CalcN ().

public static void Run()
{
    var safePod1 = SafePod.CreateForReferenceTypeResult(Calc1);
    var safePod2 = SafePod.CreateForReferenceTypeResult(Calc2);
    var safePod3 = SafePod.CreateForReferenceTypeResult(Calc3);

    User w = safePod1() ?? safePod2() ?? safePod3();

    if (w == null) throw new NoCalcsWorkedException();

    Console.Out.WriteLine($"The user object is {{{w}}}"); // The user object is {Name: Mike}
}

private static User Calc1() => throw new Exception("Intentionally thrown exception");
private static User Calc2() => new User { Name = "Mike" };
private static User Calc3() => new User { Name = "Alex" };

class User
{
    public string Name { get; set; }
    public override string ToString() => $"{nameof(Name)}: {Name}";
}

Ainsi, vous remarquerez peut-être qu'il n'est pas nécessaire "d'écrire une méthode d'assistance pour chaque méthode que vous souhaitez utiliser" .

Les deux types de pods (pour ValueTypeResults et ReferenceTypeResults) suffisent .


Voici le code de SafePod. Ce n'est cependant pas un conteneur. Au lieu de cela, il crée un wrapper de délégué sécurisé pour les exceptions pour ValueTypeResults et ReferenceTypeResults.

public static class SafePod
{
    public static Func<TResult?> CreateForValueTypeResult<TResult>(Func<TResult> jobUnit) where TResult : struct
    {
        Func<TResult?> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }

    public static Func<TResult> CreateForReferenceTypeResult<TResult>(Func<TResult> jobUnit) where TResult : class
    {
        Func<TResult> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }
}

C'est ainsi que vous pouvez tirer parti de l'opérateur de fusion nul ??combiné à la puissance d' entités citoyennes de premier ordredelegate .

AlexMelw
la source
0

Vous avez raison d'encapsuler chaque calcul, mais vous devez encapsuler selon le principe tell-don't-ask.

double calc3WithConvertedException(){
    try { val = calc3(); }
    catch (Calc3Exception e3)
    {
        throw new NoCalcsWorkedException();
    }
}

double calc2DefaultingToCalc3WithConvertedException(){
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        //defaulting to simpler method
        return calc3WithConvertedException();
    }
}


double calc1DefaultingToCalc2(){
    try { val = calc2(); }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}

Les opérations sont simples et peuvent changer leur comportement de manière indépendante. Et peu importe pourquoi ils sont par défaut. Pour prouver, vous pouvez implémenter calc1DefaultingToCalc2 comme:

double calc1DefaultingToCalc2(){
    try { 
        val = calc2(); 
        if(specialValue(val)){
            val = calc2DefaultingToCalc3WithConvertedException()
        }
    }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}
raisins secs
la source
-1

Il semble que vos calculs contiennent plus d'informations valides à renvoyer que le calcul lui-même. Il serait peut-être plus judicieux pour eux de faire leur propre gestion des exceptions et de renvoyer une classe «results» contenant des informations d'erreur, des informations de valeur, etc. Pensez comme la classe AsyncResult suit le modèle async. Vous pouvez ensuite évaluer le résultat réel du calcul. Vous pouvez rationaliser cela en pensant que si un calcul échoue, c'est tout aussi informatif que s'il réussissait. Par conséquent, une exception est une information et non une «erreur».

internal class SomeCalculationResult 
{ 
     internal double? Result { get; private set; } 
     internal Exception Exception { get; private set; }
}

...

SomeCalculationResult calcResult = Calc1();
if (!calcResult.Result.HasValue) calcResult = Calc2();
if (!calcResult.Result.HasValue) calcResult = Calc3();
if (!calcResult.Result.HasValue) throw new NoCalcsWorkedException();

// do work with calcResult.Result.Value

...

Bien sûr, je m'interroge davantage sur l'architecture globale que vous utilisez pour effectuer ces calculs.

Emoire
la source
C'est correct - similaire à ce que OP a suggéré en ce qui concerne les calculs. Je préfère juste quelque chose comme while (!calcResult.HasValue) nextCalcResult(), au lieu d'une liste de Calc1, Calc2, Calc3 etc.
Kirk Broadhurst
-3

Qu'en est-il du suivi des actions que vous faites ...

double val;
string track = string.Empty;

try 
{ 
  track = "Calc1";
  val = calc1(); 

  track = "Calc2";
  val = calc2(); 

  track = "Calc3";
  val = calc3(); 
}
catch (Exception e3)
{
   throw new NoCalcsWorkedException( track );
}
Orn Kristjansson
la source
4
Comment cela aide-t-il? si calc1 () échoue, cals2 ne sera jamais exécuté!
DeCaf
cela ne résout pas le problème. Exécuter uniquement calc1 si calc2 échoue, exécuter uniquement calc3 si calc1 && calc2 échouent.
Jason
+1 orn. C'est ce que je fais. Je n'ai qu'à coder une capture, le message qui m'a été envoyé ( trackdans ce cas), et je sais exactement quel segment de mon code a causé l'échec du bloc. Peut-être auriez-vous dû élaborer pour dire à des membres comme DeCaf que le trackmessage est envoyé à votre routine de gestion d'erreur personnalisée qui vous permet de déboguer votre code. On dirait qu'il n'a pas compris votre logique.
jp2code
Eh bien, @DeCaf est correct, mon segment de code ne continue pas à exécuter la fonction suivante qui est ce que jjoelson a demandé, là pour ma solution n'est pas faisable
Orn Kristjansson