중첩 된 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 StringDate사용하여 구문 분석 SimpleDateFormat.parse해야하며 예외가 발생하면 다음으로 넘어 가서 순서대로 여러 가지 형식을 시도해야합니다.
비참한 변수

답변:


125

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

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

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

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

throw new NoCalcsWorkedException();

15
이것은 그 가정 Calc1Exception, Calc2ExceptionCalc3Exception공통 기본 클래스를 공유 할 수 있습니다.
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.40val.

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.