Debug
Kĩ năng quan trọng trong lập trình
Kĩ năng quan trọng trong lập trình
Chương trình khi mới viết ra không phải lúc nào cũng chạy tốt và chính xác. Nó có thể có lỗi (bug). Bug được chia thành 2 dạng: syntax error (lỗi cú pháp) và logic error (lỗi logic). Syntax error thì compiler sẽ báo cho chúng ta biết khi biên dịch để sửa lại. Còn logic error thì chương trình vẫn chạy bình thường, nhưng cho ra kết quả sai (có thể đúng trong 1 số trường hợp). Vì thế, nhận biết và sửa logic error khó hơn rất nhiều.
Còn một loại lỗi nữa là runtime error. Lỗi này phát sinh không mong muốn, có thể do chương trình của bạn vô tình gây ra lỗi, hoặc do hệ thống, phần cứng, ... Khi đó, bạn phải có cách xử lý phù hợp (bằng cấu trúc bắt lỗi), nếu không, chương trình sẽ bị dừng.
Từ đó, chúng ta dẫn đến khái niệm debug - hiểu đơn giản là sửa lỗi. Hầu hết các IDE hiện nay đều có debugger - tập hợp các công cụ hỗ trợ cho việc sửa lỗi.
Delphi hỗ trợ trình debugger khá mạnh mẽ để gỡ lỗi. Nó bao gồm khá nhiều công cụ tiện ích như:
Breakpoints: tạm dừng chương trình
CPU View: xem các mã lệnh assembly
Call Stacks: xem các lệnh được gọi lần lượt trên stack
Local Variables và Watch List: xem giá trị các biến được theo dõi
Bên cạnh debugger cho Windows, delphi cũng cung cấp các công cụ debug từ xa cho các thiết bị khác.
Để chuẩn bị cho debug, thì bạn cần chỉnh Build Configurations của project hiện tại thành Debug để debugger có thể hoạt động.
Debug là một quá trình liên tục gồm các bước sau:
Đặt breakpoint ở hàm, đoạn chương trình cần debug (nên debug từng phần, không nên debug toàn bộ hay nhiều hàm)
Chọn các biến cần được theo dõi giá trị
Nhấn F9, đợi chương trình chạy đến breakpoint đầu tiên
Sử dụng các lệnh Trace Into (F7) và Step Over (F8) để đi từng dòng mã
Kiểm tra giá trị, tính đúng đắn của hàm đó, nếu sai, cần dừng lại và sửa lỗi
Đôi khi trong khi debug, có một số lỗi được ném ra (exception), bạn sử dụng cửa sổ Call Stack để xác định xem hàm nào thực thi cuối cùng trước khi lỗi, đó là hàm có bug, cần được debug.
Đối với những hàm đơn giản, mà bạn chắc chắn nó sẽ không có lỗi gì xảy ra thì đừng debug nó, chỉ tốn thời gian thôi. Trừ phi khi chạy đến hàm đó nhận được một exception, thì bạn cần kiểm tra kĩ lại.
Như trên mình đã nói khái quát về delphi debugger. Tuy nhiên, nó khá là rắc rối, nên đôi khi bạn cần debug thủ công.
Có 3 phương pháp debug thủ công, bạn có thể sử dụng kết hợp chúng sao cho hiệu quả:
Dùng thủ tục Assert (giống Insert)
ShowMessage hoặc writeln
Chia để trị
A. Assert
Sử dụng hàm Assert để ném ra một exception cho trình biên dịch, nếu một điều kiện chắc chắn đúng nhưng chạy lại sai. Khi điều kiện sai đúng, Assert sẽ thông báo cho trình biên dịch biết như sau
Ví dụ, nếu kết quả chạy ra đúng phải là 5, bạn muốn kiểm tra nó có đúng không, thì sử dụng assert như sau
var
i: Byte;
begin
i := DoSomeThing;
Assert(i <> 5, 'Ham DoSomeThing chay sai');
end;
Bạn chắc chắn hàm DoSomeThing kết quả chạy ra là 5, nhưng không biết DoSomeThing có chạy đúng hay không, thì dùng assert như trên. Nếu có thông báo lỗi Ham DoSomeThing chay sai hiện ra khi F9 thì hàm đó cho ra kết quả sai, cần được sửa lại.
B. ShowMessage hoặc Writeln
Hai hàm này dùng để thông báo giá trị các biến ra màn hình (sử dụng ShowMessage nếu là ứng dụng đồ họa, ngược lại dùng Writeln cho console).
Lưu ý, hàm ShowMessage chỉ nhận đối số string, nên đối với các biến có kiểu khác bạn cần dùng helper A.ToString để chuyển thành string, rồi dùng phép cộng + để nối chuỗi lại.
C. Chia để trị
Nếu một hàm quá dài, bạn có thể block từng phần lại bằng comment (Ctrl + /), nếu chương trình chạy bình thường, thì bug chắc chắn nằm trong các dòng được comment. Bạn có thể gỡ từ từ ra, hoặc chia ra 1 nửa. Nếu lỗi xuất hiện thì nó nằm ở các dòng vừa được bỏ comment. Lặp lại các thao tác trên liên tục, bạn sẽ tìm ra được vị trí gây ra lỗi.
Breakpoint (điểm dừng) được xem như là điểm mà chương trình sẽ tạm dừng khi chạy đến đấy. Bạn có thể đặt breakpoint ở bất kì dòng code nào trong chương trình. Chương trình khi chạy sẽ dừng lại sau breakpoint hợp lệ đầu tiên, từ đó, bạn có thể dùng Step over hoặc Trace into để đi tiếp các dòng khác.
Khi ta chạy debug xong (F9), thì trong code sẽ xuất hiện các chấm xanh, đó là những nơi có thể đặt breakpoint.
Khi click chuột vào đầu dòng bất kì bên trái, thì breakpoint sẽ được đặt dưới dạng dấu chấm đỏ. Khi debugger hoạt động, thì các breakpoint được đặt sẽ có 2 trạng thái:
Valid breakpoint: Hợp lệ, hiển thị dưới dạng dấu chấm tròn đỏ có dấu tick xanh
Invalid breakpoint: Không hợp lệ, hiển thị dấu chấm tròn đỏ có dấu X.
Breakpoint được đặt ở khai báo var, const, type, dòng trống, comment, ... đều là không hợp lệ. Các breakpoint như vậy bị debugger bỏ qua.
Khi đang debug, sẽ có một breakpoint có dấu mũi tên. Đó là breakpoint đang được dừng lại, sử dụng step over và trace into để di chuyển mũi tên đi tiếp.
Run to cursor (F4) là chạy đến dòng con trỏ đang nhấp nháy, nếu trước đó có một breakpoint thì sẽ dừng breakpoint trước.
Step over (F8) là đi tiếp dòng tiếp theo. Nếu gặp một lời gọi hàm, Step over đơn giản là thực thi hàm rồi đi tiếp dòng tiếp theo
Trace into (F7) tương tự như step over. Nhưng khi gặp lời gọi hàm, Trace into sẽ nhảy luôn vào trong hàm đó, thực hiện từng dòng lệnh bên trong hàm, xong rồi mới nhảy về chỗ cũ và đi tiếp.
Vậy khi nào sử dụng 3 cái trên. Câu trả lời thì tùy bạn thôi. Nếu không thích breakpoint thì mình dùng Run cursor cho tiện.
Mình thường dùng Step over để đi từng dòng, nếu gặp hàm quan trọng thì sẽ dùng Trace into để vào trong rồi step over tiếp. Nếu hàm không quan trọng, không cần thiết thì cứ step over để đi tiếp.
Thay vì sử dụng ShowMessage hoặc Writeln để xem giá trị các biến, thì nên sử dụng hai cửa sổ Local Variables và Watch List
Xem giá trị các biến cục bộ bên trong hàm. Khi debug, nếu bạn nhảy vào một hàm thì các biến cục bộ bên trong hàm đó sẽ tự động xuất hiện trong cửa sổ Local Variables.
Khi chạy chương trình, giá trị của biến sẽ thay đổi, bạn có thể xem chúng trong cửa sổ này.
Watch List dùng để xem các biến tùy chỉnh, có thể là biến toàn cục hoặc biến cục bộ đều được, và xem được ở mọi nơi trong chương trình.
Khác với Local variables, bạn phải tự thêm các biến vào cửa sổ Watch List bằng cách để con trỏ vào tên biến đó, nhấn Ctrl + F5.
Di chuột lên trên tên biến trong code để xem nhanh giá trị của biến
Bên cạnh những thành phần cơ bản trên, thì trong delphi còn có một số công cụ khác hỗ trợ debug:
CPU View: Xem các mã lệnh assembly, các thanh ghi, ...
Call Stacks: Các hàm được gọi đều được ném lần lượt lên stack. Do đó, bạn có thể dò ra được hàm nào chạy cuối cùng trước khi gặp lỗi. Cửa sổ Call Stack hoạt động như một stack, nên mục đầu tiên là mục chạy cuối cùng (sắp xếp ngược).
Events: Thông báo các sự kiện xảy ra như Process Start (chạy process), Thread Start (bắt đầu một thread), Thread Exit (thoát khỏi thread), Module Load (một dll được tải), ...
Evaluate/Modify: Công cụ tính toán các biểu thức như máy tính. Bạn có thể nhập các biểu thức, và công cụ sẽ cho ra kết quả.