¿Patrón para evitar bloques de captura de prueba anidados?


114

Considere una situación en la que tengo tres (o más) formas de realizar un cálculo, cada una de las cuales puede fallar con una excepción. Para intentar cada cálculo hasta encontrar uno que tenga éxito, he estado haciendo lo siguiente:

double val;

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

¿Existe algún patrón aceptado que logre esto de una manera más agradable? Por supuesto, podría envolver cada cálculo en un método auxiliar que devuelve un valor nulo en caso de falla, y luego simplemente usar el ??operador, pero ¿hay alguna forma de hacer esto de manera más general (es decir, sin tener que escribir un método auxiliar para cada método que quiero usar? )? He pensado en escribir un método estático usando genéricos que envuelva cualquier método dado en un try / catch y devuelva nulo en caso de falla, pero no estoy seguro de cómo lo haría. ¿Algunas ideas?


¿Puede incluir algunos detalles sobre el cálculo?
James Johnson

2
Básicamente, son solo métodos diferentes para resolver / aproximar un PDE. Son de una biblioteca de terceros, por lo que no puedo modificarlos para devolver códigos de error o nulos. Lo mejor que pude hacer es envolver cada uno individualmente en un método.
jjoelson

¿Son los métodos calc parte de su proyecto (en lugar de una biblioteca de terceros)? Si es así, puede extraer la lógica que genera excepciones y usarla para decidir qué método calc debe llamarse.
Chris

1
Hay otro caso de uso para esto con el que me he encontrado en Java: necesito analizar Stringun Dateuso SimpleDateFormat.parsey necesito probar varios formatos diferentes en orden, pasando al siguiente cuando uno lanza una excepción.
Variable miserable

Respuestas:


125

En la medida de lo posible, no use excepciones para el flujo de control o circunstancias no excepcionales.

Pero para responder a su pregunta directamente (asumiendo que todos los tipos de excepción son los mismos):

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

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

throw new NoCalcsWorkedException();

15
Esto supone que Calc1Exception, Calc2Exceptiony Calc3Exceptioncomparten una clase base común.
Wyzard

3
Además, asume una firma común, que en realidad no está tan lejos. Buena respuesta.
TomTom

1
Además, agregué continueen el bloque de captura y breakdespués del bloque de captura para que el ciclo finalice cuando funcione un cálculo (gracias a Lirik por este bit)
jjoelson

6
+1 solo porque dice "No use excepciones para el flujo de control", aunque habría usado "NUNCA NUNCA" en lugar de "Hasta donde sea posible".
Bill K

1
@jjoelson: Una breakdeclaración a continuación calc();dentro de try(y ninguna continuedeclaración en absoluto) podría ser un poco más idiomática.
Adam Robinson

38

Solo para ofrecer una alternativa "fuera de la caja", ¿qué tal una función recursiva ...

//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);
   }
}

NOTA: De ninguna manera estoy diciendo que esta sea la mejor manera de hacer el trabajo, solo de una manera diferente


6
Tuve que implementar "On Error Resume Next" en un idioma una vez, y el código que generé se parecía mucho a esto.
Jacob Krall

4
Por favor, nunca use una declaración de cambio para crear un bucle for.
Jeff Ferland

3
No se puede mantener una declaración de cambio para el bucle
Mohamed Abed

1
Sé que mi respuesta no es el código más eficiente, pero nuevamente, usar bloques try / catch para este tipo de cosas no es la mejor manera de hacerlo de todos modos. Desafortunadamente, el OP está utilizando una biblioteca de terceros y debe hacer lo mejor que pueda para garantizar el éxito. Idealmente, la entrada podría validarse primero y la función de cálculo correcta elegida para garantizar que no fallará; por supuesto, luego podría poner todo eso en un intento / captura solo para estar seguro;)
musefan

1
return DoCalc(c++)es equivalente a return DoCalc(c): el valor posincrementado no se pasará más profundamente. Para que funcione (e introduzca más oscuridad) podría ser más parecido return DoCalc((c++,c)).
Artur Czajka

37

Puede aplanar el anidamiento poniéndolo en un método como este:

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();
}

Pero sospecho que lo real problema diseño es la existencia de tres métodos diferentes que hacen esencialmente lo mismo (desde la perspectiva de la persona que llama) pero arrojan excepciones diferentes y no relacionadas.

Esto es asumiendo que las tres excepciones no están relacionadas. Si todos tienen una clase base común, sería mejor usar un bucle con un solo bloque de captura, como sugirió Ani.


1
+1: Esta es la solución más limpia y sensata al problema. Las otras soluciones que veo aquí solo intentan ser lindas, en mi opinión. Como dijo el OP, no escribió la API, por lo que está atascado con las excepciones que se lanzan.
Nate CK

19

Trate de no controlar la lógica basada en excepciones; tenga en cuenta también que las excepciones deben lanzarse sólo en casos excepcionales. En la mayoría de los casos, los cálculos no deben generar excepciones a menos que accedan a recursos externos o analicen cadenas o algo así. De todos modos, en el peor de los casos, siga el estilo TryMethod (como TryParse ()) para encapsular la lógica de excepción y hacer que su flujo de control sea mantenible y limpio:

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);

Otra variación que utiliza el patrón Try y combina la lista de métodos en lugar de anidados 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;

y si el foreach es un poco complicado, puedes aclararlo:

        foreach (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }

Honestamente, creo que esto solo causa abstracciones innecesarias. Esta no es una solución horrible, pero no la usaría en la mayoría de los casos.
user606723

Si no le importa el tipo de excepción y solo desea manejar el código condicional ... por lo tanto, definitivamente es mejor en términos de abstracción y mantenibilidad convertirlo en un método condicional con retorno, ya sea que tenga éxito o no, de esta manera oculta la excepción que maneja la sintaxis desordenada con un método descriptivo claro ... entonces su código lo manejará ya que es un método condicional regular.
Mohamed Abed

Conozco los puntos y son válidos. Sin embargo, cuando se usa este tipo de abstracción (ocultar desorden / complejidad) en casi todas partes, se vuelve ridículo y entender qué es una pieza de software se vuelve mucho más difícil. Como dije, no es una solución terrible, pero no la usaría a la ligera.
user606723

9

Cree una lista de delegados para sus funciones de cálculo y luego tenga un ciclo while para recorrerlas:

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?

Eso debería darle una idea general de cómo hacerlo (es decir, no es una solución exacta).


1
Excepto, por supuesto, por el hecho de que normalmente nunca debería contagiarse Exceptionentonces. ;)
DeCaf

@DeCaf como dije: eso "debería darte una idea general de cómo hacerlo (es decir, no una solución exacta)". Entonces, el OP puede detectar cualquier excepción apropiada ... no es necesario capturar el genérico Exception.
Kiril

Sí, lo siento, sentí la necesidad de sacar eso.
DeCaf

1
@DeCaf, es una aclaración válida para aquellos que no estén familiarizados con las mejores prácticas. Gracias :)
Kiril

9

Esto parece un trabajo para ... ¡MONADAS! Específicamente, la mónada Quizás. Comience con la mónada Quizás como se describe aquí . Luego agregue algunos métodos de extensión. Escribí estos métodos de extensión específicamente para el problema tal como lo describiste. Lo bueno de las mónadas es que puede escribir los métodos de extensión exactos necesarios para su situación.

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;
}

Úselo así:

[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 se encuentra haciendo este tipo de cálculos con frecuencia, la mónada quizás debería reducir la cantidad de código repetitivo que tiene que escribir mientras aumenta la legibilidad de su código.


2
Amo esta solucion Sin embargo, es bastante opaco para cualquiera que no haya tenido exposición previa a las mónadas, lo que significa que esto está muy lejos de ser idiomático en c #. No quisiera que uno de mis compañeros de trabajo aprendiera mónadas solo para modificar este código en el futuro. Sin embargo, esto es genial para referencia futura.
jjoelson

1
+1 por sentido del humor, por escribir la solución más obtusa y detallada posible a este problema y luego decir que "reducirá la cantidad de código repetitivo que tiene que escribir mientras aumenta la legibilidad de su código".
Nate CK

1
Oye, no nos quejamos de la enorme cantidad de código que acecha en System.Linq, y felizmente usamos esas mónadas todo el día. Creo que @ fre0n solo quiere decir que si estás dispuesto a poner la mónada Quizás en tu caja de herramientas, este tipo de evaluaciones encadenadas se vuelven más fáciles de ver y razonar. Hay varias implementaciones que son fáciles de agarrar.
Sebastian Good

El hecho de que utilice Maybeno la convierte en una solución monádica; usa cero de las propiedades monádicas de Maybey, por lo tanto, también puede usar null. Además, si se usa "monádicamente", sería el inverso de Maybe. Una solución monádica real tendría que usar una mónada de estado que mantenga el primer valor no excepcional como su estado, pero eso sería excesivo cuando la evaluación encadenada normal funciona.
Dax Fohl

7

Otra versión del enfoque del método try . Éste permite excepciones escritas, ya que hay un tipo de excepción para cada cálculo:

    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();
    }

En realidad, podría evitar los if anidados utilizando &&entre cada condición.
DeCaf

4

En Perl puedes hacerlo foo() or bar(), que se ejecutará bar()si foo()falla. En C # no vemos esta construcción "si falla, entonces", pero hay un operador que podemos usar para este propósito: el operador de coalescencia nula?? , que continúa solo si la primera parte es nula.

Si puede cambiar la firma de sus cálculos y si envuelve sus excepciones (como se muestra en publicaciones anteriores) o las reescribe para regresar null, su cadena de código se vuelve cada vez más breve y aún más fácil de leer:

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

Usé los siguientes reemplazos para sus funciones, lo que da como resultado el valor 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! */}

Me doy cuenta de que esta solución no siempre será aplicable, pero usted planteó una pregunta muy interesante y creo, aunque el hilo es relativamente antiguo, que este es un patrón que vale la pena considerar cuando pueda hacer las enmiendas.


1
Solo quiero decir "Gracias". Traté de implementar lo que estabas hablando . Espero haberlo entendido correctamente.
AlexMelw

3

Dado que los métodos de cálculo tienen la misma firma sin parámetros, puede registrarlos en una lista, iterar a través de esa lista y ejecutar los métodos. Probablemente sería incluso mejor para usted usar el Func<double>significado de "una función que devuelve un resultado de tipo 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);
   }
}

3

Puede usar Task / ContinueWith y verificar la excepción. Aquí hay un buen método de extensión para ayudar a que sea bonito:

    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);
    }
}

1

Si el tipo real de la excepción lanzada no importa, puede usar un bloque catch sin tipo:

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();

¿No llama eso siempre a todas las funciones de la lista? Es posible que desee lanzar (juego de palabras no intencionado) en algo como if(succeeded) { break; }una captura posterior.
a 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;
        }
    }
}

Y úsalo así:

using Utility;

...

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

1

Parece que la intención del OP era encontrar un buen patrón para resolver su problema y resolver el problema actual con el que estaba luchando en ese momento.

OP: "Podría envolver cada cálculo en un método auxiliar que devuelve nulo en caso de falla, y luego simplemente usar el ??operador, pero ¿hay alguna forma de hacer esto de manera más general (es decir, sin tener que escribir un método auxiliar para cada método que quiero uso)? He pensado en escribir un método estático usando genéricos que envuelva cualquier método dado en un try / catch y devuelva nulo en caso de falla, pero no estoy seguro de cómo lo haría. ¿Alguna idea? "

Vi muchos patrones buenos que evitan bloques de captura de prueba anidados , publicados en esta fuente, pero no encontré una solución al problema que se cita anteriormente. Entonces, aquí está la solución:

Como OP mencionó anteriormente, quería hacer un objeto contenedor que regrese nullen caso de falla . Yo lo llamaría una cápsula ( cápsula segura para excepciones ).

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;

Pero, ¿qué sucede si desea crear un pod seguro para un resultado de Tipo de referencia devuelto por las funciones / métodos de 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}";
}

Por tanto, es posible que observe que no es necesario "escribir un método auxiliar para cada método que desee utilizar" .

Los dos tipos de vainas (para ValueTypeResultsy ReferenceTypeResults) son suficientes .


Aquí está el código de SafePod. Sin embargo, no es un contenedor. En su lugar, crea un contenedor delegado seguro para excepciones tanto para ValueTypeResults como para 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;
    }
}

Así es como puede aprovechar el operador de fusión nula ??combinado con el poder de las entidades ciudadanas de primera clasedelegate .


0

Tiene razón acerca de ajustar cada cálculo, pero debe ajustarlo de acuerdo con el principio de decir-no-preguntar.

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();
    }
}

Las operaciones son simples y pueden cambiar su comportamiento de forma independiente. Y no importa por qué no lo hacen. Como prueba, podría implementar calc1DefaultingToCalc2 como:

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

-1

Parece que sus cálculos tienen más información válida para devolver que solo el cálculo en sí. Quizás tendría más sentido para ellos hacer su propio manejo de excepciones y devolver una clase de "resultados" que contiene información de error, información de valor, etc. Piense como lo hace la clase AsyncResult siguiendo el patrón asíncrono. A continuación, puede evaluar el resultado real del cálculo. Puede racionalizar esto pensando en términos de que si un cálculo falla, es tan informativo como si pasara. Por lo tanto, una excepción es un dato, no un "error".

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

...

Por supuesto, me pregunto más sobre la arquitectura general que está utilizando para realizar estos cálculos.


Esto está bien, similar a lo que OP sugirió en cuanto a envolver los cálculos. Preferiría algo como while (!calcResult.HasValue) nextCalcResult(), en lugar de una lista de Calc1, Calc2, Calc3, etc.
Kirk Broadhurst

-3

¿Qué pasa con el seguimiento de las acciones que está haciendo ...

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 );
}

4
¿Cómo ayuda esto? si calc1 () falla, cals2 nunca se ejecutará.
DeCaf

esto no resuelve el problema. Solo ejecute calc1 si calc2 falla, solo ejecute calc3 si calc1 && calc2 fallan.
Jason

+1 orn. Esto es lo que hago. Solo tengo que codificar una captura, el mensaje que se me envió ( tracken este caso), y sé exactamente qué segmento de mi código causó que el bloque fallara. Tal vez debería haber elaborado para decirle a miembros como DeCaf que el trackmensaje se envía a su rutina personalizada de manejo de errores que le permite depurar su código. Parece que no entendió tu lógica.
jp2code

Bueno, @DeCaf es correcto, mi segmento de código no sigue ejecutando la siguiente función, que es lo que pidió jjoelson, mi solución no es factible
Orn Kristjansson
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.