Vấn đề:
Từ lâu, tôi lo lắng về exceptions
cơ chế này, vì tôi cảm thấy nó không thực sự giải quyết được những gì cần làm.
YÊU CẦU: Có những cuộc tranh luận dài bên ngoài về chủ đề này và hầu hết trong số họ đấu tranh để so sánh exceptions
với việc trả lại mã lỗi. Đây chắc chắn không phải là chủ đề ở đây.
Cố gắng xác định lỗi, tôi đồng ý với CppCoreGuiances, từ Bjarne Stroustrup & Herb Sutter
Một lỗi có nghĩa là chức năng không thể đạt được mục đích được quảng cáo của nó
YÊU CẦU: Cơ exception
chế là một ngữ nghĩa ngôn ngữ để xử lý lỗi.
YÊU CẦU: Đối với tôi, "không có lý do" cho chức năng không đạt được nhiệm vụ: Hoặc chúng tôi đã xác định sai các điều kiện trước / sau để chức năng có thể đảm bảo kết quả hoặc một số trường hợp đặc biệt không được coi là đủ quan trọng để dành thời gian phát triển một giải pháp. Xem xét rằng, IMO, sự khác biệt giữa mã thông thường và xử lý mã lỗi là (trước khi thực hiện) là một dòng rất chủ quan.
YÊU CẦU: Sử dụng các ngoại lệ để chỉ ra khi không giữ một điều kiện trước hoặc sau là một mục đích khác của exception
cơ chế, chủ yếu cho mục đích gỡ lỗi. Tôi không nhắm mục tiêu sử dụng này exceptions
ở đây.
Trong nhiều sách, hướng dẫn và các nguồn khác, chúng có xu hướng hiển thị xử lý lỗi như một môn khoa học khá khách quan, đã được giải quyết exceptions
và bạn chỉ cần catch
chúng để có một phần mềm mạnh mẽ, có thể phục hồi từ mọi tình huống. Nhưng vài năm làm nhà phát triển khiến tôi thấy vấn đề từ một cách tiếp cận khác:
- Các lập trình viên có xu hướng đơn giản hóa nhiệm vụ của họ bằng cách đưa ra các ngoại lệ khi trường hợp cụ thể dường như quá hiếm để được thực hiện cẩn thận. Các trường hợp điển hình của vấn đề này là: hết các vấn đề về bộ nhớ, các vấn đề về đĩa đầy, các vấn đề về tệp bị hỏng, v.v ... Điều này có thể là đủ, nhưng không phải lúc nào cũng được quyết định từ cấp độ kiến trúc.
- Các lập trình viên có xu hướng không đọc tài liệu cẩn thận về các trường hợp ngoại lệ trong các thư viện và thường không nhận thức được chức năng nào và khi nào chức năng ném. Hơn nữa, ngay cả khi họ biết, họ không thực sự quản lý chúng.
- Các lập trình viên có xu hướng không nắm bắt ngoại lệ đủ sớm, và khi họ làm, chủ yếu là để đăng nhập và ném xa hơn. (tham khảo điểm đầu tiên).
Điều này có hai hậu quả:
- Lỗi xảy ra thường xuyên được phát hiện sớm trong quá trình phát triển và gỡ lỗi (điều này là tốt).
- Các trường hợp ngoại lệ hiếm hoi không được quản lý và làm cho hệ thống gặp sự cố (với một thông điệp tường trình đẹp) tại nhà của người dùng. Một số lần lỗi được báo cáo, hoặc thậm chí không.
Xem xét rằng, IMO mục đích chính của một cơ chế lỗi phải là:
- Hiển thị trong mã nơi một số trường hợp cụ thể không được quản lý.
- Truyền đạt thời gian chạy vấn đề đến mã liên quan (ít nhất là người gọi) khi tình huống này xảy ra.
- Cung cấp cơ chế phục hồi
Lỗ hổng chính của exception
ngữ nghĩa như một cơ chế xử lý lỗi là IMO: thật dễ dàng để biết vị trí của a throw
trong mã nguồn, nhưng hoàn toàn không rõ ràng để biết liệu một hàm cụ thể có thể ném bằng cách xem khai báo hay không. Điều này mang lại tất cả các vấn đề mà tôi đã giới thiệu ở trên.
Ngôn ngữ không thực thi và kiểm tra mã lỗi nghiêm ngặt như đối với các khía cạnh khác của ngôn ngữ (ví dụ: các loại biến mạnh)
Thử giải pháp
Với mục đích cải thiện điều này, tôi đã phát triển một hệ thống xử lý lỗi rất đơn giản, nó cố gắng đặt việc xử lý lỗi ở cùng mức độ quan trọng hơn mã thông thường.
Ý tưởng là:
- Mỗi hàm (có liên quan) nhận được một tham chiếu đến một
success
đối tượng rất nhẹ và có thể đặt nó thành trạng thái lỗi trong trường hợp. Đối tượng rất nhẹ cho đến khi một lỗi với văn bản được lưu. - Một chức năng được khuyến khích bỏ qua nhiệm vụ của nó nếu đối tượng được cung cấp đã có lỗi.
- Một lỗi không bao giờ được ghi đè.
Thiết kế đầy đủ rõ ràng xem xét kỹ lưỡng từng khía cạnh (khoảng 10 trang), cũng như cách áp dụng nó cho OOP.
Ví dụ về Success
lớp học:
class Success
{
public:
enum SuccessStatus
{
ok = 0, // All is fine
error = 1, // Any error has been reached
uninitialized = 2, // Initialization is required
finished = 3, // This object already performed its task and is not useful anymore
unimplemented = 4, // This feature is not implemented already
};
Success(){}
Success( const Success& v);
virtual ~Success() = default;
virtual Success& operator= (const Success& v);
// Comparators
virtual bool operator==( const Success& s)const { return (this->status==s.status && this->stateStr==s.stateStr);}
virtual bool operator!=( const Success& s)const { return (this->status!=s.status || this->stateStr==s.stateStr);}
// Retrieve if the status is not "ok"
virtual bool operator!() const { return status!=ok;}
// Retrieve if the status is "ok"
operator bool() const { return status==ok;}
// Set a new status
virtual Success& set( SuccessStatus status, std::string msg="");
virtual void reset();
virtual std::string toString() const{ return stateStr;}
virtual SuccessStatus getStatus() const { return status; }
virtual operator SuccessStatus() const { return status; }
private:
std::string stateStr;
SuccessStatus status = Success::ok;
};
Sử dụng:
double mySqrt( Success& s, double v)
{
double result = 0.0;
if (!s) ; // do nothing
else if (v<0.0) s.set(Error, "Square root require non-negative input.");
else result = std::sqrt(v);
return result;
}
Success s;
mySqrt(s, 144.0);
otherStuff(s);
saveStuff(s);
if (s) /*All is good*/;
else cout << s << endl;
Tôi đã sử dụng nó trong nhiều mã (của riêng tôi) và nó buộc người lập trình (tôi) phải suy nghĩ thêm về các trường hợp đặc biệt có thể xảy ra và cách giải quyết chúng (tốt). Tuy nhiên, nó có một đường cong học tập và không tích hợp tốt với mã hiện đang sử dụng nó.
Câu hỏi
Tôi muốn hiểu rõ hơn về ý nghĩa của việc sử dụng một mô hình như vậy trong một dự án:
- Là tiền đề cho vấn đề chính xác? hoặc tôi đã bỏ lỡ một cái gì đó có liên quan?
- Là giải pháp một ý tưởng kiến trúc tốt? hoặc giá quá cao?
CHỈNH SỬA:
So sánh giữa các phương pháp:
//Exceptions:
// Incorrect
File f = open("text.txt"); // Could throw but nothing tell it! Will crash
save(f);
// Correct
File f;
try
{
f = open("text.txt");
save(f);
}
catch( ... )
{
// do something
}
//Error code (mixed):
// Incorrect
File f = open("text.txt"); //Nothing tell you it may fail! Will crash
save(f);
// Correct
File f = open("text.txt");
if (f) save(f);
//Error code (pure);
// Incorrect
File f;
open(f, "text.txt"); //Easy to forget the return value! will crash
save(f);
//Correct
File f;
Error er = open(f, "text.txt");
if (!er) save(f);
//Success mechanism:
Success s;
File f;
open(s, "text.txt");
save(s, f); //s cannot be avoided, will never crash.
if (s) ... //optional. If you created s, you probably don't forget it.