Trang chủ » Thủ thuật debug trong Visual Studio (Phần 1)
Thủ thuật

Thủ thuật debug trong Visual Studio (Phần 1)

Các lập trình viên một ngày có thể viết hàng nghìn dòng code để tạo ra 1 phần mềm hay chương trình nào đó, và tất nhiên code không chạy theo đúng ý muốn là việc khó tránh khỏi. Tuy nhiên debugging đã giúp họ có thể nhẹ nhõm hơn trong việc hiệu chỉnh, chỉnh sửa lỗi. Vậy debug là gì, và sử dụng nó như thế nào trong Visual Studio?

Xin chào mọi người! Lâu rồi mình không viết bài mới (thành thật xin lỗi vì vấn đề cá nhân :v thực ra là lười thôi). Và để bắt đầu trở lại thì mình sẽ viết về debugging – một vấn đề mà dân lập trình rất thường xuyên sử dụng, ăn ngủ nghỉ gì cũng phải sử dụng, thậm chí có người dành hẳn lượng lớn thời gian chỉ để “tận hưởng” nó, mặc dù việc viết code chỉ tốn gần phân nửa thời gian.

difficult to debug
Rất khó để có thể debug thành thạo

Bài viết tham khảo từ trang MSDN Channel 9. Các bạn có thể tải example solution về và thực hành tại đây.

Bug là gì?

Bug tiếng Anh là bọ, tuy nhiên trong lập trình thì nó mang ý nghĩa lỗi. Lỗi đa phần được chia thành 3 loại:

  • Lỗi cú pháp (syntax errors).
  • Lỗi thực thi (run-time errors).
  • Lỗi luận lý (logic errors).

Với các lỗi cú pháp hay lỗi thực thi thì rất dễ để fix nếu bản thân có hiểu biết về ngôn ngữ đang sử dụng, và các IDE bây giờ rất hiện đại, có thể dễ dàng hiển thị các dạng lỗi như trên. Tuy nhiên với lỗi luận lý thì rất khó có thể nhận biết vì chương trình vẫn hoạt động bình thường, tuy nhiên kết quả trả về lại không đúng ý muốn của mình, và các IDE không thể phân biệt được dạng lỗi này. Vậy nên để có thể fix dạng lỗi này thì ta cần phải nắm rõ về Debugging.

Debug là gì?

Debug chính là việc rà soát lỗi trong chương trình để có thể fix. Việc debug có thể tốn vài phút với 1 lập trình viên có kinh nghiệm, nhưng có thể tốn hàng giờ với 1 lập trình viên còn non tay. Vì vậy nên việc sử dụng Debugging là một việc vô cùng quan trọng với mỗi lập trình viên. Ở đây mình xin phép lấy Visual Studio làm công cụ để viết blog, bạn cũng có thể tự thực hành bằng các IDE khác.

Step over (F10), Run to Cursor (Ctrl + F10) và Set Next Statement (Ctrl + Shift + F10)

Đa phần mọi người đều đã biết về Step over/into/out (F10/F11/Shift + F11) trong VS (trừ phi bạn mới dùng VS lần đầu :v). Step over được hiểu là “thực thi dòng code và chạy qua bước tiếp theo”, nó thực thi toàn bộ các lệnh, phương thức (method), tính toán,… ở dòng đó. Tuy nhiên nếu phương thức vừa được Step over có chứa breakpoint hoặc exception, thì nó sẽ nhảy vào phương thức đó.

step over
Để ý rằng property Greeting có sự thay đổi khi Step over.

Vậy còn Run to Cursor? Về ý nghĩa nó tương tự như 1 breakpoint, chương trình sẽ tự động thực thi các lệnh đến dòng code nằm ở con trỏ hiện tại. Công cụ này rất hữu ích nếu bạn không muốn ngồi bấm F10 liên tục :v

run to cursor
Quan sát 3 properties đã có sự thay đổi khi dùng Run to Cursor.

Cuối cùng là Set Next Statement, nó sẽ cho phép bạn nhảy tới dòng code mà KHÔNG thực thi các dòng ở giữa.  Đây là một công cụ rất mạnh mẽ, nhưng nên cẩn thận khi sử dụng. Thông thường mình sử dụng nó để quay về dòng trước đó để thực thi lại đoạn code mà mình có sửa lại trong quá trình debug. Bạn có thể sử dụng tổ hợp phím Ctrl + Shift + F10, hoặc đơn giản hơn là nắm kéo mũi tên vàng ở bên trái vào dòng code mong muốn.

Để ý rằng 2 properties vẫn không được gán mặc dù dòng debug đã đi qua.
Để ý rằng 2 properties vẫn không được gán mặc dù dòng debug đã đi qua.

Step Into Specific

Step into Specific issue

Làm thế nào để chạy vào method sp.GetPunctuation() mà không cần đặt breakpoint? Có lẽ rất nhiều lập trình viên mới thường sẽ bị vấn đề này. Cách giải quyết thông thường là vào phương thức cần Step into, đặt breakpoint và F10. Tuy nhiên với Visual Studio, bạn có thể sử dụng Step Into Specific mà không cần phải đặt breakpoint.

Step into Specific solution
Công cụ rất hữu ích phải không :D?

Call Stack

Bạn đã bao giờ gặp tình huống “phương thức này gọi phương thức kia”, hay đơn cử là những bài toán đệ quy mà các hàm tự gọi bản thân, lặp lại nhiều lần. Và ở một lần gọi nào đó mà mình muốn debug nhưng có quá nhiều hàm gọi lồng vào nhau thì biết lỗi nằm ở đâu? Đừng lo, vì Call Stack sẽ giúp bạn giải quyết vấn đề đó.

call stack and run to cursor
Call Stack giúp bạn hiển thị các method gọi lồng nhau nằm trong stack.

Ở hình trên, chương trình chạy phương thức MainWindow(), trong đó nó gọi phương thức GetPunctuation(), và phương thức GetPunctuation() lại gọi phương thức GetExclamationPoint(). Ngoài ra bạn cũng có thể sử dụng Run To Cursor (Ctrl + F10) để có thể di chuyển đến phương thức cần debug. Bạn thấy rằng nếu không có Call Stack thì việc quản lý các phương thức gọi lồng nhau rất khó khăn. Thử hình dung nếu bạn gặp 1 bài toán về đệ quy mà có công cụ này thì tuyệt vời biết bao!

Immediate Window

Đôi lúc trong quá trình chạy, ta muốn kiểm thử 1 phương thức, biểu thức, hay 1 biến một cách độc lập với chương trình thì làm thế nào? Immediate Window sẽ giúp bạn làm việc này bằng cách gõ phương thức, biểu thức hay một biến bất kì mà bạn muốn kiểm tra vào hộp thoại (hỗ trợ cả Lambda Expression đấy nhé :v).

immediate window begin
Tự sướng 1 chút nào :v

Ở hình trên ta thấy rằng có 3 cách để in một biến ra ngoài cửa sổ (dấu ? tương đương với Debug.Print). Ta cũng có thể gọi các phương thức, thuộc tính trong chương trình. Nhưng chỉ có thế thôi sao?

Immediate Window còn có thể làm những việc này.
Immediate Window còn có thể làm những việc này.

Như bạn thấy, mình đã đặt 1 breakpoint vào phương thức StringUtilities.ReverseString(), sau đó ở Immediate Window mình gọi phương thức đó 2 lần. Và Call Stack của mình bắt đầu xuất hiện 2 breakpoint khác nhau cho 2 lần gọi phương thức. Việc debug này hoàn toàn độc lập với chương trình chính khi đang chạy. Để hoàn thành việc debug thì chỉ việc nhấn Continue. Thật tiện lợi phải không nào?

immediate window advance 2
Kết quả sau 2 lần gọi phương thức. Bạn chú ý đến nút Continue đã được khoanh tròn nhé!

Bạn cũng có thể xem thêm về Immediate Window tại đây.

Visualizer

Bình thường khi mình muốn debug để xem giá trị JSON, XML, HTML,… trả về thì dùng Auto/Locals/Watch Window để xem. Thế nhưng với những giá trị kiểu như thế này thì sao?

Nhìn chuỗi thấy mà gớm :v
Nhìn chuỗi thấy mà gớm :v

Rất may, VS hỗ trợ cho chúng ta một thứ gọi là Visualizer, nó giúp hiển thị kết quả trả về một cách trực quan hơn như Text, XML, HTML, JSON hay thậm chí là DataSet.

visualizer solution
Giá trị trả về trở nên trực quan hơn với Visualizer

DebuggerDisplay Attribute và Peek Definition

Bạn tao ra 1 List<StudentInfo> với class StudentInfo chứa rất nhiều thuộc tính bên trong, nhưng bạn muốn Debugger chỉ hiển thị ra một vài thuộc tính lúc debug thì như thế nào?

debuggerdisplay issue

Lấy ví dụ ở hình trên, giả sử mình muốn kiểm tra thuộc tính AppName, nhưng thật là rắc rối nếu cứ mỗi item lại phải bấm vào mũi tên để xổ ra danh sách các thuộc tính rồi xem. Trong VS đã hỗ trợ DebuggerDisplay Attribute cho phép bạn tuỳ biến giá trị hiển thị ra khi debug, và Peek Definition (tổ hợp phím Alt + F12) dùng để hiển thị ra 1 popup nhỏ tương tự như Go to Definition, tuy nhiên bạn không cần phải chuyển giữa 2 cửa sổ để làm việc. Thật là tiện lợi phải không nào?

debuggerdisplay solution

Như bạn thấy ở hình trên, mình đã thêm 1 dòng DebuggerDisplay Attribute nằm trong popup Peek Definition, tuy nhiên do VS Intellisense nó sẽ hiển thị ra các gợi ý, và điều đó vô tình che đi các tên thuộc tính nằm phía bên dưới. Để làm cho ô gợi ý mờ đi, bạn chỉ cần bấm phím Ctrl trong lúc code. Điều này giúp tiết kiệm kha khá thời gian so với việc di chuyển con trỏ đi chỗ khác hoặc lăn cuộn chuột xuống để thấy được các thuộc tính bên dưới.

debuggerdisplay solution 2
Kết quả hiển thị đúng như ý muốn.

MakeObjectID

Khi xem 1 biến nào đó, nếu vô tình Step over đi ra khỏi tầm vực của biến thì sẽ xuất hiện một lỗi vô cùng khó chịu này:

makeobjectid issue
Biến app đã ra khỏi tầm vực hiện tại nên sẽ báo lỗi.

Điều này vô cùng khó chịu với những bạn muốn xem nội dung biến cụ thể trong bất kì hoàn cảnh nào. Trong VS đã hỗ trợ 1 tính năng gọi là MakeObjectID. Ta nhấp chuột phải vào 1 biến cần gán ID, và chọn Make Object ID, nó sẽ gán ID vào trong biến cụ thể nào đó vào 1 ký hiệu cụ thể:

makeobjectid solution
Biến app ($2) vẫn tồn tại mặc dù debugger đã đi khỏi tầm vực của nó.

Đến đây mình xin phép tạm dừng phần 1 vì bài viết quá dài :v. Cảm ơn các bạn đã chú ý theo dõi, hẹn gặp lại ở những phần sau!

About the author

Võ Hoài Sơn

Tính tình bất định
Chọc vào là bịnh
Rất yêu lập trình
Luôn code hết mình
Mình hiện đang là sinh viên của trường ĐH Khoa học tự nhiên TPHCM. Bản thân rất thích code, kiêm luôn cả mần thơ nên thường hơi hâm hâm dở dở. Ngoài ra chém gió, chém chuối, chém trái cây các kiểu cũng là sở trường của mình. Rất mong được làm quen với các bạn :D

Add Comment