Xử lý lỗi thời gian chạy
Bất kỳ chương trình nào được viết đủ chính xác để biên dịch mà không có lỗi vẫn không thể tránh khỏi các lỗi thời gian chạy. Chúng có thể xảy ra cả do sự sơ suất của nhà phát triển lẫn do những tình huống không lường trước được trong môi trường phần mềm (như mất kết nối Internet, hết bộ nhớ, v.v.). Nhưng không kém phần phổ biến là trường hợp lỗi xảy ra do việc áp dụng chương trình không đúng cách. Trong tất cả các trường hợp này, chương trình phải có khả năng phân tích bản chất của vấn đề và xử lý nó một cách phù hợp.
Mỗi câu lệnh MQL5 là một nguồn tiềm ẩn của lỗi thời gian chạy. Nếu xảy ra lỗi như vậy, terminal sẽ lưu một mã mô tả vào biến đặc biệt _LastError
. Hãy đảm bảo phân tích mã này ngay sau mỗi câu lệnh, vì các lỗi tiềm ẩn trong các câu lệnh tiếp theo có thể ghi đè giá trị này.
Lưu ý rằng có một số lỗi nghiêm trọng sẽ ngay lập tức làm gián đoạn việc thực thi chương trình khi chúng xảy ra:
- Chia cho số không
- Chỉ số vượt quá phạm vi
- Con trỏ đối tượng không chính xác
Để xem danh sách đầy đủ các mã lỗi và ý nghĩa của chúng, hãy tham khảo tài liệu.
Trong phần Mở và đóng tệp, chúng ta đã đề cập đến vấn đề chẩn đoán lỗi như một phần của việc viết macro PRTF hữu ích. Ở đó, đặc biệt, chúng ta đã thấy một tệp tiêu đề phụ trợ MQL5/Include/MQL5Book/MqlError.mqh
, trong đó liệt kê MQL_ERROR
cho phép chuyển đổi dễ dàng mã lỗi số thành tên bằng cách sử dụng EnumToString
.
enum MQL_ERROR
{
SUCCESS = 0,
INTERNAL_ERROR = 4001,
WRONG_INTERNAL_PARAMETER = 4002,
INVALID_PARAMETER = 4003,
NOT_ENOUGH_MEMORY = 4004,
...
// bắt đầu vùng cho các lỗi do lập trình viên định nghĩa (xem phần tiếp theo)
USER_ERROR_FIRST = 65536,
};
#define E2S(X) EnumToString((MQL_ERROR)(X))
2
3
4
5
6
7
8
9
10
11
12
Ở đây, với tham số X
của macro E2S
, chúng ta nên sử dụng biến _LastError
hoặc hàm tương đương của nó là GetLastError
.
int GetLastError() ≡ int _LastError
Hàm này trả về mã của lỗi cuối cùng xảy ra trong các câu lệnh của chương trình MQL. Ban đầu, khi chưa có lỗi, giá trị là 0. Sự khác biệt giữa việc đọc _LastError
và gọi hàm GetLastError
chỉ mang tính ngữ pháp (hãy chọn tùy chọn phù hợp với phong cách ưa thích của bạn).
Cần lưu ý rằng việc thực thi các câu lệnh không có lỗi theo cách thông thường sẽ không đặt lại mã lỗi. Việc gọi GetLastError
cũng không thực hiện điều đó.
Do đó, nếu có một chuỗi hành động mà chỉ một trong số đó đặt cờ lỗi, cờ này sẽ được hàm trả về cho các hành động tiếp theo (thành công). Ví dụ:
// _LastError = 0 mặc định
action1; // ok, _LastError không thay đổi
action2; // lỗi, _LastError = X
action3; // ok, _LastError không thay đổi, tức là vẫn bằng X
action4; // lỗi khác, _LastError = Y
action5; // ok, _LastError không thay đổi, tức là vẫn bằng Y
action6; // ok, _LastError không thay đổi, tức là vẫn bằng Y
2
3
4
5
6
7
Hành vi này sẽ gây khó khăn trong việc xác định khu vực có vấn đề. Để tránh điều này, có một hàm riêng ResetLastError
đặt lại biến _LastError
về 0.
void ResetLastError()
Hàm này đặt giá trị của biến tích hợp _LastError
về 0.
Nên gọi hàm này trước bất kỳ hành động nào có thể dẫn đến lỗi và sau đó bạn sẽ phân tích lỗi bằng GetLastError
.
Một ví dụ tốt về việc sử dụng cả hai hàm là macro PRTF đã được đề cập (tệp PRTF.mqh). Mã của nó được hiển thị dưới đây:
#include <MQL5Book/MqlError.mqh>
#define PRTF(A) ResultPrint(#A, (A))
template<typename T>
T ResultPrint(const string s, const T retval = NULL)
{
const int snapshot = _LastError; // ghi lại _LastError tại đầu vào
const string err = E2S(snapshot) + "(" + (string)snapshot + ")";
Print(s, "=", retval, " / ", (snapshot == 0 ? "ok" : err));
ResetLastError(); // xóa cờ lỗi cho các lệnh gọi tiếp theo
return retval;
}
2
3
4
5
6
7
8
9
10
11
12
13
Mục đích của macro và hàm ResultPrint
được bao bọc trong đó là ghi lại giá trị được truyền vào, là mã lỗi hiện tại, và ngay lập tức xóa mã lỗi. Do đó, việc áp dụng liên tục PRTF trên một số câu lệnh luôn đảm bảo rằng lỗi (hoặc chỉ báo thành công) được in vào nhật ký tương ứng với câu lệnh cuối cùng mà giá trị của tham số retval
được lấy.
Chúng ta cần lưu _LastError
vào biến cục bộ trung gian snapshot
vì _LastError
có thể thay đổi giá trị của nó hầu như ở bất kỳ đâu trong việc đánh giá một biểu thức nếu bất kỳ thao tác nào thất bại. Trong ví dụ cụ thể này, macro E2S
sử dụng hàm EnumToString
có thể tạo ra mã lỗi riêng nếu một giá trị không nằm trong liệt kê được truyền dưới dạng đối số. Sau đó, trong các phần tiếp theo của cùng một biểu thức, khi tạo chuỗi, chúng ta sẽ thấy không phải lỗi ban đầu mà là lỗi được tạo ra.
Có thể có nhiều vị trí trong bất kỳ câu lệnh nào mà _LastError
đột nhiên thay đổi. Về vấn đề này, nên ghi lại mã lỗi ngay sau hành động mong muốn.