C ++の観点から答えます。すべてのコアコンセプトがC#に移行可能であると確信しています。
あなたの好みのスタイルは「常に例外を投げる」ようです:
int CalculateArea(int x, int y) {
if (x < 0 || y < 0) {
throw Exception("negative side lengths");
}
return x * y;
}
例外処理は重いため、これはC ++コードの問題になる可能性があります。これにより、エラーケースの実行が遅くなり、エラーケースにメモリが割り当てられ(場合によっては利用できないこともあります)、一般的に予測が難しくなります。EHのヘビーウェイトは、「制御フローに例外を使用しないでください」などと言っている人がいる理由の1つです。
そのため、一部のライブラリ(など<filesystem>
)は、C ++が「デュアルAPI」と呼ぶもの、またはC#がTry-Parse
パターンと呼ぶものを使用します(Peterにヒントをありがとう!)
int CalculateArea(int x, int y) {
if (x < 0 || y < 0) {
throw Exception("negative side lengths");
}
return x * y;
}
bool TryCalculateArea(int x, int y, int& result) {
if (x < 0 || y < 0) {
return false;
}
result = x * y;
return true;
}
int a1 = CalculateArea(x, y);
int a2;
if (TryCalculateArea(x, y, a2)) {
// use a2
}
「デュアルAPI」の問題をすぐに見ることができます。多くのコードの複製、どのAPIを使用するのが「正しい」かに関するユーザーへのガイダンスはなく、ユーザーは有用なエラーメッセージ(CalculateArea
)とスピード(TryCalculateArea
)は、より高速なバージョンが有用な"negative side lengths"
例外を取得し、それを役に立たないfalse
ものにフラット化するためです。(一部のデュアルAPIには、次のような、より表現のエラータイプを使用するint errno
C ++のかstd::error_code
、それはまだあなたを教えてくれないところエラーが発生した-それだけということでしたどこかで発生します。)
コードの振る舞いを決定できない場合は、いつでも呼び出し元に決定を委ねることができます!
template<class F>
int CalculateArea(int x, int y, F errorCallback) {
if (x < 0 || y < 0) {
return errorCallback(x, y, "negative side lengths");
}
return x * y;
}
int a1 = CalculateArea(x, y, [](auto...) { return 0; });
int a2 = CalculateArea(x, y, [](int, int, auto msg) { throw Exception(msg); });
int a3 = CalculateArea(x, y, [](int, int, auto) { return x * y; });
これは基本的に同僚が行っていることです。ただし、彼は「エラーハンドラ」をグローバル変数に分解しています。
std::function<int(const char *)> g_errorCallback;
int CalculateArea(int x, int y) {
if (x < 0 || y < 0) {
return g_errorCallback("negative side lengths");
}
return x * y;
}
g_errorCallback = [](auto) { return 0; };
int a1 = CalculateArea(x, y);
g_errorCallback = [](const char *msg) { throw Exception(msg); };
int a2 = CalculateArea(x, y);
重要なパラメーターを明示的な関数パラメーターからグローバル状態に移行することは、ほとんど常に悪い考えです。お勧めしません。(あなたのケースではグローバルな状態ではなく、単にインスタンス全体のメンバー状態であるという事実は、悪さを少しだけ軽減しますが、それほど多くはありません。)
さらに、同僚は、考えられるエラー処理動作の数を不必要に制限しています。エラー処理ラムダを許可するのではなく、彼は2つだけを決定しました。
bool g_errorViaException;
int CalculateArea(int x, int y) {
if (x < 0 || y < 0) {
return g_errorViaException ? throw Exception("negative side lengths") : 0;
}
return x * y;
}
g_errorViaException = false;
int a1 = CalculateArea(x, y);
g_errorViaException = true;
int a2 = CalculateArea(x, y);
これはおそらく、これらの可能な戦略のうちの「サワースポット」です。厳密に2つのエラー処理コールバックのいずれかを使用することをエンドユーザーに強制することにより、エンドユーザーから柔軟性をすべて取り去りました。そして、あなたは共有グローバル状態のすべての問題を抱えています。そして、あなたはまだどこでもその条件分岐のために支払っています。
最後に、C ++(または条件付きコンパイルを使用する任意の言語)の一般的なソリューションは、コンパイル時にユーザーにプログラム全体をグローバルに決定させ、未使用のコードパスを完全に最適化することです。
int CalculateArea(int x, int y) {
if (x < 0 || y < 0) {
#ifdef NEXCEPTIONS
return 0;
#else
throw Exception("negative side lengths");
#endif
}
return x * y;
}
// Now these two function calls *must* have the same behavior,
// which is a nice property for a program to have.
// Improves understandability.
//
int a1 = CalculateArea(x, y);
int a2 = CalculateArea(x, y);
このように動作し、何かの例があるassert
マクロた条件プリプロセッサマクロにその動作をCやC ++で、NDEBUG
。