ネストされたtry catchブロックを回避するパターン?


114

計算を実行する方法が3つ(またはそれ以上)あり、それぞれ例外が発生して失敗する状況を考えてみます。成功するものが見つかるまで各計算を試行するために、私は次のことを行っています:

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を返すジェネリックスを使用して静的メソッドを作成することを考えましたが、どうすればよいかわかりません。何か案は?


計算に関する詳細を含めることができますか?
ジェームズジョンソン

2
それらは基本的に、PDEを解く/近似するための異なる方法です。これらはサードパーティのライブラリのものなので、エラーコードやnullを返すように変更することはできません。私ができる最善の方法は、それぞれをメソッドに個別にラップすることです。
jjoelson、2011年

Calcメソッドはプロジェクトの一部ですか(サードパーティのライブラリではなく)?その場合、例外をスローするロジックを引き出し、それを使用して、呼び出す必要のあるcalcメソッドを決定できます。
Chris

1
このため、別のユースケースは、私はJavaで遭遇していることがあります-私は解析する必要StringDate使用してSimpleDateFormat.parse、私は1つが例外をスローするとき、次の上を移動する、ために、いくつかの異なるフォーマットを試してみる必要があります。
悲惨な変数

回答:


125

可能な限り、制御フローや例外のない状況では例外を使用しないでください。

ただし、質問に直接回答するには(すべての例外タイプが同じであると仮定):

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

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

throw new NoCalcsWorkedException();

15
これは、を想定しCalc1ExceptionCalc2Exceptionし、Calc3Exception共通の基底クラスを共有しています。
Wyzard

3
さらに、彼は一般的な署名を前提としています-これは実際にはそれほど遠くないです。良い答え。
TomTom

1
また、continuecatchブロックとcatchブロックのbreak後に追加して、計算が機能したときにループが終了するようにしました(このビットについてLirikに感謝)
jjoelson

6
「可能な限り」ではなく「絶対に使用しない」を使用したとしても、「制御フローに例外を使用しない」と表示されているため、+ 1のみ。
Bill K

1
@jjoelson:(内のステートメントがない)内でbreak続くステートメントは、もう少し慣用的かもしれません。calc();trycontinue
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"を言語で1回実装する必要があり、生成したコードは次のようになりました。
ジェイコブクラル2011年

4
forステートメントを使用してforループを作成しないでください。
ジェフファーランド、2011年

3
ループ用のswitchステートメントを作成することは維持できません
Mohamed Abed

1
私の答えは最も効率的なコードではないことはわかっていますが、この場合もtry / catchブロックを使用することは、とにかく最善の方法ではありません。残念ながら、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();
}

しかし、私は疑います 本当設計上の問題は、(呼び出し側の観点から)本質的に同じことを行うが、無関係な例外をスローする3つの異なるメソッドの存在にあると思います。

これは、3つの例外無関係であることを前提としています。それらすべてに共通の基本クラスがある場合、Aniが提案したように、単一のcatchブロックでループを使用することをお勧めします。


1
+1:これは、この問題に対する最もクリーンで最も実用的なソリューションです。私がここで見る他の解決策は、かわいくなろうとしている、IMOです。OPが言ったように、彼はAPIを書かなかったので、スローされた例外で立ち往生しています。
Nate CK、

19

例外に基づいてロジックを制御しないようにしてください。また、例外は例外的な場合にのみスローする必要があることにも注意してください。ほとんどの場合、計算は、外部リソースにアクセスしたり、文字列を解析したりしない限り、例外をスローすべきではありません。とにかく、最悪の場合は、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

私はポイントを知っています、そしてそれらは有効です。ただし、このタイプの抽象化(乱雑さ/複雑さを隠す)をほぼすべての場所で使用すると、ばかげてしまい、ソフトウェアの一部を理解することがはるかに困難になります。私が言ったように、それはひどい解決策ではありませんが、私はそれを軽く使用しません。
user606723 '17 / 10/17

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 2011年

私が言ったように@DeCaf:「それをする方法の一般的な考えを与えるべきです(すなわち、正確な解決策ではありません」)。したがって、OPは、適切な例外が発生した場合にキャッチできますException。ジェネリックをキャッチする必要はありません。
Kiril、

ええ、申し訳ありませんが、それをそこに出す必要性を感じました。
DeCaf 2011年

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、2011年

1
ユーモアのセンス、この問題に対して可能な限り鈍くて冗長なソリューションを記述し、「コードの読みやすさを向上させながら、記述しなければならないボイラープレートコードの量を削減する」ための+1。
Nate CK

1
ねえ、私たちはSystem.Linqに潜む膨大な量のコードについて文句を言わず、一日中それらのモナドを楽しく使っています。@ fre0nが意味するのは、ツールキットにMaybeモナドを入れてもかまわない場合、これらの種類の連鎖評価が見やすくなり、理由付けが容易になるということです。簡単に入手できる実装はいくつかあります。
Sebastian Good

使用Maybeしているからといって、これがモナディックソリューションになるわけではありません。それは0のモナドプロパティを使用しないMaybeので、だけを使用することもできますnull。さらに、「モナディックに」使用すると、これはのになりMaybeます。実際のモナディックソリューションでは、最初の例外以外の値を状態として保持するStateモナドを使用する必要がありますが、通常の連鎖評価が機能する場合は過剰です。
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 2011年

4

Perl foo() or bar()では、失敗した場合に実行bar()することfoo()ができます。C#では、この「if fail、then」構文は表示されませんが、この目的で使用できる演算子があります。null-coalesce演算子??は、最初の部分がnullの場合にのみ続行されます。

計算のシグネチャを変更でき、それらの例外をラップするか(前の投稿に示されているように)、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 2018年

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、2011年

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を返すヘルパーメソッドで各計算をラップし、??演算子を使用することもできますが、これをより一般的に行う方法があります(つまり、目的の各メソッドのヘルパーメソッドを記述する必要はありません。 use)?try / catchで特定のメソッドをラップし、失敗するとnullを返すジェネリックを使用して静的メソッドを書くことを考えましたが、これについてどうすればよいかわかりません。

このフィードに投稿された入れ子になったtry catchブロックを回避する良いパターンがたくさんありましたが、上記の問題の解決策は見つかりませんでした。だから、ここに解決策があります:

上記のOPのように、彼は失敗時に戻るnullラッパーオブジェクト作成したいと考えてました。私はそれをポッド例外セーフポッド)と呼びます。

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

したがって、「使用するメソッドごとにヘルパーメソッドを記述する必要がないことに気付くかもしれません。

ポッドの2種類(のためのValueTypeResultSとReferenceTypeResultS)がある十分


これがのコードですSafePod。ただし、コンテナではありません。代わりに、との両方に例外セーフなデリゲートラッパー作成します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;
    }
}

このようにして、ヌル結合演算子??ファーストクラスの市民エンティティ(delegates)の能力と組み合わせて活用できます。


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 2011年

これは問題を解決しません。calc2が失敗した場合にのみcalc1を実行し、calc1 && calc2が失敗した場合にのみcalc3を実行します。
Jason、

+1 orn。これが私がすることです。私は1つの catch、つまり私に送信されるメッセージ(trackこの場合)をコーディングするだけでよく、コード内のどのセグメントがブロックを失敗させたのかを正確に把握しています。DeCafなどのメンバーにtrack、コードをデバッグできるカスタムエラー処理ルーチンにメッセージが送信されることを伝えるように工夫する必要があるかもしれません。彼はあなたの論理を理解していないように聞こえます。
jp2code

まあ、@ DeCafは正しいです。私のコードセグメントは、jjoelsonが要求した次の関数を実行し続けません。そこでは、私の解決策は実行不可能です
Orn Kristjansson
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.