Saya akan menjawab dari sudut pandang C ++. Saya cukup yakin semua konsep inti dapat ditransfer ke C #.
Sepertinya gaya pilihan Anda adalah "selalu melempar pengecualian":
int CalculateArea(int x, int y) {
if (x < 0 || y < 0) {
throw Exception("negative side lengths");
}
return x * y;
}
Ini bisa menjadi masalah untuk kode C ++ karena penanganan pengecualian berat - ini membuat case kegagalan berjalan lambat, dan membuat case kegagalan mengalokasikan memori (yang kadang-kadang bahkan tidak tersedia), dan umumnya membuat hal-hal yang kurang dapat diprediksi. Beratnya EH adalah salah satu alasan Anda mendengar orang mengatakan hal-hal seperti "Jangan gunakan pengecualian untuk aliran kontrol."
Jadi beberapa perpustakaan (seperti <filesystem>
) menggunakan apa yang C ++ sebut sebagai "API ganda," atau apa yang C # sebut Try-Parse
polanya (terima kasih Peter atas tipnya!)
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
}
Anda dapat melihat masalah dengan "API ganda" segera: banyak duplikasi kode, tidak ada panduan bagi pengguna tentang API mana yang "benar" digunakan, dan pengguna harus membuat pilihan sulit antara pesan kesalahan yang berguna ( CalculateArea
) dan speed ( TryCalculateArea
) karena versi yang lebih cepat mengambil "negative side lengths"
pengecualian berguna kita dan meratakannya menjadi tidak berguna false
- "ada yang salah, jangan tanya saya apa atau di mana." (Beberapa API ganda menggunakan jenis kesalahan yang lebih ekspresif, seperti int errno
atau C ++ 's std::error_code
, tapi yang masih tidak memberitahu Anda di mana kesalahan terjadi - hanya saja memang terjadi di suatu tempat.)
Jika Anda tidak dapat memutuskan bagaimana perilaku kode Anda, Anda selalu dapat menendang keputusan sampai ke pemanggil!
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; });
Ini pada dasarnya adalah apa yang dilakukan rekan kerja Anda; kecuali bahwa dia memfaktorkan "penangan kesalahan" ke dalam variabel global:
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);
Memindahkan parameter penting dari parameter fungsi eksplisit ke status global hampir selalu merupakan ide yang buruk. Saya tidak merekomendasikannya. (Fakta bahwa itu bukan negara global dalam kasus Anda tetapi hanya negara anggota contoh-lebar sedikit mengurangi kejahatan, tapi tidak banyak.)
Selain itu, rekan kerja Anda tidak perlu membatasi jumlah perilaku penanganan kesalahan yang mungkin terjadi. Daripada mengizinkan setiap lambda penanganan kesalahan, dia memutuskan hanya dua:
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);
Ini mungkin "titik asam" dari semua strategi yang mungkin ini. Anda telah mengambil semua fleksibilitas dari pengguna akhir dengan memaksa mereka untuk menggunakan salah satu dari dua panggilan balik penanganan kesalahan Anda; dan Anda memiliki semua masalah negara global bersama; dan Anda masih membayar untuk cabang bersyarat itu di mana-mana.
Akhirnya, solusi umum dalam C ++ (atau bahasa apa pun dengan kompilasi bersyarat) akan memaksa pengguna untuk membuat keputusan untuk seluruh program mereka, secara global, pada waktu kompilasi, sehingga codepath yang tidak diambil dapat dioptimalkan sepenuhnya:
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);
Contoh dari sesuatu yang bekerja dengan cara ini adalah assert
makro di C dan C ++, yang mengkondisikan perilakunya pada makro preprosesor NDEBUG
.