중첩 된 try catch 블록을 피하기위한 패턴?


114

계산을 수행하는 방법이 세 가지 (또는 그 이상)가있는 상황을 생각해보십시오. 각 방법은 예외로 인해 실패 할 수 있습니다. 성공할 때까지 각 계산을 시도하기 위해 다음을 수행했습니다.

double val;

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

더 좋은 방법으로 이것을 달성하는 허용되는 패턴이 있습니까? 물론 실패시 null을 반환하는 도우미 메서드로 각 계산을 래핑 한 다음 ??연산자를 사용할 수 있지만보다 일반적으로이 작업을 수행하는 방법이 있습니다 (즉, 사용하려는 각 메서드에 대한 도우미 메서드를 작성할 필요없이 )? 주어진 메서드를 try / catch에 래핑하고 실패시 null을 반환하는 제네릭을 사용하여 정적 메서드를 작성하는 것에 대해 생각했지만 어떻게해야할지 모르겠습니다. 어떤 아이디어?


계산에 대한 세부 정보를 포함 할 수 있습니까?
— James Johnson

2
그들은 기본적으로 PDE를 풀거나 근사화하는 다른 방법입니다. 타사 라이브러리에서 가져온 것이므로 오류 코드 또는 null을 반환하도록 변경할 수 없습니다. 내가 할 수있는 최선의 방법은 방법으로 각각을 개별적으로 포장하는 것입니다.
— jjoelson

계산 방법이 프로젝트의 일부입니까 (타사 라이브러리 대신)? 그렇다면 예외를 발생시키는 논리를 꺼내서 호출해야하는 계산 메서드를 결정하는 데 사용할 수 있습니다.
— 크리스

1
이것에 대한 또 다른 사용 사례가 Java에서 발견되었습니다. a String를 Date사용하여 구문 분석 SimpleDateFormat.parse해야하며 예외가 발생하면 다음으로 넘어 가서 순서대로 여러 가지 형식을 시도해야합니다.
— 비참한 변수

답변:


125

가능한 한 제어 흐름이나 예외적 인 상황에 예외를 사용하지 마십시오.

그러나 질문에 직접 답변하려면 (모든 예외 유형이 동일하다고 가정) :

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

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

throw new NoCalcsWorkedException();

15
이것은 그 가정 Calc1Exception, Calc2Exception및 Calc3Exception공통 기본 클래스를 공유 할 수 있습니다.
— Wyzard

3
맨 위에 그는 공통 서명을 가정합니다. 이는 그리 멀지 않습니다. 좋은 답변.
— TomTom

1
또한 계산이 작동 할 때 루프가 종료되도록 continuecatch 블록과 catch 블록 break뒤에 추가 했습니다 (이 비트에 대해
— Lirik

6
"가능한 한"대신 "절대적"을 사용했지만 "제어 흐름에 예외를 사용하지 마십시오"라고 표시되기 때문에 +1합니다.
— Bill K

1
@jjoelson :의 break다음 문 calc();내에서 try(도없고 continue전혀 문) 좀 더 관용적 수 있습니다.
— Adam Robinson

38

"상자 밖"대안을 제공하기 위해 재귀 함수는 어떻습니까?

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

참고 :이 최선의 방법 작업을 수행하려면이 말하는 결코 오전, 단지 다른 방법


6
"On Error Resume Next"를 언어로 한 번 구현해야했는데 생성 한 코드가 이와 비슷해 보였습니다.
— Jacob Krall

4
for 루프를 생성하기 위해 switch 문을 사용하지 마십시오.
— Jeff Ferland

3
루핑을위한 스위치 문을 가지고 유지 보수가 아니다
— 모하메드 아베드

1
내 대답이 가장 효율적인 코드가 아니라는 것을 알고 있지만 이러한 종류의 시도 / 캐치 블록을 다시 사용하는 것은 어쨌든 가장 좋은 방법이 아닙니다. 불행히도 OP는 타사 라이브러리를 사용하고 있으며 성공을 보장하기 위해 최선을 다해야합니다. 이상적으로는 입력을 먼저 검증하고 올바른 계산 기능을 선택하여 실패하지 않도록 할 수 있습니다. 물론 안전을 위해 모든 것을 try / catch에 넣을 수 있습니다.)
— musefan

1
return DoCalc(c++)다음과 동일합니다 return DoCalc(c)-증가 된 후 값은 더 깊이 전달되지 않습니다. 작동하게 만들고 더 많은 모호함을 도입하려면 더 비슷할 수 있습니다 return DoCalc((c++,c)).
— Artur Czajka

37

다음과 같은 메서드에 넣어 중첩을 평평하게 만들 수 있습니다.

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

그러나 실제 디자인 문제는 본질적으로 동일한 작업을 수행하지만 (호출자의 관점에서) 서로 다른 관련없는 예외를 던지는 세 가지 다른 메서드의 존재 라고 생각합니다 .

이것은 세 가지 예외 가 관련 이 없다고 가정합니다 . 모두 공통 기본 클래스를 가지고 있다면 Ani가 제안한 것처럼 단일 catch 블록이있는 루프를 사용하는 것이 좋습니다.


1
+1 : 이것은 문제에 대한 가장 깨끗하고 말도 안되는 해결책입니다. 내가 여기에서 보는 다른 솔루션은 IMO입니다. OP가 말했듯이 그는 API를 작성하지 않았기 때문에 throw되는 예외에 갇혀 있습니다.
— Nate CK

19

예외를 기반으로 논리를 제어하지 마십시오. 예외는 예외적 인 경우에만 throw되어야합니다. 대부분의 경우 계산은 외부 리소스에 액세스하거나 문자열을 구문 분석하지 않는 한 예외를 throw하지 않아야합니다. 어쨌든 최악의 경우 TryMethod 스타일 (예 : TryParse ())을 따라 예외 논리를 캡슐화하고 제어 흐름을 유지 관리 가능하고 깔끔하게 만듭니다.

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

Try 패턴을 사용하고 다음과 같은 경우 중첩 된 메서드 목록을 결합하는 또 다른 변형 :

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;

foreach가 약간 복잡하다면 간단하게 만들 수 있습니다.

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

솔직히 이것이 불필요한 추상화를 유발한다고 생각합니다. 이것은 끔찍한 해결책은 아니지만 대부분의 경우에 이것을 사용하지 않을 것입니다.
— user606723

예외 유형에 신경 쓰지 않고 조건부 코드 만 처리하고 싶은 경우 .. 따라서 성공 여부를 반환하는 조건부 메서드로 변환하는 것이 추상화 및 유지 관리 측면에서 더 좋습니다. 명확한 설명 메서드를 사용하여 복잡한 구문을 처리하는 예외를 숨기면 .. 코드가 일반 조건부 메서드로 처리합니다.
— Mohamed Abed 2011 년

나는 요점을 알고 있으며 유효합니다. 그러나 이러한 유형의 추상화 (지저분 함 / 복잡함 숨기기)를 거의 모든 곳에서 사용하면 우스꽝스러워지고 소프트웨어를 이해하는 것이 훨씬 더 어려워집니다. 내가 말했듯이 끔찍한 해결책은 아니지만 가볍게 사용하지는 않을 것입니다.
— user606723

9

계산 함수에 대한 대리자 목록을 만든 다음 while 루프를 사용하여 순환합니다.

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?

그것은 당신에게 그것을하는 방법에 대한 일반적인 아이디어를 줄 것입니다 (즉, 정확한 해결책이 아닙니다).


1
물론 당신이 일반적으로 결코 잡을 수 없다는 사실을 제외하고 Exception. ;)
— DeCaf

내가 말했듯이 @DeCaf : "그것을 수행하는 방법에 대한 일반적인 아이디어를 제공해야합니다 (즉, 정확한 솔루션이 아님)." 따라서 OP는 적절한 예외가 발생하는 모든 것을 잡을 수 있습니다 ... generic을 잡을 필요가 없습니다 Exception.
— Kiril

그래, 미안해, 그걸 밖으로 나가야한다고 느꼈어.
— DeCaf

1
@DeCaf, 모범 사례에 익숙하지 않은 사람들에게 유효한 설명입니다. 감사합니다 :)
— 키릴

9

이건 직업 같네요 ... MONADS! 특히 Maybe 모나드입니다. 여기에 설명 된대로 Maybe 모나드 로 시작 합니다 . 그런 다음 몇 가지 확장 방법을 추가하십시오. 설명하신대로 문제에 대해 특별히 이러한 확장 방법을 작성했습니다. 모나드의 좋은 점은 상황에 필요한 정확한 확장 메소드를 작성할 수 있다는 것입니다.

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

다음과 같이 사용하십시오.

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

이러한 종류의 계산을 자주 수행하는 경우 모나드는 코드의 가독성을 높이면서 작성해야하는 상용구 코드의 양을 줄여야합니다.


2
저는이 솔루션을 좋아합니다. 그러나 이전에 모나드에 노출 된 적이없는 사람에게는 상당히 불투명합니다. 즉, 이는 C #의 관용적이지 않습니다. 미래에이 어리석은 코드 조각을 수정하기 위해 동료 중 한 명이 모나드를 배우는 것을 원하지 않습니다. 그래도 나중에 참조하기에 좋습니다.
— jjoelson

1
유머 감각에 +1,이 문제에 대해 가능한 가장 둔하고 장황한 해결책을 작성한 다음 "코드의 가독성을 높이면서 작성해야하는 상용구 코드의 양을 줄입니다"라고 말합니다.
— Nate CK

1
이봐, 우리는 System.Linq에 숨어있는 엄청난 양의 코드에 대해 불평하지 않고 하루 종일 즐겁게 모나드를 사용합니다. @ fre0n은 툴킷에 Maybe 모나드를 기꺼이 넣으려는 경우 이러한 종류의 연쇄 평가를보고 추론하기가 더 쉬워진다는 의미라고 생각합니다. 잡기 쉬운 몇 가지 구현이 있습니다.
— Sebastian Good

사용 Maybe한다고해서 모나 딕 솔루션이되는 것은 아닙니다. 의 모나 딕 속성을 0으로 사용 Maybe하므로 null. 게다가, "monadicly"사용이는 것 역 의 Maybe. 실제 모나 딕 솔루션은 첫 번째 비 예외적 값을 상태로 유지하는 상태 모나드를 사용해야하지만 정상적인 체인 평가가 작동 할 때는 과잉입니다.
— Dax Fohl

7

try 메소드 접근법 의 또 다른 버전입니다 . 이것은 각 계산에 대한 예외 유형이 있기 때문에 유형화 된 예외를 허용합니다.

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

&&대신 각 조건 사이 에 사용하여 중첩 된 if를 실제로 피할 수 있습니다.
— DeCaf

4

Perl에서 할 수 있습니다 . 실패하면 foo() or bar()실행 bar()됩니다 foo(). C #에서는이 "실패하면"구조를 볼 수 없지만이 목적으로 사용할 수있는 연산자가 있습니다 ??. 첫 번째 부분이 null 인 경우에만 계속되는 null-coalesce 연산자 입니다.

계산의 서명을 변경할 수 있고 예외를 래핑하거나 (이전 게시물에 표시된대로) null대신 반환하도록 다시 작성하면 코드 체인이 점점 더 짧아지고 읽기도 쉬워집니다.

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

나는 당신의 기능, 가치의 결과에 대해 다음 교체 사용 40.40에 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! */}

이 솔루션이 항상 적용 가능한 것은 아니라는 것을 알고 있지만 매우 흥미로운 질문을 제기했으며 스레드가 상대적으로 오래되었지만 수정을 할 수있을 때 고려할 가치가있는 패턴이라고 생각합니다.


1
그냥 "감사합니다"라고 말하고 싶어요. 나는 당신이 말하는 것을 구현하려고 노력했습니다 . 올바르게 이해했으면 좋겠습니다.
— AlexMelw

3

계산 방법에 매개 변수없는 동일한 서명이있는 경우이를 목록에 등록하고 해당 목록을 반복하고 메서드를 실행할 수 있습니다. 아마도 Func<double>"유형의 결과를 반환하는 함수"라는 의미 를 사용 하는 것이 더 나을 것 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

Task / ContinueWith를 사용하고 예외를 확인할 수 있습니다. 예쁘게 만드는 데 도움이되는 멋진 확장 방법은 다음과 같습니다.

    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

던져진 예외의 실제 유형이 중요하지 않은 경우 유형이없는 catch 블록을 사용할 수 있습니다.

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

항상 목록의 모든 함수를 호출하지 않습니까? if(succeeded) { break; }포스트 캐치 와 같은 것에 던지고 싶을 수도 있습니다 (의도하지 않은 말장난) .
— 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;
        }
    }
}

다음과 같이 사용하십시오.

using Utility;

...

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

1

OP의 의도는 자신의 문제를 해결하기위한 좋은 패턴을 찾고 그 순간 그가 고심하고있는 현재 문제를 해결하는 것이었던 것 같습니다.

OP : "각 계산을 실패시 null을 반환하는 도우미 메서드로 래핑 한 다음 ??연산자를 사용할 수 있지만보다 일반적으로이 작업을 수행하는 방법이 있습니다 (예 : 원하는 각 메서드에 대한 도우미 메서드를 작성하지 않아도 됨). try / catch에서 주어진 메서드를 래핑하고 실패시 null을 반환하는 제네릭을 사용하여 정적 메서드를 작성하는 것에 대해 생각했지만 어떻게해야할지 모르겠습니다. 어떤 아이디어라도 있습니까? "

이 피드에 게시 된 중첩 된 try catch 블록을 피하는 좋은 패턴을 많이 보았지만 위에서 언급 한 문제에 대한 해결책을 찾지 못했습니다. 그래서 여기에 해결책이 있습니다.

OP에서 언급했듯이 그는 실패시 반환null 되는 래퍼 객체를 만들고 싶었습니다 . 나는 그것을 pod ( Exception-safe pod )라고 부를 것입니다.

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;

하지만 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}";
}

따라서 "사용하려는 각 메서드에 대한 도우미 메서드를 작성"할 필요가 없음을 알 수 있습니다 .

포드의 두 가지 유형 (대한 ValueTypeResult의과 ReferenceTypeResult들)이다 충분 .


다음은의 코드입니다 SafePod. 그래도 컨테이너가 아닙니다. 대신 s 및 s 모두에 대해 예외 안전 대리자 래퍼 를 만듭니다 .ValueTypeResultReferenceTypeResult

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

이것이 바로 일류 시민 엔티티 ( s) ??의 힘과 결합 된 널 통합 연산자를 활용하는 방법 입니다.delegate


0

각 계산을 래핑하는 것은 옳지 만 tell-don't-ask-principle에 따라 래핑해야합니다.

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

작업은 간단하며 독립적으로 동작을 변경할 수 있습니다. 그리고 그들이 왜 기본이되는지는 중요하지 않습니다. 증명으로 calc1DefaultingToCalc2를 다음과 같이 구현할 수 있습니다.

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

-1

계산 자체가 계산보다 더 많은 유효한 정보를 반환하는 것 같습니다. 자신의 예외 처리를 수행하고 오류 정보, 값 정보 등을 포함하는 "결과"클래스를 반환하는 것이 더 합리적 일 수 있습니다. AsyncResult 클래스가 비동기 패턴을 따르는 것처럼 생각하십시오. 그런 다음 계산의 실제 결과를 평가할 수 있습니다. 계산이 실패하면 마치 통과하는 것처럼 정보를 제공한다고 생각하여이를 합리화 할 수 있습니다. 따라서 예외는 "오류"가 아니라 정보의 일부입니다.

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

...

물론 이러한 계산을 수행하는 데 사용하는 전체 아키텍처에 대해 더 궁금합니다.


이것은 괜찮습니다-OP가 계산을 래핑하는 한 제안한 것과 유사합니다. while (!calcResult.HasValue) nextCalcResult()Calc1, Calc2, Calc3 등의 목록 대신, 같은 것을 선호합니다 .
— Kirk Broadhurst

-3

당신의 행동을 추적하는 것은 어떻습니까?

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
이것이 어떻게 도움이됩니까? calc1 ()이 실패하면 cals2가 실행되지 않습니다!
— DeCaf

이것은 문제를 해결하지 못합니다. calc2가 실패하면 calc1 만 실행하고, calc1 && calc2가 실패하면 calc3 만 실행합니다.
— 제이슨

+1 orn. 이것이 제가하는 것입니다. 나는 단지 하나의 캐치, 나에게 보낸 메시지 ( track이 경우) 만 코딩하면되고, 내 코드의 어떤 세그먼트로 인해 블록이 실패 했는지 정확히 알고있다 . DeCaf와 같은 회원들에게 track메시지가 코드를 디버깅 할 수있는 사용자 지정 오류 처리 루틴으로 전송 된다는 사실을 알려주도록 정교하게 작성 했어야합니다 . 그가 당신의 논리를 이해하지 못한 것 같습니다.
— jp2code

음, @DeCaf이 올바른지, jjoelson이 요구 것입니다 다음 기능을 실행 유지하지 않습니다 내 코드 세그먼트는, feasable이 내 솔루션을하지 않습니다
— 오른 아날 Kristjansson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.