Ghi Nhận Một Lỗi Lập Trình
Vào một ngày cuối tuần yên ắng, cửa hàng board game vốn dĩ không có khách, lại càng tĩnh mịch hơn khi người bạn hẹn gặp hôm qua không xuất hiện. Tôi liền tranh thủ đến văn phòng kiểm tra lại đoạn mã đã viết tuần trước.
Tuần vừa rồi tôi tập trung thiết kế một định dạng gói dữ liệu mới rồi tích hợp vào hệ thống file ảo đã hoàn thiện trước đó. Công việc này có nhiều điểm tương đồng với dự án zipfs từng thực hiện, nhờ vậy tiến độ khá nhanh. Tuy nhiên do chủ quan không kiểm thử kỹ càng, hôm nay nhân lúc rảnh rỗi, tôi đã xây dựng nhiều bộ dữ liệu phức tạp nhằm kiểm tra các trường hợp biên - và quả nhiên phát hiện lỗi nghiêm trọng.
Vấn đề này mang tính minh họa rõ ràng, nên sau khi khắc phục xong, tôi quyết định ghi chép lại để rút kinh nghiệm. Định dạng gói dữ liệu của tôi tương tự ZIP - sử dụng cấu trúc phẳng (flat structure), không phân chia thư mục rõ ràng. Mọi file đều được lưu trữ với đường dẫn đầy đủ ghép nối vào nhau. Ví dụ một gói dữ liệu có thể chứa các file sau:
|
|
Danh sách này được sắp xếp nhằm tối ưu tốc độ tìm kiếm. Các tên file đã qua sắp xếp cho phép áp dụng thuật toán tìm kiếm nhị phân hiệu quả. Trong thiết kế hệ thống file ảo, mỗi module fs cần cung cấp khả năng liệt kê nội dung tương tự hệ thống thư mục thật. Chẳng hạn khi duyệt thư mục “bar”, cần trả về lần lượt “bar.txt” rồi “foo.txt”. Khi duyệt thư mục gốc “/”, phải trả về “bar.txt”, “bar”, “foo”, “foobar.txt”.
Giao diện API tôi thiết kế là hàm C enum_next
với logic như sau: truyền con trỏ NULL sẽ trả về mục đầu tiên; truyền con trỏ không rỗng sẽ trả về mục kế tiếp; truyền con trỏ cuối cùng sẽ trả về NULL. Dù yêu cầu hiệu năng của API này không quá nghiêm ngặt - vì kết quả duyệt thư mục đã được lưu cache trong bảng băm ở tầng khung nhìn chung, nhưng thói quen nghề nghiệp khiến tôi phản ứng本能地 từ chối cách so sánh tuần tự đơn giản (nếu vậy chẳng cần sắp xếp làm gì), thay vào đó áp dụng tìm kiếm nhị phân để nâng cao tốc độ lên một bậc độ phức tạp.
Tuy nhiên khi gặp các file nằm trong thư mục con trung gian, enum_next
cần có khả năng bỏ qua các mục không mong muốn. Thao tác bỏ qua này lại tiếp tục yêu cầu một phép tìm kiếm nhị phân trả về dãy kết quả chứ không phải một phần tử đơn lẻ. Trong thư viện thuật toán của C++, có sẵn lower_bound
và upper_bound
để xử lý trường hợp này, nhưng với C thuần túy, tôi không nhớ rõ liệu có hàm tương đương nào hỗ trợ tìm khoảng hay không.
Lại vì thói quen xấu cố hữu, tôi lập tức viết thủ công đoạn mã tìm kiếm này. Tự tin thái quá vào khả năng lập trình nên tôi cho rằng việc tự viết sẽ hiệu quả hơn, vừa tránh phải định nghĩa callback phức tạp, vừa có thể tích hợp mọi logic vào một vòng lặp lớn duy nhất.
Thật không may, lỗi phát sinh lại không phải ở vòng lặp nhị phân, mà ở chỗ tôi chủ quan cho rằng sau khi sắp xếp, tên file của thư mục con luôn đứng trước file cùng tên ở cấp cao hơn. Giả định sai lầm này khiến tôi viết sai logic xử lý trong suốt cả đoạn mã quan trọng. Trong ví dụ trên, “bar.txt” thực tế lại xếp trước “bar/bar.txt”, điều hoàn toàn hợp lý về mặt thứ tự từ điển nhưng lại mâu thuẫn với kỳ vọng của tôi. Sau hơn một giờ vật lộn với đủ thứ giả thuyết về lỗi triển khai thuật toán, cuối cùng tôi mới nhận ra ngộ nhận ban đầu.
Thực ra lỗi này hoàn toàn có thể tránh được nếu tôi không phạm phải những sai lầm kinh điển sau:
-
Tối ưu hóa quá sớm (Premature optimization)
Dù tranh luận rằng đây là tối ưu hóa hiển nhiên xứng đáng thực hiện, nhưng việc quá tập trung vào hiệu năng ngay từ đầu đã khiến tôi bỏ qua những kiểm chứng cơ bản về logic. -
Không phân tách thuật toán thành module nhỏ
Tôi cho rằng bài toán đơn giản nên không cần chia nhỏ, nhưng chính sự kết hợp chồng chéo logic đã làm mờ đi các điểm dễ gây lỗi. -
Không tận dụng thư viện sẵn có
Một phần do giới hạn của thư viện C, nhưng ngay cả với C++ tôi cũng thường phản cảm với các template yêu cầu định nghĩa hàm so sánh phức tạp.
Bài học lớn nhất hôm nay: Đôi khi niềm tin mù quáng vào trực giác và kỹ năng lập trình lại chính là nguồn gốc của những lỗi ngớ ngẩn nhất. Trong lập trình, sự cẩn trọng và kiểm chứng kỹ lưỡng luôn cần được đặt lên hàng đầu, dù bạn tự tin đến đâu.