Có những lúc bạn nhận được báo cáo rằng chương trình thỉnh thoảng gặp một lỗi lạ, nhưng khi bật trình gỡ lỗi để tìm nguyên nhân thì lỗi lại biến mất như chưa từng có. Chỉ cần thêm một dòng in ra để kiểm tra là sự cố dừng lại, xóa dòng đó đi thì nó lại xuất hiện. Giới lập trình đã đặt cho những lỗi cứ bị soi là trốn mất này một biệt danh mượn tên một nhà vật lý: ‘Heisenbug’.
Nói đơn giản, Heisenbug là lỗi phần mềm biến mất hoặc thay đổi hành vi khi ta cố điều tra nguyên nhân của nó. Đây là cách nói ví von với hiệu ứng người quan sát, tức là chính hành động quan sát làm thay đổi trạng thái của đối tượng, và được dùng để chỉ những lỗi đặc biệt khó tái hiện và truy vết.
Trong bài viết này, chúng ta sẽ lần lượt tìm hiểu cái tên Heisenbug đến từ đâu, vì sao lỗi lại lẩn trốn, những biệt danh lỗi thường dùng cùng với nó và cách bắt Heisenbug.
Lỗi trốn đi khi bị nhìn: Heisenbug
Nguồn gốc của cái tên Heisenbug

Biệt danh này ra đời khi một nguyên lý nổi tiếng của vật lý gặp gỡ óc hài hước của các lập trình viên.
(1) Lối chơi chữ mượn tên nhà vật lý
Heisenbug là lối chơi chữ ghép tên nhà vật lý người Đức Werner Heisenberg, người đưa ra nguyên lý bất định, với từ bug, nghĩa là con bọ. Cái tên này nhằm gợi nhớ đến hiệu ứng người quan sát, khi đối tượng thay đổi ngay khoảnh khắc ta định quan sát nó.
(2) Thời điểm lần đầu xuất hiện trong ghi chép
Ghi chép sớm nhất được biết đến là một ấn phẩm năm 1983 của Hiệp hội Máy tính Hoa Kỳ (ACM). Năm 1985, nhà nghiên cứu cơ sở dữ liệu Jim Gray dùng từ này trong một bài báo về sự cố phần mềm, nên có lúc người ta lầm tưởng ông là người đặt ra nó.
(3) Người hàng xóm lâu năm trong ngành điện tử
Ngành điện tử đã có sẵn một thuật ngữ cho hiện tượng tương tự, gọi là hiệu ứng đầu dò (probe effect). Đó là hiện tượng hoạt động của thiết bị thay đổi chỉ vì đặt đầu dò đo lường vào mạch.
Tuy vậy, cái tên này cũng bị phê bình. Nguyên lý bất định và hiệu ứng người quan sát là hai khái niệm khác nhau, nhưng biệt danh lại trộn lẫn chúng như thể là một. Vì thế, nên hiểu Heisenbug như một tiếng lóng dí dỏm của giới phát triển phần mềm hơn là một thuật ngữ khoa học chính xác.
Vì sao Heisenbug lẩn trốn?

Lỗi không thực sự biết nhìn sắc mặt con người. Chỉ là công cụ và phương pháp dùng để tìm nguyên nhân đã làm thay đổi đôi chút điều kiện chạy của chương trình.
(1) Thời gian thực thi thay đổi
Khi chạy từng dòng bằng trình gỡ lỗi, chương trình chậm hơn bình thường rất nhiều. Trong những chương trình có nhiều tác vụ chạy cùng lúc, tình trạng tranh chấp (race condition), phát sinh khi thứ tự các tác vụ chỉ lệch đi một chút, là chuyện thường gặp, và khi tốc độ thay đổi thì sự lệch ấy có thể không còn xảy ra.
(2) Bố cục bộ nhớ thay đổi
Khi chèn câu lệnh in ra để kiểm tra hoặc biên dịch lại để gỡ lỗi, vị trí các biến nằm trong bộ nhớ sẽ thay đổi. Nếu nguyên nhân là một biến chưa khởi tạo hay một con trỏ trỏ sai chỗ, thì ngay khi vị trí thay đổi, triệu chứng cũng thay đổi theo.
(3) Việc tối ưu hóa thay đổi
Thường có trường hợp lỗi chỉ xảy ra ở chương trình đã được tối ưu hóa vì hiệu năng, còn chương trình tắt tối ưu hóa để gỡ lỗi thì chạy bình thường. Ví dụ, giá trị tính toán khi giữ trong thanh ghi và khi chuyển ra bộ nhớ có độ chính xác khác nhau, nên kết quả so sánh số thực dấu phẩy động có thể thay đổi.
Rốt cuộc, Heisenbug là một khiếm khuyết thật sự vẫn tồn tại, chỉ lúc lộ ra lúc ẩn đi tùy theo điều kiện chạy. Điểm mấu chốt là triệu chứng biến mất không có nghĩa là nguyên nhân cũng biến mất.

Những biệt danh lỗi dùng cùng Heisenbug

Khi Heisenbug trở nên phổ biến, nhiều biệt danh lỗi khác mượn tên các nhà khoa học cũng ra đời.
- Bohrbug: Lỗi tuân theo quy tắc cố định giống như mô hình nguyên tử của nhà vật lý Niels Bohr, cùng điều kiện thì lúc nào cũng xuất hiện y như nhau. Vì dễ tái hiện nên tương đối dễ bắt.
- Mandelbug: Lỗi có nguyên nhân phức tạp đến mức hành vi trông hỗn loạn. Cái tên bắt nguồn từ nhà toán học Benoit Mandelbrot, người nghiên cứu fractal.
- Schrödinbug: Lỗi chỉ thực sự bắt đầu gây sự cố sau khi một lập trình viên đọc mã nhận ra rằng đoạn mã đó vốn dĩ không thể chạy được.
- Higgs-bugson: Lỗi mà nhật ký và báo cáo của người dùng cho thấy hẳn là có tồn tại, nhưng lại khó tái hiện trong môi trường phát triển.
Phần lớn những biệt danh này bắt đầu từ chuyện đùa, nhưng chúng hữu ích để gọi ngắn gọn xem lỗi có tái hiện được không và nguyên nhân phức tạp đến đâu.
Cách bắt Heisenbug

Heisenbug tuy khó chịu nhưng nếu thay đổi cách tiếp cận thì hoàn toàn có thể tóm được.
(1) Ghi lại mà không dừng chương trình
Thay vì dùng trình gỡ lỗi để dừng chương trình, hãy để lại những bản ghi nhẹ ít can thiệp vào luồng thực thi rồi xem xét sau khi sự cố xảy ra. Giảm ảnh hưởng của việc quan sát là nguyên tắc đầu tiên.
(2) Tái tạo y nguyên điều kiện
Hãy khớp càng sát càng tốt thiết lập biên dịch, giá trị đầu vào và môi trường chạy lúc sự cố xảy ra. Cũng có thể dùng công cụ ghi lại quá trình thực thi để phát lại y hệt.
(3) Thu hẹp các nguyên nhân khả nghi
Lần lượt kiểm tra những nguyên nhân hay gây ra Heisenbug như biến chưa khởi tạo, con trỏ sai hay vấn đề thứ tự giữa các tác vụ. Công cụ kiểm tra mã và công cụ kiểm tra bộ nhớ giúp giảm đáng kể công sức cho quá trình này.
(4) Đừng dừng lại chỉ vì lỗi đã biến mất
Nếu sửa một dòng mã mà triệu chứng ngừng xuất hiện, cần xét xem lỗi đã được sửa thật hay chỉ tạm thời trốn đi. Chỉ khi giải thích được nguyên nhân thì mới có thể nói là đã giải quyết.
Heisenbug nhắc nhở lập trình viên rằng ngay cả công cụ dùng để quan sát cũng là một phần của hệ thống. Kể cả khi không thấy lỗi, vẫn cần đặt câu hỏi điều kiện chạy đã thay đổi điều gì.
Không thỏa mãn ở khoảnh khắc triệu chứng biến mất mà truy đến cùng lý do của nó, đó chính là bước đầu tiên để phát triển phần mềm đáng tin cậy.