Phân loại lỗi với Claude Code bằng một vòng lặp lặp lại: tái hiện, thu nhỏ, xác định nguyên nhân khả dĩ, thêm test hồi quy, và phát hành bản sửa hẹp kèm kiểm tra xác thực.

Lỗi có cảm giác ngẫu nhiên khi mỗi báo cáo trở thành một bí ẩn riêng lẻ. Bạn chọc vào code, thử vài ý tưởng, và hy vọng vấn đề biến mất. Đôi khi nó biến mất, nhưng bạn không học được gì, và cùng vấn đề lại xuất hiện dưới dạng khác.
Phân loại lỗi thì ngược lại. Đó là cách nhanh để giảm sự không chắc chắn. Mục tiêu không phải sửa mọi thứ ngay lập tức. Mục tiêu là biến một phàn nàn mơ hồ thành một tuyên bố rõ ràng, có thể kiểm tra được, rồi thực hiện thay đổi nhỏ nhất chứng minh tuyên bố đó không còn đúng.
Đó là lý do vòng lặp quan trọng: tái hiện, thu nhỏ, xác định nguyên nhân khả dĩ có bằng chứng, thêm test hồi quy, thực hiện sửa hẹp, và xác thực. Mỗi bước loại bỏ một kiểu suy đoán cụ thể. Bỏ qua bước và bạn thường phải trả giá sau này bằng các sửa lớn hơn, tác dụng phụ, hoặc “đã sửa” nhưng thực ra không sửa.
Ví dụ thực tế. Một user nói: “Nút Save đôi khi không phản hồi.” Nếu không có vòng lặp, bạn có thể lục lọi code UI và thay đổi timing, state, hoặc các cuộc gọi mạng. Với vòng lặp, trước tiên bạn biến “đôi khi” thành “luôn xảy ra trong những điều kiện chính xác này,” như: “Sau khi sửa tiêu đề, rồi nhanh chóng chuyển tab, Save vẫn bị vô hiệu hóa.” Câu đó đã là tiến bộ.
Claude Code có thể tăng tốc phần tư duy: biến các báo cáo thành giả thuyết chính xác, gợi ý chỗ nên nhìn, và đề xuất test tối thiểu sẽ fail. Nó đặc biệt hữu ích khi quét code, logs và diff gần đây để nhanh chóng sinh ra các giải thích khả dĩ.
Bạn vẫn phải kiểm chứng điều quan trọng. Xác nhận lỗi là thật trong môi trường của bạn. Ưu tiên bằng chứng (logs, trace, test fail) hơn một câu chuyện nghe hợp lý. Giữ sửa thật nhỏ, chứng minh bằng test hồi quy, và xác thực bằng các kiểm tra rõ ràng để bạn không đánh đổi lỗi này lấy lỗi khác.
Lợi ích là một bản sửa nhỏ, an toàn mà bạn có thể giải thích, biện hộ và ngăn không cho bị hồi quy.
Bản sửa tốt bắt đầu với workspace sạch và một câu mô tả vấn đề duy nhất, rõ ràng. Trước khi bạn hỏi Claude, chọn một báo cáo và viết lại nó dưới dạng:
“Ở khi tôi làm X, tôi mong Y, nhưng tôi nhận Z.”
Nếu bạn không viết được câu đó, bạn chưa có lỗi. Bạn có một bí ẩn.
Thu thập những yếu tố cơ bản trước để khỏi phải quay lại liên tục. Những chi tiết này làm cho các đề xuất có thể kiểm tra được thay vì mơ hồ: phiên bản app hoặc commit (và đang ở local, staging hay production), chi tiết môi trường (OS, trình duyệt/thiết bị, feature flags, vùng), input chính xác (field form, payload API, hành động user), ai thấy lỗi (mọi người, một role, một account/tenant), và “mong đợi” nghĩa là gì (copy, trạng thái UI, status code, quy tắc nghiệp vụ).
Rồi giữ bằng chứng khi còn mới. Một timestamp có thể cứu bạn hàng giờ. Ghi lại logs quanh thời điểm xảy ra (client và server nếu có thể), ảnh chụp màn hình hoặc đoạn ghi ngắn, request ID hay trace ID, timestamp chính xác (kèm timezone), và đoạn dữ liệu nhỏ nhất kích hoạt vấn đề.
Ví dụ: một app React tạo bởi Koder.ai hiển thị “Payment succeeded” nhưng đơn hàng vẫn ở trạng thái “Pending.” Ghi lại vai trò user, ID đơn hàng chính xác, body phản hồi API, và các dòng log server cho request ID đó. Giờ bạn có thể nhờ Claude tập trung vào một flow thay vì nói vòng vo.
Cuối cùng, đặt quy tắc dừng. Quyết định điều gì sẽ được tính là đã sửa trước khi bạn bắt đầu coding: một test cụ thể pass, trạng thái UI thay đổi, lỗi không còn xuất hiện trong logs, cộng với một checklist xác thực ngắn bạn sẽ chạy mỗi lần. Điều này ngăn bạn “sửa” triệu chứng rồi phát hành một lỗi mới.
Một báo cáo lộn xộn thường trộn lẫn sự kiện, phỏng đoán và bực bội. Trước khi bạn nhờ trợ giúp, chuyển nó thành câu hỏi sắc nét để Claude có thể trả lời bằng bằng chứng.
Bắt đầu bằng một tóm tắt một câu tên tính năng và lỗi. Tốt: “Lưu bản nháp đôi khi xóa tiêu đề trên mobile.” Không tốt: “Drafts bị vỡ.” Câu này trở thành mỏ neo cho toàn bộ thread triage.
Rồi tách những gì bạn thấy khỏi những gì bạn mong đợi. Giữ cho khô khan và cụ thể: nút bạn nhấn chính xác, thông báo trên màn hình, dòng log, timestamp, thiết bị, trình duyệt, branch, commit. Nếu bạn chưa có những thứ đó, nói rõ bạn thiếu gì.
Một cấu trúc đơn giản bạn có thể paste:
Nếu thiếu chi tiết, hỏi chúng dưới dạng câu yes/no để người khác trả lời nhanh: Có xảy ra trên account mới? Chỉ trên mobile? Chỉ sau refresh? Bắt đầu sau release gần nhất? Tái hiện ở chế độ incognito được không?
Claude cũng hữu ích như một “người dọn báo cáo.” Paste báo cáo gốc (kèm text copy từ screenshot, logs, chat), rồi hỏi:
“Viết lại thành checklist có cấu trúc. Gắn cờ mâu thuẫn. Liệt kê 5 thông tin thiếu quan trọng nhất dạng câu yes/no. Đừng phỏng đoán nguyên nhân.”
Nếu đồng nghiệp nói “Nó fail ngẫu nhiên,” ép nó về thứ có thể test: “Fail 2/10 lần trên iPhone 14, iOS 17.2, khi chạm Save hai lần nhanh.” Giờ bạn có thể tái hiện cố ý.
Nếu bạn không thể làm cho lỗi xảy ra theo yêu cầu, mọi bước sau đều là đoán mò.
Bắt đầu bằng tái hiện trong môi trường nhỏ nhất vẫn có thể hiện vấn đề: build dev local, một branch nhỏ, dataset tối thiểu, và ít service bật nhất có thể.
Ghi lại các bước chính xác để người khác có thể làm theo mà không hỏi thêm. Làm cho nó dễ copy-paste: lệnh, ID, và payload mẫu nên được đưa đúng như đã dùng.
Mẫu ghi chép đơn giản:
Tần suất thay đổi chiến lược của bạn. Bug “luôn” thuận lợi cho lặp nhanh. Bug “thỉnh thoảng” thường liên quan đến timing, cache, race condition hoặc state ẩn.
Khi bạn có notes tái hiện, nhờ Claude gợi ý các probe nhanh giảm sự không chắc chắn mà không cần viết lại app. Probe tốt là nhỏ: một dòng log mục tiêu quanh biên giới fail (inputs, outputs, state chính), một flag debug cho một component, cách ép hành vi xác định (fixed random seed, fixed time, single worker), dataset nhỏ kích hoạt, hoặc một cặp request/response fail để replay.
Ví dụ: flow signup fail “thỉnh thoảng.” Claude có thể gợi ý log ID người dùng sinh ra, kết quả chuẩn hóa email, và chi tiết lỗi unique constraint, rồi chạy lại cùng payload 10 lần. Nếu lỗi chỉ xảy ra lần đầu sau deploy, đó là manh mối mạnh để kiểm tra migrations, cache warmup, hoặc seed data thiếu.
Một reproduction tốt là hữu dụng. Một reproduction tối thiểu thì mạnh mẽ. Nó làm cho bug dễ hiểu hơn, dễ debug hơn và ít có khả năng “được sửa” một cách tình cờ.
Loại bỏ mọi thứ không cần thiết. Nếu bug xuất hiện sau một flow UI dài, tìm đường ngắn nhất vẫn kích hoạt nó. Bỏ các màn hình không bắt buộc, feature flags, và tích hợp không liên quan đến khi bug biến mất (bạn đã loại bỏ thứ thiết yếu) hoặc vẫn tồn tại (bạn tìm ra nhiễu).
Rồi thu nhỏ dữ liệu. Nếu bug cần payload lớn, thử payload nhỏ nhất vẫn break. Nếu cần danh sách 500 item, thử 5, rồi 2, rồi 1. Bỏ field từng cái một. Mục tiêu là ít phần thay đổi nhất vẫn tái hiện lỗi.
Phương pháp thực tế là “cắt đôi và retest”:
Ví dụ: trang checkout crash “thỉnh thoảng” khi áp coupon. Bạn phát hiện nó chỉ fail khi cart có ít nhất một item đang được giảm giá, coupon viết chữ thường, và shipping là “pickup.” Đó là minimal case: 1 item được giảm, 1 coupon chữ thường, 1 lựa chọn pickup.
Khi minimal case rõ ràng, nhờ Claude chuyển nó thành scaffold tái hiện nhỏ: một test gọi hàm lỗi với input nhỏ nhất, một script gọi một endpoint với payload rút gọn, hoặc một UI test nhỏ truy cập một route và thực hiện một hành động.
Khi bạn có thể tái hiện và có test case nhỏ, dừng đoán mò. Mục tiêu là có một danh sách ngắn các nguyên nhân khả dĩ, rồi chứng minh hoặc bác bỏ từng cái.
Quy tắc hữu ích: giữ tối đa ba giả thuyết. Nếu nhiều hơn, test case có lẽ vẫn quá lớn hoặc quan sát còn mơ hồ.
Dịch những gì bạn thấy thành nơi có thể xảy ra. Một triệu chứng UI không luôn luôn nghĩa là bug ở UI.
Ví dụ: một trang React hiện toast “Saved”, nhưng bản ghi lại mất sau đó. Điều này có thể là (1) trạng thái UI, (2) hành vi API, hoặc (3) đường viết vào DB.
Nhờ Claude giải thích các chế độ fail khả dĩ bằng ngôn ngữ đơn giản, rồi hỏi bằng chứng nào sẽ xác nhận từng cái. Mục tiêu là biến “có thể” thành “kiểm tra cái này đúng.”
Ba giả thuyết thường gặp và bằng chứng cần thu:
Giữ notes chặt: symptom, hypothesis, evidence, verdict. Khi một giả thuyết khớp với sự thật, bạn sẵn sàng khoá test hồi quy và sửa chỉ những gì cần thiết.
Test hồi quy tốt là vành an toàn của bạn. Nó chứng minh lỗi tồn tại, và cho biết khi nào bạn thực sự sửa xong.
Bắt đầu bằng chọn test nhỏ nhất khớp với lỗi thực tế. Nếu lỗi chỉ xảy ra khi nhiều phần phối hợp, unit test có thể không bắt được.
Dùng unit test khi một hàm trả giá trị sai. Dùng integration test khi biên giữa các phần là vấn đề (handler + DB, hoặc UI + state). Dùng end-to-end chỉ khi lỗi phụ thuộc vào toàn bộ flow.
Trước khi nhờ Claude viết gì, diễn đạt lại minimal case như hành vi mong muốn rõ ràng. Ví dụ: “Khi user lưu tiêu đề rỗng, API phải trả 400 với message ‘title required’.” Giờ test có mục tiêu rõ.
Rồi nhờ Claude phác thảo một test fail trước. Giữ phần setup tối thiểu và chỉ copy dữ liệu gây lỗi. Đặt tên test theo trải nghiệm user, không theo hàm nội bộ.
Lướt qua nhanh:
Khi test fail vì đúng lý do, bạn có thể yên tâm implement sửa hẹp.
Khi đã có repro nhỏ và test hồi quy fail, cố gắng không “dọn dẹp” quá sớm. Mục tiêu là dừng lỗi với thay đổi nhỏ nhất làm test pass vì đúng lý do.
Một sửa hẹp tốt thay đổi diện tích bề mặt nhỏ nhất có thể. Nếu lỗi ở một hàm, sửa hàm đó, không cả module. Nếu thiếu check biên, thêm check ở biên, không khắp chuỗi gọi.
Nếu dùng Claude để giúp, yêu cầu hai phương án sửa, rồi so sánh về phạm vi và rủi ro. Ví dụ: form React crash khi field rỗng, bạn có thể có:
Option A thường là lựa chọn triage: nhỏ hơn, dễ review hơn, ít khả năng phá vỡ thứ khác.
Để giữ sửa hẹp, chạm ít file nhất có thể, ưu tiên fix local hơn refactor, thêm guard/validation nơi giá trị xấu vào, và giữ thay đổi hành vi rõ ràng với một trước/sau. Chỉ để comment khi lý do không hiển nhiên.
Ví dụ cụ thể: một endpoint Go panic khi query param tuỳ chọn thiếu. Sửa hẹp là xử lý chuỗi rỗng ở biên handler (parse với default, hoặc trả 400 với message rõ). Tránh thay đổi util parsing chung trừ khi test chứng minh lỗi ở code chia sẻ.
Sau thay đổi, chạy lại test fail và một vài test lân cận. Nếu fix bắt bạn cập nhật nhiều test không liên quan, đó là dấu hiệu fix quá rộng.
Xác thực là nơi bạn bắt các vấn đề nhỏ dễ bỏ sót: sửa pass một test nhưng phá đường khác, thay đổi message lỗi, hoặc thêm truy vấn chậm.
Đầu tiên, chạy lại test hồi quy bạn đã thêm. Nếu pass, chạy các test lân cận: tests cùng file, cùng module, và bất cứ thứ nào che cùng input. Lỗi thường ẩn trong helper chia sẻ, parsing, check biên, hoặc cache, nên các failure liên quan thường xuất hiện gần nhau.
Rồi làm một kiểm tra thủ công nhanh theo các bước báo cáo gốc. Giữ ngắn và cụ thể: cùng môi trường, cùng dữ liệu, cùng chuỗi click hoặc gọi API. Nếu báo cáo mơ hồ, test chính xác kịch bản bạn dùng để tái hiện.
Nếu muốn giúp giữ tập trung, nhờ Claude soạn kế hoạch xác thực ngắn dựa trên thay đổi và kịch bản fail. Cho biết file bạn thay đổi, hành vi mong muốn, và những phần có khả năng bị ảnh hưởng. Kế hoạch tốt ngắn gọn và có thể thực hiện: 5–8 kiểm tra bạn có thể làm trong vài phút, mỗi kiểm tra với pass/fail rõ ràng.
Cuối cùng, ghi lại những gì bạn đã xác thực trong PR hoặc ghi chú: test đã chạy, các bước thủ công đã thử, và giới hạn (ví dụ “không test mobile”). Điều này làm cho bản sửa đáng tin hơn và dễ xem lại sau này.
Cách nhanh nhất để lãng phí thời gian là chấp nhận một “sửa” trước khi bạn có thể tái hiện vấn đề theo yêu cầu. Nếu bạn không thể làm cho nó fail ổn định, bạn không thể biết điều gì thực sự được cải thiện.
Quy tắc thực tế: đừng yêu cầu sửa cho đến khi bạn mô tả được setup có thể lặp lại (các bước chính xác, input, môi trường, và biểu hiện “sai”). Nếu báo cáo lộn xộn, dành vài phút đầu biến nó thành checklist bạn chạy hai lần và nhận cùng kết quả.
Sửa mà không có case tái hiện. Yêu cầu một script “fail mọi lần” hoặc bộ bước tối thiểu. Nếu chỉ fail “thỉnh thoảng”, ghi lại timing, kích thước dữ liệu, flags, và logs đến khi nó không còn ngẫu nhiên.
Thu nhỏ quá sớm. Nếu bạn lược case xuống trước khi xác nhận baseline ban đầu, bạn có thể mất tín hiệu. Trước tiên khoá baseline reproduction, rồi thu nhỏ từng thay đổi một.
Để Claude đoán thay bạn. Claude có thể đề xuất nguyên nhân, nhưng bạn vẫn cần bằng chứng. Yêu cầu 2–3 giả thuyết và các quan sát chính xác sẽ xác nhận/bác bỏ mỗi cái (một dòng log, một breakpoint, một kết quả query).
Test hồi quy pass vì lý do sai. Một test có thể “pass” vì nó không đi vào đường cố fail. Hãy chắc test fail trước khi fix, và fail với message/assertion mong đợi.
Xử lý triệu chứng thay vì trigger. Nếu bạn thêm null check nhưng nguyên nhân thật là “giá trị này không nên null”, bạn có thể che dấu lỗi sâu hơn. Ưu tiên sửa điều tạo ra trạng thái xấu.
Chạy test hồi quy mới và các bước tái hiện gốc trước và sau thay đổi. Nếu lỗi checkout chỉ xảy ra khi áp mã khuyến mãi sau khi thay đổi shipping, giữ đầy đủ chuỗi đó làm “sự thật”, dù test tối thiểu nhỏ hơn.
Nếu xác thực của bạn dựa trên “trông có vẻ ổn”, hãy thêm một kiểm tra cụ thể (log, metric, hoặc output rõ) để người tiếp theo có thể xác minh nhanh.
Khi bạn bị giới hạn thời gian, một vòng lặp nhỏ, lặp lại đánh bại debug kiệt sức.
Viết quyết định cuối cùng vài dòng để người tiếp theo (thường là bạn trong tương lai) có thể tin tưởng. Format hữu ích: “Root cause: X. Trigger: Y. Fix: Z. Vì sao an toàn: W. Những gì không thay đổi: Q.”
Bước tiếp theo: tự động hoá những gì có thể (script repro lưu sẵn, lệnh test chuẩn, template ghi chú root-cause).
Nếu bạn xây app với Koder.ai (koder.ai), Planning Mode có thể giúp bạn phác thảo thay đổi trước khi chạm code, và snapshot/rollback giúp bạn thử nghiệm an toàn khi xử lý repro khó. Khi fix được xác thực, bạn có thể xuất mã nguồn hoặc triển khai app đã cập nhật, bao gồm với domain tuỳ chỉnh khi cần.
Phân loại lỗi là thói quen biến một báo cáo mơ hồ thành một tuyên bố rõ ràng, có thể kiểm tra được, rồi thực hiện thay đổi nhỏ nhất chứng minh tuyên bố đó không còn đúng.
Mục tiêu không phải là “sửa mọi thứ” ngay lập tức mà là giảm dần sự không chắc chắn theo các bước: tái hiện, thu nhỏ, lập giả thuyết có bằng chứng, thêm test hồi quy, sửa hẹp, xác thực.
Vì mỗi bước loại bỏ một kiểu suy đoán khác nhau.
Viết lại thành: “Khi tôi làm X, tôi mong Y, nhưng tôi nhận Z.”
Rồi chỉ thu thập vừa đủ ngữ cảnh để biến nó thành testable:
Bắt đầu bằng cách xác nhận bạn có thể tái hiện nó trong môi trường nhỏ nhất vẫn hiển thị lỗi (thường là local dev với dataset nhỏ).
Nếu nó xảy ra “thỉnh thoảng”, cố gắng làm cho nó xác định bằng cách kiểm soát biến:
Đừng tiến tới bước tiếp theo nếu bạn chưa làm cho nó fail theo yêu cầu, nếu không bạn chỉ đang đoán mò.
Minimize nghĩa là loại bỏ mọi thứ không cần thiết trong khi vẫn giữ được lỗi.
Phương pháp thực tế: “cắt đôi rồi thử lại”:
Thu nhỏ cả bước (flow ngắn hơn) lẫn dữ liệu (payload nhỏ hơn, ít field/items) đến khi còn trigger nhỏ nhất có thể lặp lại.
Dùng Claude Code để tăng tốc phần phân tích, chứ không thay thế việc kiểm chứng.
Yêu cầu tốt có dạng:
Rồi bạn xác minh: tái hiện local, kiểm tra logs/traces, và chắc chắn test fail vì đúng lý do dự kiến.
Giữ ở ba giả thuyết. Nhiều hơn thường có nghĩa là repro vẫn còn quá lớn hoặc quan sát còn mơ hồ.
Với mỗi giả thuyết, ghi:
Chọn mức test nhỏ nhất phù hợp với lỗi:
Một test hồi quy tốt:
Thực hiện thay đổi nhỏ nhất để test hồi quy fail bật sang pass.
Nguyên tắc:
Nếu fix buộc bạn cập nhật nhiều test không liên quan, có lẽ fix quá rộng.
Dùng một checklist ngắn để chạy nhanh:
Ghi lại bạn đã chạy gì và chưa chạy gì để kết luận đáng tin cậy.
Cách này ép bạn tiến tới kết luận thay vì vòng vo “có thể là X”.