Tôi sẽ trả lời từ quan điểm của C ++. Tôi khá chắc chắn rằng tất cả các khái niệm cốt lõi đều có thể chuyển sang C #.
Có vẻ như phong cách ưa thích của bạn là "luôn ném ngoại lệ":
int CalculateArea(int x, int y) {
if (x < 0 || y < 0) {
throw Exception("negative side lengths");
}
return x * y;
}
Đây có thể là một vấn đề đối với mã C ++ vì việc xử lý ngoại lệ rất nặng nề - nó làm cho trường hợp thất bại chạy chậm và làm cho trường hợp thất bại phân bổ bộ nhớ (đôi khi thậm chí không khả dụng) và nói chung làm cho mọi thứ trở nên khó đoán hơn. Sự nặng nề của EH là một lý do bạn nghe thấy mọi người nói những điều như "Đừng sử dụng ngoại lệ cho luồng kiểm soát."
Vì vậy, một số thư viện (chẳng hạn như <filesystem>
) sử dụng cái mà C ++ gọi là "API kép" hoặc cái mà C # gọi là Try-Parse
mẫu (cảm ơn Peter vì tiền boa!)
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
}
Bạn có thể thấy vấn đề với "API kép" ngay lập tức: rất nhiều sự trùng lặp mã, không có hướng dẫn nào cho người dùng về việc API nào là "quyền" để sử dụng và người dùng phải đưa ra lựa chọn khó khăn giữa các thông báo lỗi hữu ích ( CalculateArea
) và speed ( TryCalculateArea
) bởi vì phiên bản nhanh hơn lấy "negative side lengths"
ngoại lệ hữu ích của chúng tôi và làm phẳng nó thành vô dụng false
- "có gì đó không ổn, đừng hỏi tôi cái gì hoặc ở đâu." (Một số API kép sử dụng một loại lỗi biểu cảm hơn, chẳng hạn như int errno
hay C ++ 's std::error_code
, nhưng điều đó vẫn không cho bạn biết nơi xảy ra lỗi - chỉ là nó đã xảy ra ở đâu đó.)
Nếu bạn không thể quyết định cách mã của bạn nên hoạt động, bạn luôn có thể đưa ra quyết định cho người gọi!
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; });
Đây thực chất là những gì đồng nghiệp của bạn đang làm; ngoại trừ việc anh ấy bao gồm "trình xử lý lỗi" thành một biến toàn cục:
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);
Chuyển các tham số quan trọng từ các tham số chức năng rõ ràng sang trạng thái toàn cầu hầu như luôn là một ý tưởng tồi. Tôi không khuyên bạn nên nó. (Thực tế đó không phải là trạng thái toàn cầu trong trường hợp của bạn mà chỉ đơn giản là quốc gia thành viên toàn thể giảm nhẹ tính xấu một chút, nhưng không nhiều.)
Hơn nữa, đồng nghiệp của bạn không cần thiết phải giới hạn số lượng các hành vi xử lý lỗi có thể xảy ra. Thay vì cho phép bất kỳ lambda xử lý lỗi, anh ấy đã quyết định chỉ hai:
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);
Đây có lẽ là "điểm chua" trong số các chiến lược có thể xảy ra. Bạn đã lấy tất cả sự linh hoạt khỏi người dùng cuối bằng cách buộc họ sử dụng một trong hai cuộc gọi xử lý lỗi chính xác của bạn ; và bạn đã có tất cả các vấn đề của nhà nước toàn cầu chia sẻ; và bạn vẫn đang trả tiền cho chi nhánh có điều kiện ở khắp mọi nơi.
Cuối cùng, một giải pháp phổ biến trong C ++ (hoặc bất kỳ ngôn ngữ nào có biên dịch có điều kiện) sẽ buộc người dùng đưa ra quyết định cho toàn bộ chương trình của họ, trên toàn cầu, vào thời gian biên dịch, để có thể tối ưu hóa hoàn toàn bộ mã hóa:
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);
Một ví dụ về một cái gì đó hoạt động theo cách này là assert
macro trong C và C ++, điều kiện hành vi của nó trên macro tiền xử lý NDEBUG
.