例外、エラーコード、差別化された組合


80

最近、C#プログラミングの仕事を始めましたが、Haskellにはかなりのバックグラウンドがあります。

しかし、私はC#がオブジェクト指向言語であることを理解しています。丸い釘を四角い穴に押し込みたくありません。

私は、Microsoftからの例外のスローに関する記事を読みました。

エラーコードを返さないでください。

しかし、Haskellに慣れているため、C#データ型を使用してOneOf、結果を「右」値として返すか、エラー(ほとんどの場合は列挙)を「左」値として返します。

これはEitherHaskell の慣習によく似ています。

私には、これは例外よりも安全に思えます。C#では、例外を無視してもコンパイルエラーは発生しません。例外がキャッチされない場合は、プログラムがバブルアップしてクラッシュします。これはおそらく、エラーコードを無視して未定義の動作を生成するよりも優れていますが、クライアントのソフトウェアをクラッシュさせることは、特にバックグラウンドで他の多くの重要なビジネスタスクを実行している場合、まだ良いことではありません。

を使用してOneOf、パッケージを展開し、戻り値とエラーコードを処理することを明確にする必要があります。呼び出しスタックのその段階でそれを処理する方法がわからない場合は、現在の関数の戻り値に入れる必要があるため、呼び出し元はエラーが発生する可能性があることを知っています。

しかし、これはMicrosoftが提案するアプローチではないようです。

OneOf「通常の」例外(File Not Foundなど)を処理するために例外の代わりに使用するのは合理的なアプローチですか、それともひどい習慣ですか?


制御フローとしての例外は深刻なアンチパターンと見なされていると聞いたことは注目に値します。したがって、「例外」がプログラムを終了せずに通常処理するものであれば、その「制御フロー」はある意味ではありませんか?ここに灰色の領域が少しあることを理解しています。

OneOf「メモリ不足」のようなものには使用していないことに注意してください。回復が期待できない条件でも例外がスローされます。しかし、解析されないユーザー入力は本質的に「制御フロー」であり、おそらく例外をスローすべきではないなど、かなり合理的な問題だと思います。


その後の考え:

この議論から、私が現在取り上げていることは次のとおりです。

  1. すぐに呼び出し元がcatch例外を処理し、ほとんどの場合例外を処理し、別のパスを介して作業を続行する場合は、おそらく戻り型の一部である必要があります。OptionalまたはOneOfここで役立ちます。
  2. すぐに呼び出し元がほとんどの場合例外をキャッチしないと予想される場合は、例外をスローして、手動でスタックに渡す手間を省きます。
  3. 直近の発信者が何をしようとしているのかわからない場合は、Parseとなどの両方を提供しTryParseます。

13
F#を使用しますResult;-)
アストリニアス

8
多少話題外ですが、あなたが見ているアプローチには、開発者がエラーを適切に処理することを保証する方法がないという一般的な脆弱性があることに注意してください。確かに、タイピングシステムはリマインダーとして機能しますが、エラーの処理に失敗したためにプログラムを爆破するデフォルトを失い、予期しない状態になる可能性が高くなります。ただし、リマインダーがそのデフォルトを失うだけの価値があるかどうかは、未解決の議論です。例外がある時点で終了すると仮定して、すべてのコードを記述する方が良いと思う傾向があります。その場合、リマインダーの価値は低くなります。
jpmc26

9
関連記事:例外の4つの「バケット」に関するEric Lippert。彼が外因性の欺 onに与えた例は、あなたがただ例外に対処しなければならないシナリオがあることを示していますFileNotFoundException
ソレンD.プテウス

7
私はこれで一人かもしれませんが、クラッシュは良いと思います。適切なトレースのクラッシュログよりも、ソフトウェアに問題があることを示すより良い証拠はありません。30分以内に修正プログラムを修正および展開できるチームがある場合、それらのい仲間を表示させることは悪いことではないかもしれません。
T.サー

9
どうして答えEitherモナドがエラーコードはなく、そうでもないことを明確にしないのですOneOfか?それらは根本的に異なっており、その結果、質問は誤解に基づいているようです。(変更された形式ではありますが、依然として有効な質問です。)
コンラッドルドルフ

回答:


131

しかし、クライアントのソフトウェアをクラッシュさせることはまだ良いことではありません

それは間違いなく良いことです。

未定義のシステムは、破損したデータなどの厄介なことを行い、ハードドライブをフォーマットし、大統領を脅かす電子メールを送信するため、システムを未定義の状態のままにするものが必要です。システムを回復して定義済みの状態に戻すことができない場合は、クラッシュが原因です。それがまさに、静かに分裂するのではなく、クラッシュするシステムを構築する理由です。確かに、私たちは皆、クラッシュしない安定したシステムを望んでいますが、システムが定義された予測可能な安全な状態にとどまっているときにのみ本当に望みます。

制御フローとしての例外は深刻なアンチパターンと見なされていると聞きました

それは絶対に真実ですが、それはしばしば誤解されます。彼らが例外システムを発明したとき、彼らは構造化プログラミングを壊しているのではないかと恐れていました。我々が持っている理由、構造化プログラミングがあるforwhileuntilbreak、およびcontinueすべての私たちが必要とするとき、そのすべてを行うには、ですgoto

ダイクストラは、gotoを非公式に使用する(つまり、好きなところにジャンプする)と、コードの読み取りが悪夢になることを教えてくれました。彼らは私たちに例外システムを与えたとき、彼らはgotoを再発明しているのではないかと恐れていました。それで、彼らは私たちが理解することを期待して「フロー制御のためにそれを使用しない」と言った。残念ながら、私たちの多くはそうしませんでした。

奇妙なことに、gotoで使用していたように、スパゲッティコードを作成するために例外を悪用することはあまりありません。アドバイス自体がより多くのトラブルを引き起こしたようです。

基本的に例外とは、仮定を拒否することです。ファイルを保存するように依頼するとき、ファイルは保存可能であり、保存されると想定します。名前が違法であること、HDがいっぱいであること、またはラットがデータケーブルをかじったことが原因である可能性がない場合に発生する例外です。これらすべてのエラーを別々に処理したり、すべて同じ方法で処理したり、システムを停止させたりすることができます。コードには、前提が当てはまらなければならない幸せな道があります。何らかの方法で例外があれば、その幸福な道から外れます。厳密に言えば、それは一種の「フロー制御」ですが、それは彼らがあなたに警告していたことではありません。彼らはこのようなナンセンスについて話していました:

ここに画像の説明を入力してください

「例外は例外的であるべきです」。この小さなトートロジーは、例外システムの設計者がスタックトレースを作成する時間が必要なために生まれました。飛び回るのに比べて、これは遅いです。CPU時間を消費します。ただし、システムをログに記録して停止しようとしている場合、または少なくとも次の処理を開始する前に現在の時間を集中的に使用する処理を停止しようとしている場合は、しばらく時間を空けてください。「フロー制御」のために例外を使用し始めた場合、時間に関するこれらの仮定はすべて窓から消えます。そのため、パフォーマンスの考慮事項として「例外は例外的である必要があります」ということが実際に示されました。

それよりもはるかに重要なことは、私たちを混乱させないことです。上記のコードで無限ループを見つけるのにどれくらいかかりましたか?

エラーコードを返さないでください。

...通常エラーコードを使用しないコードベースを使用している場合は、良いアドバイスです。どうして?誰も戻り値を保存してエラーコードを確認することを忘れないからです。Cを使用しているときは、まだ良い規則です。

OneOf

さらに別の規則を使用しています。単に別の大会と戦うのではなく、大会を設定している限り、それは問題ありません。同じコードベースに2つのエラー規則があると混乱します。どういうわけか、他の規則を使用するすべてのコードを削除した場合は、先に進みます。

私はコンベンションが好きです。私がここで見つけた最高の説明の1つ*

ここに画像の説明を入力してください

しかし、私はそれが好きなように、私はまだ他の慣習とそれを混ぜることはありません。1つを選んでそれを使い続けます。1

1:つまり、複数のコンベンションについて同時に考えさせないでください。


その後の考え:

この議論から、私が現在取り上げていることは次のとおりです。

  1. 即時の呼び出し元がほとんどの場合例外をキャッチして処理し、おそらく別のパスを介して作業を続行することを期待している場合、おそらく戻り値型の一部である必要があります。ここでは、オプションまたはOneOfが便利です。
  2. すぐに呼び出し元がほとんどの場合例外をキャッチしないと予想される場合は、例外をスローして、手動でスタックに渡す手間を省きます。
  3. 直接の呼び出し元が何をするのかわからない場合は、ParseやTryParseのように両方を提供することもできます。

これは本当に簡単なことではありません。理解する必要がある基本的なことの1つは、ゼロが何であるかです。

5月の残り日数は?0(5月ではないため。すでに6月です)。

例外は仮定を拒否する方法ですが、唯一の方法ではありません。例外を使用して仮定を拒否する場合、幸せな道を離れます。しかし、物事が想定されたほど単純ではないことを示す幸福なパスを送信する値を選択した場合、それらの値を処理できる限り、そのパスに留まることができます。時には0が何かを意味するためにすでに使用されているため、別の値を見つけて、アイデアを拒否する仮定をマッピングする必要があります。あなたは古き良き代数での使用からこの考えを認識するかもしれません。モナドはそれを助けることができますが、必ずしもモナドである必要はありません。

2

IList<int> ParseAllTheInts(String s) { ... }

これが意図的に何かを投げるように設計しなければならない理由を考えることができますか?intを解析できないときに何が得られると思いますか?私もあなたに言う必要はありません。

それは良い名前のしるしです。申し訳ありませんが、TryParseは良い名前の私の考えではありません。

答えが同時に複数のものになる可能性がある場合、何も得られない場合に例外をスローすることは避けますが、何らかの理由で答えが単一のものであるか、または何もない場合、私たちはそれが私たちに1つのものを与えるか投げることに執着します:

IList<Point> Intersection(Line a, Line b) { ... }

平行線は本当にここで例外を引き起こす必要がありますか?このリストに複数のポイントが含まれないのは本当に悪いことですか?

たぶん意味的には、あなたはそれを取ることができません。もしそうなら、それは残念です。しかし、Monadsのように任意のサイズがListない場合は、気分が良くなるかもしれません。

Maybe<Point> Intersection(Line a, Line b) { ... }

モナドは、それらをテストする必要を回避する特定の方法で使用されることを意図した小さな特別な目的のコレクションです。それらに含まれるものに関係なく、それらに対処する方法を見つけることになっています。そうすれば、幸せな道はシンプルなままです。あなたが触ってすべてのMonadをクラックしてテストすると、間違った使い方をしていることになります。

変だね。しかし、それは新しいツールです(まあ、私たちにとって)。少し時間をおいてください。ハンマーは、ネジでの使用を停止するとより意味があります。


あなたが私にふけるなら、私はこのコメントに対処したいと思います:

どちらのモナドもエラーコードではなく、OneOfでもないことを明確にする答えはありません。それらは根本的に異なっており、その結果、質問は誤解に基づいているようです。(修正された形式ではありますが、依然として有効な質問です。)– コンラッド・ルドルフ 18年6月4日14:08

これは絶対に真実です。モナドは、例外、フラグ、エラーコードよりもコレクションにはるかに近いです。それらは賢明に使用されるときそのような物のための良い容器を作ります。


9
赤いトラックがボックスの下にあり、それらを通り抜けていない場合は、より適切ではないでしょうか?
フラット

4
論理的には、あなたは正しいです。ただし、この特定の図の各ボックスは、単項バインド操作を表します。bindは赤いトラックを含みます(おそらく作成します!)。プレゼンテーション全体をご覧になることをお勧めします。それは興味深く、スコットは素晴らしいスピーカーです。
ガスドール

7
例外は、GOTOではなくCOMEFROM命令のようなものだと思います。なぜなら、throw実際にどこにジャンプするかわからないからです;)
Warbo

3
境界を確立できれば、混合規則に問題はありません。これがカプセル化のすべてです。
ロバートハーベイ

4
この回答の最初のセクション全体は、引用文の後に質問を読むのをやめたかのように強く読みます。質問は、プログラムのクラッシュが未定義の動作に移行するよりも優れていることを認めています:継続的な未定義の実行ではなく、潜在的なエラーを考慮せずにプログラムをビルドすることさえできないコンパイル時エラーとランタイムクラッシュを対比していますそしてそれを処理します(他に何もすることがなければクラッシュするかもしれませんが、それはあなたがそれを忘れたからではなく、あなたが望んでいるからです)。
KRyan

35

C#はHaskellではないため、C#コミュニティの専門家のコンセンサスに従う必要があります。代わりに、C#プロジェクトでHaskellのプラクティスに従うことを試みると、チームの他の全員を疎外し、最終的には、C#コミュニティが異なる方法で行う理由を発見するでしょう。大きな理由の1つは、C#が差別化されたユニオンを便利にサポートしていないことです。

制御フローとしての例外は深刻なアンチパターンと見なされると聞きましたが、

これは、広く受け入れられている真実ではありません。例外をサポートするすべての言語での選択は、例外をスローする(呼び出し側が自由に処理できない)か、複合値を返す(呼び出し側が処理しなければならない)ことです。

コールスタックを介してエラー条件を上方に伝播するには、すべてのレベルで条件が必要であり、これらのメソッドの循環的な複雑さが倍になり、結果としてユニットテストケースの数が倍になります。典型的なビジネスアプリケーションでは、多くの例外はリカバリを超えており、最高レベル(Webアプリケーションのサービスエントリポイントなど)に伝播するために残すことができます。


9
モナドの伝播には何も必要ありません。
バシレフス

23
@Basilevsコードを読みやすく保ちながらモナドを伝播するためのサポートが必要です。これはC#にはありません。if-return命令またはラムダのチェーンが繰り返されます。
セバスチャンレッド

4
@SebastianRedlラムダはC#では問題なく見えます。
バシレフス

@Basilevs構文ビューからはい、しかしパフォーマンスの観点からはあまり魅力的ではないと思う。
ファラプ

5
現代のC#では、おそらくローカル関数を部分的に使用して、速度をいくらか戻すことができます。いずれにせよ、ほとんどのビジネスソフトウェアは、IOによって他よりはるかに遅くなります。
タークサラマ

26

興味深い時期にC#に来ました。比較的最近まで、この言語は命令型プログラミングの分野にしっかりと定着していました。エラーを伝えるために例外を使用することは、間違いなく標準でした。戻りコードの使用は、言語サポートの欠如に苦しんでいました(たとえば、差別化された共用体やパターンマッチングの周辺)。かなり合理的には、Microsoftからの公式ガイダンスは、リターンコードを回避し、例外を使用することでした。

ただし、これには例外が常にあり、TryXXXメソッドの形式で、boolean成功の結果を返し、outパラメーターを介して2番目の値の結果を提供します。これらは関数型プログラミングの「try」パターンに非常に似ていますが、結果がMaybe<T>戻り値ではなくoutパラメーターを介して送られることを除きます。

しかし、状況は変化しています。関数型プログラミングはさらに普及しており、C#のような言語が応答しています。C#7.0では、いくつかの基本的なパターンマッチング機能が言語に導入されました。C#8では、スイッチ式、再帰パターンなど、さらに多くの機能が導入されます。これに加えて、差別化された共用体もサポートする、独自のSuccinc <T>ライブラリなど、C#の「機能ライブラリ」が増加しました。

これら2つのことを組み合わせると、次のようなコードの人気が徐々に高まっています。

static Maybe<int> TryParseInt(string source) =>
    int.TryParse(source, out var result) ? new Some<int>(result) : none; 

public int GetNumberFromUser()
{
    Console.WriteLine("Please enter a number");
    while (true)
    {
        var userInput = Console.ReadLine();
        if (TryParseInt(userInput) is int value)
        {
            return value;
        }
        Console.WriteLine("That's not a valid number. Please try again");
    }
}

現時点ではまだかなりニッチですが、過去2年間でさえ、C#開発者の間での一般的な敵意からこうしたコードへの著しい変化から、こうした手法の使用に対する関心が高まっていることに気づきました。

私たちは言ってではまだ存在していない「しないでくださいエラーコードを返します。」時代遅れであり、引退する必要があります。しかし、その目標に向かって進む道は順調に進んでいます。それで、あなたの選択をしてください:それがあなたが物事をするのが好きな方法であるならば、同性愛の放棄で例外を投げる古い方法に固執してください。または、関数型言語の規則がC#で一般的になりつつある新しい世界の探索を開始します。


22
これが私がC#を嫌う理由です:)彼らは猫の皮を剥ぐ方法を追加し続けており、もちろん古い方法は決して消えません。最終的には何も慣習はありません。完璧主義者のプログラマーは何時間も座って、どちらの方法が「より良い」かを決定できます。
アレクサンドルドゥビンスキー

24
@ AleksandrDubinsky、C#だけでなく、一般的なプログラミング言語の中心的な問題に出くわしました。新しい言語は、無駄のない、新鮮で、現代のアイデアに満ちています。そしてそれらを使用する人はほとんどいません。時間が経つにつれて、既存のコードを壊してしまうために削除できない古い機能の上にユーザーと新しい機能が追加されます。むくみが大きくなります。だから、フォークは...、リーン新鮮で現代的なアイデアに満ちている新しい言語を作成する
デビッドアルノ

7
@AleksandrDubinskyは、1960年代の機能が追加されたときに嫌いではないでしょうか。
ctrl-alt-delor

6
@AleksandrDubinskyうん。Javaが一度(ジェネリック)の追加を試みたため失敗しました。今では、結果として
生じる

3
@Agent_L:Javaが追加されたjava.util.Stream(半ばIEnumerableに似た)ラムダと、ラムダを知っていますよね?:)
cHao

5

エラーを返すためにDUを使用することは完全に合理的だと思いますが、OneOfを書いたときにそれを言うでしょう:)しかし、とにかく、それはF#などの一般的な慣行です)。

私が通常返すエラーは例外的な状況とは思いませんが、代わりに通常の状況と見なされますが、処理される必要がある場合とモデル化される必要がある場合があります。

プロジェクトページの例を次に示します。

public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

これらのエラーに対して例外をスローすることは、やり過ぎのように思えます。メソッドシグネチャで明示的でないことや、完全な一致を提供することの不利な点を除き、パフォーマンスオーバーヘッドがあります。

OneOfがC#でどの程度慣用的であるかは、別の質問です。構文は有効であり、かなり直感的です。DUとレール指向プログラミングはよく知られた概念です。

可能であれば、何かが壊れていることを示すものについては、例外を保存します。


あなたが言っているのOneOfは、Nullableを返すように、戻り値をより豊かにすることです。最初から例外的ではない状況の例外を置き換えます。
アレクサンドルドゥビンスキー

はい-呼び出し元で徹底的なマッチングを行う簡単な方法を提供しますが、継承ベースの結果階層を使用することはできません。
mcintyre321

4

OneOfJavaのチェック例外とほぼ同等です。いくつかの違いがあります。

  • OneOf副作用のために呼び出されるメソッドを生成するメソッドでは機能しません(意味のある呼び出しが行われ、結果が単に無視される可能性があるため)。明らかに、純粋な関数の使用を試みる必要がありますが、これは常に可能とは限りません。

  • OneOf問題が発生した場所に関する情報を提供しません。これはパフォーマンスを節約し(Javaでスローされた例外はスタックトレースを埋めるためにコストがかかり、C#では同じになります)、エラーメンバOneOf自体に十分な情報を提供するように強制するため、良好です。また、この情報が不十分である可能性があり、問題の原因を見つけるのに苦労する可能性があるため、悪いことです。

OneOf チェック例外には、共通の重要な「機能」が1つあります。

  • 両方とも、呼び出しスタックのどこでもそれらを処理するように強制します

これはあなたの恐怖を防ぎます

C#では、例外を無視してもコンパイルエラーは発生しません。例外がキャッチされない場合は、プログラムがバブルアップしてクラッシュします。

しかし、すでに述べたように、この恐怖は非合理的です。基本的に、プログラムには通常、すべてをキャッチする必要のある場所が1つあります(フレームワークを使用して既にそれを実行している可能性があります)。あなたとあなたのチームは通常、プログラムに数週間または数年を費やすので、忘れないでしょうか?

無視できる例外の大きな利点は、スタックトレースのどこでもではなく、どこででも処理できることです。これにより、大量のボイラープレートが不要になり(「スロー....」を宣言するJavaコードを参照するか、例外をラップするだけです)、コードのバグが少なくなります。幸いなことに、正しいアクションは通常は何もしません。つまり、適切に処理できる場所までバブルします。


+1ですが、なぜOneOfは副作用で動作しないのですか?(私はわからないあなたは、戻り値を割り当てない場合、それはチェックを通過しますので、それはあると思いますが、私はoneofのかわからないよう、またかなりのC# - 。明確化は、あなたの答えを改善する)
dcorking

1
あなたは、有益な情報など含まれて返されたエラーオブジェクトを作ることによってoneofの持つエラー情報を返すことができ、エラーの詳細を提供します。OneOf<SomeSuccess, SomeErrorInfo>SomeErrorInfo
mcintyre321

@dcorking編集済み。もちろん、戻り値を無視した場合、どのような問題が発生したかはわかりません。純粋な関数でこれを行う場合、それは問題ありません(時間を無駄にしただけです)。
-maaartinus

2
@ mcintyre321確かに、情報を追加することはできますが、手動で行う必要があり、怠lazで忘れがちです。もっと複雑なケースでは、スタックトレースの方が役立つと思います。
maaartinus

4

「通常の」例外(File Not Foundなど)を処理するために例外の代わりにOneOfを使用するのは合理的なアプローチですか、それともひどい習慣ですか?

制御フローとしての例外は深刻なアンチパターンと見なされていると聞いたことは注目に値します。したがって、「例外」がプログラムを終了せずに通常処理するものであれば、その「制御フロー」はある意味ではありませんか?

エラーには多くの次元があります。防止できるか、発生する頻度、および回復できるかどうかの3つの側面を特定します。一方、エラーシグナリングには主に1つの側面があります。エラーを処理するために発信者を頭上で打ち負かす範囲を決定するか、例外としてエラーを「静かに」バブルアップさせるかを決定します。

防止できない、頻繁に発生する、回復可能なエラーは、通話サイトで本当に対処する必要があるものです。彼らは、発信者に彼らに立ち向かうことを強制することを最も正当化する。Javaでは、チェック例外を作成します。同様の効果はTry*、メソッドを作成するか、識別された共用体を返すことで実現されます。識別された共用体は、関数に戻り値がある場合に特に便利です。それらは返品のより洗練されたバージョンですnulltry/catchブロックの構文は素晴らしいものではなく(括弧が多すぎる)、代替案の見栄えが良くなります。また、例外はスタックトレースを記録するため、わずかに遅くなります。

防止可能なエラー(プログラマーのエラー/過失のために防止されなかった)と回復不能なエラーは、通常の例外と同様に非常にうまく機能します。まれなエラーは通常の例外としても機能します。これは、プログラマーが予測する努力に値しないことが多いためです(プログラムの目的によって異なります)。

重要な点は、多くの場合、メソッドのエラーモードがこれらの次元にどのように適合するかを決定するのは使用サイトであるということです。これは、以下を考慮するときに留意する必要があります。

私の経験では、エラーが回避不可能、頻繁、および回復可能な場合、エラー状態に直面するように発信者を強制することは問題ありませんが、発信者が必要または不要なときにフープをジャンプするように強制されると非常に迷惑になります。これは、呼び出し元がエラーが発生しないことを知っているか、コードを(まだ)回復力を持たせたくないためです。Javaでは、これにより、チェック済み例外の頻繁な使用とOptionalを返すことに対する不安が説明されます(これはMaybeに似ており、最近多くの論争の中で導入されました)。疑わしい場合は、例外をスローし、呼び出し元にそれらの処理方法を決定させます。

最後に、エラー状態に関する最も重要なことは、それらがどのように通知されるかなどの他の考慮事項よりもはるかに重要であり、徹底的文書化することです


2

他の回答では、例外とエラーコードについて十分に詳細に説明しているため、質問に固有の別の観点を追加したいと思います。

OneOfはエラーコードではなく、モナドのようなものです

使用方法はOneOf<Value, Error1, Error2>、実際の結果または何らかのエラー状態を表すコンテナです。Optional値が存在しない場合、それが理由をより詳細に提供できることを除いて、に似ています。

エラーコードの主な問題は、チェックを忘れることです。しかし、ここでは、エラーをチェックしないと結果にアクセスできません。

唯一の問題は、結果を気にしないときです。OneOfのドキュメントの例は次のとおりです。

public OneOf<User, InvalidName, NameTaken> CreateUser(string username) { ... }

...そして、結果ごとに異なるHTTP応答を作成します。これは、例外を使用するよりもはるかに優れています。しかし、このようなメソッドを呼び出す場合。

public void CreateUserButtonClick(String username) {
    UserManager.CreateUser(string username)
}

あなた問題を抱えており、例外がより良い選択になるでしょう。鉄道の比較save対をsave!


1

考慮する必要があることの1つは、C#のコードは一般に純粋ではないということです。副作用に満ちたコードベースで、ある関数の戻り値で何をするかを気にしないコンパイラでは、あなたのアプローチはかなりの悲しみを引き起こす可能性があります。たとえば、ファイルを削除するメソッドがある場合、「ハッピーパス上」のエラーコードをチェックする理由がない場合でも、メソッドが失敗したときにアプリケーションに通知するようにします。これは、使用していない戻り値を明示的に無視する必要がある関数型言語とはかなり異なります。C#では、戻り値は常に暗黙的に無視されます(唯一の例外はプロパティゲッターIIRCです)。

MSDNが「エラーコードを返さないように」と言っている理由はまさにこれです。誰もエラーコードを読むことさえ強制しません。コンパイラはまったく役に立ちません。ただし、場合、あなたの関数は副作用の自由である、あなたができるように、安全なものを使用Either-ポイントは、あなたがエラー結果を(もしあれば)を無視しても、あなたが唯一できることであるあなたは、「適切な結果を」使用しない場合どちらか。いくつかの方法は、あなたがこれを実現する方法があります-たとえば、あなただけの成功を処理するためのデリゲートを渡すことで、値を「読み」できる可能性があるエラーケースを、あなたは簡単のようなアプローチであることを組み合わせることができます鉄道は指向プログラミング(それはありませんC#で問題なく動作します)。

int.TryParse途中です。純粋です。これは、2つの結果の値が常に何であるかを定義します。したがって、戻り値がそうであればfalse、出力パラメーターはに設定されることがわかります0。それでも、戻り値をチェックせずに出力パラメーターを使用することを妨げることはありませんが、少なくとも、関数が失敗しても結果がどうなるかは保証されます。

しかし、ここで絶対的に重要なことは、一貫性です。誰かがその関数を変更して副作用を起こしたとしても、コンパイラはあなたを救いません。または、例外をスローします。したがって、このアプローチはC#ではまったく問題ありませんが(私はそれを使用しています)、チームの全員がそれを理解して使用していることを確認する必要があります。関数型プログラミングがうまく機能するために必要な不変条件の多くは、C#コンパイラによって強制されていないため、自分でそれらに従うようにする必要があります。Haskellがデフォルトで実施するルールに従わない場合、壊れるものに注意する必要があります。これは、コードに導入する機能的なパラダイムについて留意することです。


0

正しいステートメントは、例外にエラーコードを使用しないことです! また、例外以外には例外を使用しないでください。

コードが回復できない(動作させる)何かが発生した場合、それは例外です。すぐにコードがエラーコードを処理することはありません。エラーコードを上に渡してログに記録するか、何らかの方法でユーザーにコードを提供することです。しかし、これはまさに例外の目的です-すべての処理を処理し、実際に何が起こったかを分析するのに役立ちます。

ただし、自動的に再フォーマットするか、データを修正するようにユーザーに要求することで(およびその場合を考えて)処理できる無効な入力などの何かが発生した場合、それは実際には例外ではありません-あなたとしてそれを期待する。エラーコードまたはその他のアーキテクチャを使用して、これらのケースを自由に処理できます。例外ではありません。

(C#の経験はあまりありませんが、例外は概念レベルに基づいた一般的な場合に当てはまるはずです。)


7
私はこの答えの前提に根本的に同意しません。言語設計者が例外を回復可能なエラーに使用することを意図していなかった場合、catchキーワードは存在しません。そして、私たちはC#のコンテキストで話しているので、ここで支持されている哲学には.NETフレームワークが従わないことに注意する価値があります。おそらく、「処理できる無効な入力」の最も簡単な想像できる例は、ユーザーが数字が予想されるフィールドに非数字を入力して... int.Parse例外をスローすることです。
マークアメリー

@MarkAmery int.Parseの場合、回復できるものも期待されるものでもありません。通常、catch句は、外部ビューからの完全な失敗を回避するために使用されます。新しいデータベース資格情報などをユーザーに要求せず、プログラムのクラッシュを回避します。アプリケーションの制御フローではなく、技術的な障害です。
フランクホプキンス

3
条件が発生したときに関数が例外をスローする方がよいかどうかの問題は、関数の直接の呼び出し元がその条件を有効に処理できるかどうかによって異なります。可能な場合、すぐに呼び出し元がキャッチする必要がある例外をスローすることは、呼び出し元コードの作成者にとって余分な作業であり、それを処理するマシンにとって余分な作業です。ただし、呼び出し元が条件を処理する準備ができていない場合、例外により、呼び出し元に明示的にコーディングする必要性が軽減されます。
-supercat

@Darkwing「エラーコードやその他のアーキテクチャでこれらのケースを気軽に処理できます」はい、あなたはそれを自由に行うことができます。ただし、.NET CL自体はエラーコードの代わりに例外を使用するため、そのための.NETからのサポートはありません。したがって、多くの場合、.NET例外をキャッチし、例外をバブルアップさせるのではなく、対応するエラーコードを返すことを余儀なくされるだけでなく、チームの他のユーザーに、使用する必要がある別のエラー処理の概念を強制します例外と並行して、標準エラー処理の概念。
アレクサンダー

-2

それは「恐ろしいアイデア」です。

少なくとも.netでは、可能な例外の完全なリストがないため、ひどいです。コードは例外をスローする可能性があります。特に、OOPを実行していて、宣言された型ではなくサブ型でオーバーライドされたメソッドを呼び出すことができる場合

したがって、すべてのメソッドと関数をに変更する必要があり、OneOf<Exception, ReturnType>異なる例外タイプを処理する場合は、タイプIf(exception is FileNotFoundException)などを調べる必要があります。

try catch と同等です OneOf<Exception, ReturnType>

編集----

あなたが戻ることを提案することは承知していますOneOf<ErrorEnum, ReturnType>が、これはやや違いのない区別だと思います。

戻りコード、予期される例外、および例外による分岐を組み合わせています。しかし、全体的な効果は同じです。


2
「通常の」例外もひどい考えです。手がかりは名前にあります
ユアン

4
を返すことを提案していませんException
クリントン

例外の1つに代わる制御フローを提案していますか
ユアン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.