LẬP TRÌNH VIÊN CẦN HỌC NHỮNG GÌ TỪ BUG PHẦN II

Cập nhật ngày: 27/10/2021 - Đã có 595 lượt xem bài viết này!
LẬP TRÌNH VIÊN CẦN HỌC NHỮNG GÌ TỪ BUG PHẦN II
Làm thế nào để học hỏi hiệu quả nhất từ những bug chúng ta đã fix? Phương pháp mà tôi dùng là luôn dành ra vài phút để ghi chú lại các thông tin: mô tả bug, cách fix, bài học kinh nghiệm.

LẬP TRÌNH VIÊN CẦN HỌC NHỮNG GÌ TỪ BUG PHẦN II

Nguyên tắc:

-  Chỉ ghi chú những bug khó nhằn hoặc thực sự thú vị. Đây không phải là bug tracker.
-  Ghi chú những bug do chính mình gây ra. (Trừ trường hợp bug của người khác nhưng đủ thú vị).
-  Ghi lại bug ngay sau khi fix xong. Tránh nhớ nhầm, nhớ không chi tiết.

Cách ghi lại bug:

Tôi thường dùng form dưới đây để ghi lại bug dưới dạng file text (bugs.txt).

Ví dụ:

-  Ngày: 2004-08-17
-  Triệu chứng: Vòng lặp vô tận khi giải mã tín hiệu Q.931.
-  Nguyên nhân: Khi tìm thấy id của một thành phần chưa biết trong tín hiệu Q.931, ta tìm cách bỏ qua nó bằng cách lấy chiều dài, và di chuyển con trỏ pos tương ứng với độ dài tìm được. Tuy nhiên, với trường hợp độ dài bằng 0 làm ta liên tục bỏ qua cùng 1 id.
-  Cách tìm ra: Nhờ vào phân tích tín hiệu SETUP lấy từ trace của Ethereal ở Nortel. Tín hiệu của họ có độ dài 1016 bytes, nhưng MSX_MAX_LEN chỉ có 1000. Bình thường ta sẽ nhận một tín hiệu bị cắt từ common/Communication.cxx, nhưng ở đây khi cung cấp dữ liệu trực tiếp để phân tích, khoảng bộ nhớ vượt quá array bị truy cập, và vô tình nó bằng 0, làm xuất hiện lỗi. Để sửa lỗi, tôi đã thêm vào vài lệnh print trong phần code phân tích Q.931. Nhưng may mắn là dữ liệu lại bằng 0.
-  Sửa: Nếu chiều dài tìm thấy bằng 0, đặt nó lại bằng 1. Như vậy chúng ta sẽ luôn đi tiếp được.
-  Sửa trong file(s): callh/q931_msg.cxx
-  Thủ phạm là tôi: Đúng vậy.
-  Thời gian sửa bug: 1 giờ.
-  Bài học: Đặt “niềm tin lầm chỗ” vào dữ liệu của tín hiệu gửi tới. Giá trị dữ liệu có thể quá lớn làm chương trình chạy sai. Ngoài ra khi chiều dài bằng 0 cũng có thể là một dấu hiệu xấu.

LẬP TRÌNH VIÊN CẦN HỌC NHỮNG GÌ – 3 BÀI HỌC LỚN

1. Về coding.

Những lỗi phạm phải trong code? Có phải đã quên một else-part? Có phải một lệnh gọi hệ thống bị thất bại, nhưng hồi đáp chưa được check? Có cách nào chỉnh sửa code để tránh những vấn đề này trong tương lai không?

Trình tự sự kiện.

-  Khi xử lý sự kiện, những câu hỏi sau sẽ rất có ích:

Liệu sự kiện có thể đến theo trật tự khác được không?

Sẽ thế nào nếu không nhận được sự kiện này? Sẽ thế nào nếu sự kiện này diễn ra hai lần liên tiếp?

Thậm chí, nếu nó không bao giờ xảy ra, bugs ở những phần khác của hệ thống (hoặc của những hệ thống khác có tương tác) vẫn có thể khiến nó xảy ra.

Quá sớm.

Cái này là một trường hợp đặc biệt của phần “Trình tự sự kiện” ở trên, nhưng bởi vì nó gây ra một số lỗi rất khó tìm, nên nó được đặt ra riêng.

Chẳng hạn, nếu tín hiệu nhận được quá sớm, trước khi các tiến trình thiết lập và khởi động hoàn tất, khả năng chương trình sẽ có những biểu hiện kì lạ.

Một ví dụ khác: khi một kết nối được đánh dấu là down ngay cả trước khi nó được đưa vào danh sách idle. Khi phải tìm lỗi này, chúng ta luôn mặc định rằng nó bị đánh dấu down trong khi đang ở trong danh sách idle (nhưng lúc đó tại sao nó không được lấy ra khỏi danh sách?).

Đó là một sai lầm trong nhận thức của chúng ta khi không xét đến trường hợp có những thứ xảy ra quá sớm

“Cái chết êm đềm”.

Một trong số những lỗi khó phát hiện nhất là khi chúng lặng lẽ ra đi và chương trình tiếp tục được thực thi mà không quăng ra exception nào.

Chẳng hạn như các lệnh gọi hệ thống (bind chẳng hạn) trả về mã lỗi nhưng không được kiểm tra.

Hoặc như, phần code để phân tích tín hiệu chỉ đơn giản return khi bắt gặp một thành phần không hợp lệ, trong khi đáng lẽ phải quăng lỗi.

Chương trình tiếp tục chạy trong trạng thái sai, làm cho debug càng khó hơn. Nói chung tốt nhất là một lỗi nên được quăng ra càng sớm càng tốt.

If.

Lệnh if với nhiều điều kiện, if (a or b), đặc biệt là khi được nối lại với nhau, if (x) else if (y), gây ra quá trời lỗi cho tôi.

Dù cho câu lệnh if về mặt khái niệm quá đơn giản đi, chúng vẫn dễ bị sai khi có nhiều điều kiện đi kèm.

Bây giờ tôi cố gắng viết code đơn giản hơn để tránh phải xử lý những câu if phức tạp.

Else.

Cũng có quá trời lỗi là do không xét đến trường hợp bỏ qua lệnh else. Gần như tất cả trường hợp, luôn phải có một lệnh else cho mỗi câu if. Hơn nữa, nếu bạn đặt một biến bên trong lệnh if, khả năng cao là bạn phải đặt nó ở những chỗ khác nữa.

Một ví dụ rất liên quan là các đoạn lệnh kiểm tra cờ (flag). Quá dễ dàng khi đặt điều kiện để bật cờ, nhưng lại rất hay quên đặt điều kiện để đặt lại cờ. Để lá cờ bật liên tục có khả năng cao là sẽ có lỗi về sau.

Thay đổi các giả định.

Những lỗi khó phòng tránh nhất trong giai đoạn đầu thường là do thay đổi giả định.

Chẳng hạn, ban đầu có thể chỉ có một sự kiện customer mỗi ngày. Thế là rất nhiều code được viết với giả định này. Một thời gian sau, thiết kế thay đổi cho phép nhiều sự kiện customer diễn ra trong ngày. Khi chuyện này xảy ra, có thể rất khó để thay đổi hết tất cả trường hợp bị ảnh hưởng bởi thiết kế mới.

Nói chung không khó để tìm tất cả các phần phụ thuộc hiển nhiên, nhưng cái khó là tìm ra những phần phụ thuộc tiềm ẩn bên trong thiết kế cũ.

Chẳng hạn có thể có phần code thu thập tất cả sự kiện của customers trong một ngày nhất định. Một giả định hiển nhiên có thể là kết quả trả về không bao giờ lớn hơn số lượng customers.

Tôi không biết cách nào tốt để đề phòng những trường hợp này, nếu bạn nào biết thì gợi ý giùm với.

Logging.

Điều tối quan trọng là có nhận thức về những gì chương trình hoạt động, đặc biệt trong những chương trình có logic phức tạp.

Cần chắc chắn logging được đặt vừa đủ và đúng chỗ, để bạn có thể lý luận tại sao chương trình lại chạy như vậy.

Khi mọi thứ hoạt động trơn tru thì không sao, nhưng ngay khi chương trình xảy ra lỗi (chuyện không thể tránh khỏi), ít ra bạn sẽ thấy hạnh phúc vì đã logging đúng chỗ.
 

BTV.Trần Thị Thu Huyền
Phòng Truyền Thông IMicroSoft Việt Nam
Hotline: 0916 878 224
Email: huyenttt@imicrosoft.edu.vn

Xem khóa đào tạo nhân sự theo danh mục!

Xem các khóa đào tạo nhân sự