Một quy trình gỡ lỗi, cho lúc bạn đã cạn ý tưởng
Phần lớn lời khuyên về gỡ lỗi là một danh sách công cụ. Công cụ giúp ích khi bạn biết chĩa chúng vào đâu. Bài này nói về tình huống còn lại: con bug đã trụ được cả một ngày cố gắng, mọi giả thuyết đều sai, và giờ bạn đang đổi lung tung mọi thứ rồi cầu may.
Đây là quy trình tôi quay về. Nó chậm hơn một cú đoán may mắn và nó luôn có điểm dừng.
1. Làm cho nó tái hiện theo yêu cầu
Không gì khác chạy được cho tới khi bước này chạy. Một con bug bạn kích hoạt được trong mười giây là một con bug bạn sẽ sửa được; một con bug xảy ra “thỉnh thoảng, sau một lúc” sẽ ngốn cả tuần của bạn.
Hãy bỏ thời gian thật vào đây. Viết cái script, thêm mục menu debug, gán cứng cái trạng thái, làm gì cũng được. Nếu nó chỉ tái hiện trên một thiết bị cụ thể hay một tài khoản cụ thể, hãy đi lấy đúng thiết bị đó và tài khoản đó.
Nếu nó thật sự không tái hiện được thì bạn chưa đang gỡ lỗi — bạn đang thu thập dữ liệu, và nhiệm vụ là thêm đủ log vào môi trường thật để về sau nó tái hiện được. Đó là một công việc khác và đáng thừa nhận rằng bạn đang làm việc đó.
2. Viết ra thứ bạn tin
Viết ra theo đúng nghĩa đen. Một câu: “Danh sách rỗng vì API trả về một mảng rỗng.”
Đây là bước người ta bỏ qua và là bước làm việc thật. Một niềm tin không viết ra thì trơn tuột — bạn sẽ lặng lẽ chỉnh sửa nó khi bằng chứng đến và chẳng bao giờ nhận ra mình đã sai. Viết ra rồi thì nó bác bỏ được.
3. Tìm thứ rẻ nhất có thể chứng minh bạn sai
Không phải thứ xác nhận bạn. Thứ bác bỏ bạn.
Với niềm tin ở trên: in ra số phần tử của mảng ngay tại chỗ nó đến. Đó là một dòng và ba mươi giây, và nó chẻ thế giới làm đôi. Hoặc là API trả về rỗng thật — và con bug nằm ở phía máy chủ, và giờ bạn đang gỡ lỗi một hệ thống hoàn toàn khác — hoặc là không, và mọi thứ bạn tin về con bug này đều sai và bạn vừa tiết kiệm được một ngày ngồi nhìn nhầm tầng.
4. Chia đôi
Nếu bước 3 chưa chốt được, hãy thôi lập luận và bắt đầu chia đôi. Dữ liệu chảy từ A tới Z; hãy kiểm tra ở M.
print("M: \(items.count)")
Giá trị ở M hoặc đúng hoặc không, và dù thế nào bạn cũng loại được một nửa lượng code. Mười vòng như vậy tìm ra một con bug trong một codebase có một nghìn hàm, và không vòng nào đòi bạn phải thông minh.
Điều tương tự áp dụng cho thời gian chứ không chỉ không gian:
git bisect start
git bisect bad
git bisect good v1.4.0
git bisect là công cụ bị dùng ít nhất so với giá trị của nó mà tôi biết. Nó biến câu “cái này hỏng
từ bao giờ?” từ một dự án khảo cổ thành hai mươi phút chạy ứng dụng và gõ good hay bad. Nếu bạn
có một bài test tái hiện được nó, git bisect run làm hộ bạn cả phần đó.
Mẹo
Chia đôi cũng áp dụng được cho dữ liệu đầu vào. Nếu một file JSON 4MB làm sập bộ phân tích cú pháp, hãy cắt nó làm đôi rồi thử lại. Sáu vòng và bạn có một tài liệu mười hai dòng tái hiện được cú sập — thứ vừa là chẩn đoán vừa là bài test chống tái phát.
5. Đổi đúng một thứ
Khi bạn bắt đầu thử các cách sửa, hãy đổi đúng một thứ rồi thử lại. Đổi hai thứ cùng lúc mà test pass thì chẳng nói cho bạn biết cái nào có tác dụng — và một trong hai có thể đã gieo mầm cho con bug kế tiếp.
Đây là quy tắc tôi phá nhiều nhất khi bị dí thời gian, và là quy tắc mà phá thì trả giá đắt nhất.
6. Nói to lên
Nếu tất cả những cái trên đều thất bại, hãy giải thích vấn đề từ đầu cho một người khác, hoặc cho không ai cả. Gỡ lỗi bằng cách nói với con vịt cao su là chuyện có thật và cơ chế của nó chẳng huyền bí gì: việc giải thích buộc bạn phải phát biểu các giả định của mình theo thứ tự, và không thể nào nói to câu “rồi nó lấy user về, mà ở chỗ đó thì user luôn khác nil” mà không lập tức tự hỏi liệu điều đó có đúng thật không.
Phần lớn những lần cách này hiệu nghiệm với tôi, tôi chưa bao giờ nói hết câu.
Những kiểu thất bại cần đề phòng
“Không thể nào là chỗ đó.” Mọi con bug đều sống trong đoạn code mà bạn chắc chắn là ổn. Sự chắc chắn đó không phải bằng chứng, và nó là lý do con bug sống sót lâu đến thế — bạn đã loại chỗ ở của nó ra khỏi vùng tìm kiếm ngay từ lượt đầu.
Sửa triệu chứng. Danh sách rỗng, nên bạn thêm một cái guard hiện ra chỗ trống tạm. Danh sách vẫn sai, và giờ nó sai một cách lặng lẽ. Trước khi viết bản sửa, hãy nói được câu “con bug xảy ra vì X” — nếu không nói được thì bạn đang dán băng dính.
Không đọc thông báo lỗi. Tôi từng mất hàng giờ vì một stack trace mà dòng thứ hai của nó nói chính xác chỗ sai. Hãy đọc hết, kể cả những khung bạn đinh ninh là khuôn mẫu vô nghĩa.
Vì sao lại cần một quy trình
Vì vào một ngày đẹp trời thì bạn không cần. Trực giác chạy tốt, bạn thấy con bug trong năm phút, và chẳng có điều gì ở trên áp dụng.
Quy trình này dành cho cái ngày trực giác hỏng — và vào ngày đó, thứ thay thế cho một quy trình là sáu tiếng đổi lung tung rồi phát hành một thứ trông có vẻ chạy. Tôi đã làm phiên bản đó rồi. Con bug luôn quay lại, và nó quay lại ngay trước mặt một khách hàng.