Gỡ lỗi báo cáo lỗi bạn không viết: quy trình thực tế
Gỡ lỗi các báo cáo lỗi bạn không viết bằng quy trình thực tế để tái hiện lỗi, cô lập UI/API/DB, và yêu cầu bản vá AI tối thiểu có thể kiểm tra.

Những điều khiến báo cáo lỗi này khó xử lý (và những gì bạn có thể kiểm soát)
Gỡ lỗi một báo cáo lỗi bạn không viết khó hơn vì bạn thiếu bản đồ tư duy của người xây dựng ban đầu. Bạn không biết chỗ nào dễ vỡ, điều gì là "bình thường", hoặc những shortcut nào đã được dùng. Một triệu chứng nhỏ (một nút, một lỗi chính tả, một màn hình chậm) vẫn có thể bắt nguồn từ vấn đề sâu hơn ở API, cơ sở dữ liệu, hoặc một job nền.
Một báo cáo hữu ích cho bạn bốn thứ:
- hành động chính xác
- dữ liệu chính xác
- kết quả mong đợi
- kết quả thực tế
Hầu hết báo cáo chỉ cho bạn phần cuối: "Không lưu được", "hỏng", "lỗi ngẫu nhiên." Thiếu đi bối cảnh khiến vấn đề có thể lặp lại: vai trò người dùng, bản ghi cụ thể, môi trường (prod vs staging), và liệu lỗi có bắt đầu sau một thay đổi nào đó không.
Mục tiêu là biến một triệu chứng mơ hồ thành một cách tái hiện đáng tin cậy. Khi bạn có thể làm nó xảy ra theo yêu cầu, nó không còn bí ẩn nữa. Nó trở thành một chuỗi kiểm tra.
Những gì bạn có thể kiểm soát ngay lập tức:
- tập hợp bước nhỏ nhất kích hoạt vấn đề
- dữ liệu thử nghiệm chính xác (ID, email, payload, bộ lọc)
- môi trường và phiên bản (build, feature flags, trình duyệt/thiết bị)
- bằng chứng rằng nó đã xảy ra (timestamp, ảnh chụp màn hình, văn bản lỗi, đoạn log)
- kết quả pass/fail rõ ràng bạn có thể chạy lại
"Xong" không phải là "Tôi nghĩ đã sửa." Xong là: các bước tái hiện của bạn pass sau một thay đổi nhỏ, và bạn nhanh chóng kiểm tra lại các hành vi lân cận có thể bị ảnh hưởng.
Thiết lập baseline ổn định trước khi bạn động đến bất cứ gì
Cách nhanh nhất để lãng phí thời gian là thay đổi nhiều thứ cùng lúc. Khoá điểm bắt đầu của bạn để mỗi kết quả thử nghiệm có ý nghĩa.
Chọn một môi trường và gắn bó với nó cho đến khi bạn có thể tái hiện vấn đề. Nếu báo cáo đến từ production, xác nhận ở đó trước. Nếu quá rủi ro, dùng staging. Local cũng ổn nếu bạn có thể khớp sát dữ liệu và cấu hình.
Rồi xác định mã nào đang chạy thực tế: phiên bản, thời điểm build, và bất kỳ feature flags hoặc config nào ảnh hưởng luồng. Những khác biệt nhỏ (tích hợp bị tắt, base URL API khác, job nền không chạy) có thể biến một lỗi thật thành ma.
Tạo một thiết lập thử nghiệm sạch và có thể lặp lại. Dùng một tài khoản mới và dữ liệu đã biết. Nếu có thể, reset trạng thái trước mỗi lần thử (đăng xuất, xóa cache, bắt đầu từ cùng một bản ghi).
Ghi lại các giả định khi bạn đi. Đây không phải việc làm chiếm thời gian; nó ngăn bạn tranh luận với chính mình sau này.
Mẫu ghi chú baseline:
- Môi trường: prod, staging, hoặc local
- Phiên bản/build: commit, tag, hoặc timestamp build
- Cấu hình: feature flags, tích hợp, test keys
- Nhân vật thử nghiệm: email/role tài khoản, permissions
- Dữ liệu: ID bản ghi, mục seed, trạng thái bắt đầu mong đợi
Nếu không tái hiện được, những ghi chú này cho bạn biết cần thay gì tiếp theo, từng nút một.
Chuyển báo cáo thành các bước và dữ liệu có thể thử nghiệm
Chiến thắng nhanh nhất là biến một lời phàn nàn mơ hồ thành thứ bạn có thể chạy như một script.
Bắt đầu bằng cách viết lại báo cáo như một user story ngắn: ai đang làm gì, ở đâu, và họ mong đợi điều gì. Rồi thêm kết quả quan sát được.
Ví dụ viết lại:
"Là một billing admin, khi tôi đổi trạng thái invoice thành Paid và nhấn Save trên trang hóa đơn, trạng thái nên được lưu. Thay vào đó, trang vẫn như cũ và trạng thái không thay đổi sau khi refresh."
Tiếp theo, nắm bắt các điều kiện khiến báo cáo đúng. Lỗi thường xoay quanh một chi tiết thiếu: vai trò, trạng thái bản ghi, locale, hoặc môi trường.
Các đầu vào chính cần ghi trước khi bạn click thử:
- Vai trò người dùng và loại tài khoản (admin vs standard, trial vs paid)
- Trạng thái dữ liệu (ID bản ghi, status, dữ liệu liên quan cần thiết)
- Thông tin client (OS, trình duyệt/phiên bản app)
- Locale và cài đặt thời gian (ngôn ngữ, timezone, phạm vi ngày)
- Môi trường và build (prod vs staging, phiên bản release, feature flags)
Thu thập bằng chứng khi hành vi gốc còn rõ. Ảnh chụp màn hình hữu ích, nhưng một đoạn quay ngắn còn tốt hơn vì nó ghi lại thời điểm và các click chính xác. Luôn ghi timestamp (kèm timezone) để bạn có thể đối chiếu log sau này.
Ba câu hỏi làm rõ loại bỏ phần lớn sự đoán:
- Đây xảy ra trên tài khoản người dùng và vai trò nào chính xác, và bạn có thể chia sẻ một ID bản ghi ví dụ không?
- Bạn mong đợi thấy gì ngay sau hành động, và bạn thực tế thấy gì?
- Nó xảy ra mỗi lần với cùng các bước hay chỉ với dữ liệu cụ thể (status, phạm vi ngày, locale, input lớn)?
Tái hiện vấn đề một cách đáng tin cậy
Đừng bắt đầu bằng đoán nguyên nhân. Làm cho vấn đề xảy ra có chủ ý, theo cùng một cách, hơn một lần.
Đầu tiên, chạy đúng các bước người báo cáo mô tả. Đừng "cải thiện" chúng. Ghi lại điểm đầu tiên trải nghiệm của bạn khác, dù là nhỏ (nhãn nút khác, trường bị thiếu, văn bản lỗi hơi khác). Khớp sai đầu tiên đó thường là manh mối.
Một workflow đơn giản áp dụng cho hầu hết app:
- Reset về trạng thái bắt đầu đã biết (tải lại, cùng tài khoản, cùng quyền, cùng flags).
- Theo các bước từng bước và ghi lại các đầu vào chính xác bạn dùng (ID, ngày, bộ lọc).
- Ghi lại mong đợi vs thực tế ở bước bị lỗi.
- Lặp lại một lần để xác nhận có thể lặp lại.
- Thu gọn xuống tập nhỏ nhất các bước vẫn kích hoạt lỗi.
Sau khi nó lặp lại được, thay đổi từng thứ một. Các test đơn biến thường mang lại kết quả:
- cùng các bước, vai trò khác
- cùng các bước, bản ghi khác (mới vs legacy)
- cùng các bước, trình duyệt/thiết bị khác
- cùng các bước, session sạch (incognito, xóa cache)
- cùng các bước, mạng khác
Kết thúc bằng một script repro ngắn người khác có thể chạy trong 2 phút: trạng thái khởi đầu, các bước, các đầu vào, và quan sát thất bại đầu tiên.
Cô lập lớp lỗi: UI, API hay DB
Trước khi đọc toàn bộ codebase, quyết định lớp nào đang lỗi.
Hỏi: triệu chứng chỉ ở UI hay có trong dữ liệu và phản hồi API nữa?
Ví dụ: "Tên hồ sơ của tôi không cập nhật." Nếu API trả tên mới nhưng UI vẫn hiển thị tên cũ, nghi ngờ trạng thái UI/cache. Nếu API không lưu, nhiều khả năng lỗi ở API hoặc DB.
Câu hỏi phân loại nhanh có thể trả lời trong vài phút:
- Bạn tái hiện được trên hơn một trình duyệt/thiết bị không?
- Làm mới mạnh (hard refresh) có thay đổi gì không?
- Có request mạng được gửi khi bạn bấm nút không?
- Phản hồi API đã sai chưa?
- Cơ sở dữ liệu có hiển thị hàng mong đợi sau hành động không?
Kiểm tra UI xoay quanh hiển thị: lỗi console, tab Network, và trạng thái cũ (UI không fetch lại sau khi lưu, hoặc đọc từ cache cũ).
Kiểm tra API liên quan đến hợp đồng: payload (các trường, kiểu, ID), mã trạng thái, và body lỗi. Một 200 với body bất ngờ có thể quan trọng như một 400.
Kiểm tra DB là về thực tế: hàng bị thiếu, ghi không đầy đủ, vi phạm ràng buộc, cập nhật chạm 0 hàng vì WHERE không khớp.
Để giữ phương hướng, phác thảo một bản đồ nhỏ: hành động UI nào gọi endpoint nào, và endpoint đó đọc hoặc ghi bảng nào.
Theo dõi request từ đầu đến cuối với log và timestamp
Sự rõ ràng thường đến khi bạn theo dõi một request thực sự từ click tới database và ngược lại.
Bắt ba mốc từ báo cáo hoặc repro của bạn:
- thời gian chính xác (kèm timezone)
- định danh người dùng (account/email/ID nội bộ)
- correlation ID (request ID/trace ID/session ID)
Nếu bạn không có correlation ID, thêm một cái ở gateway/backend và bao gồm nó trong header phản hồi và logs.
Để tránh bị ngập trong tiếng ồn, chỉ lấy những gì cần để trả lời "Nó thất bại ở đâu và vì sao?":
- phạm vi timestamp (ví dụ: 1 phút trước đến 1 phút sau)
- một user ID (và tenant/org ID nếu liên quan)
- correlation ID
- method, path, status code, latency
- thông báo lỗi có ý nghĩa đầu tiên (không cần cả trang stack trace)
Các tín hiệu cần chú ý:
- Timeout/độ trễ cao: truy vấn chậm, gọi ngoại vi, khoá
- 401/403: vấn đề quyền hay ngữ cảnh tenant
- 400 validation errors: thường là mismatch payload ở UI
Nếu "hôm qua chạy được mà hôm nay không", nghi ngờ drift môi trường: flags thay đổi, secret xoay vòng, migration thiếu, hoặc job dừng hoạt động.
Xây dựng một trường hợp tối thiểu có thể tái hiện (để bản vá nhỏ hơn)
Lỗi dễ sửa nhất là thử nghiệm nhỏ, có thể lặp lại.
Thu nhỏ mọi thứ: ít click hơn, ít trường hơn, dataset nhỏ nhất vẫn gây lỗi. Nếu chỉ xảy ra với "khách hàng có nhiều bản ghi", thử tạo một trường hợp tối thiểu vẫn kích hoạt nó. Nếu không thể, đó là manh mối lỗi liên quan khối lượng dữ liệu.
Tách "trạng thái xấu" khỏi "mã xấu" bằng cách reset trạng thái có chủ ý: tài khoản sạch, tenant/dataset mới, build đã biết.
Một cách thực tế giữ repro rõ ràng là bảng input gọn:
| Given (setup) | When (action) | Expect | Got |
|---|---|---|---|
| User role: Editor; one record with Status=Draft | Click Save | Toast "Saved" + updated timestamp | Button shows spinner then stops; no change |
Làm cho repro có thể di chuyển để người khác chạy nhanh:
- 3 đến 6 bước từ trạng thái bắt đầu sạch
- một bản ghi test (hoặc một body request) có thể tái sử dụng
- một tín hiệu thành công rõ ràng (UI message, HTTP code, số hàng DB)
- chi tiết môi trường chính xác (build/version, role, flags)
Bẫy thường gặp làm lãng phí thời gian
Con đường nhanh nhất thường nhàm chán: thay đổi từng thứ một, quan sát, ghi chú.
Những sai lầm phổ biến:
- Sửa triệu chứng bề mặt (che lấp lỗi API/DB thật)
- Thay đổi nhiều biến cùng lúc (cập nhật phụ thuộc + tinh chỉnh cấu hình + refactor)
- Test trên baseline khác với người báo cáo (env, data, build, browser)
- Quên quyền và vai trò (admin vs user thường)
- Bỏ sót feature flags hoặc experiment thay đổi luồng
- Tuyên bố chiến thắng mà không xác minh (không chạy lại repro, không kiểm tra tác dụng phụ)
Ví dụ thực tế: ticket ghi "Export CSV trắng." Bạn test bằng tài khoản admin và thấy dữ liệu. Người dùng có role giới hạn, và API trả danh sách rỗng vì filter quyền. Nếu bạn chỉ vá UI để hiện "No rows," bạn bỏ qua câu hỏi thực sự: role đó có được phép export hay product nên giải thích vì sao bị lọc?
Sau bất kỳ bản vá nào, chạy lại đúng các bước repro, rồi thử một kịch bản lân cận vẫn nên hoạt động.
Checklist nhanh trước khi yêu cầu sửa
Bạn sẽ nhận được câu trả lời tốt hơn từ đồng nghiệp (hoặc công cụ) nếu bạn mang theo một gói gọn: các bước có thể lặp lại, một lớp khả nghi, và bằng chứng.
Trước khi ai đó thay đổi code, xác nhận:
- Bạn tái hiện được hai lần với cùng các đầu vào (cùng user, cùng data, cùng môi trường).
- Bạn có thể nêu lớp lỗi (UI, API hoặc DB) và đưa một lý do.
- Bạn đã chụp bằng chứng: request, response/error, log liên quan, và timestamp khớp.
- Bạn đã giảm repro xuống nhỏ nhất có thể.
- Bạn viết tiêu chí chấp nhận trong một câu (ví dụ: "Saving updates the record and shows success within 2 seconds").
Rồi làm một lượt kiểm tra hồi qui nhanh: thử vai trò khác, một trình duyệt cửa sổ riêng/ẩn danh, một tính năng lân cận dùng cùng endpoint/bảng, và một input góc (rỗng, văn bản dài, ký tự đặc biệt).
Ví dụ thực tế: thu hẹp lỗi "Nút Save không làm gì"
Một tin nhắn support nói: "Nút Save không làm gì trên form Edit Customer." Theo dõi cho biết chỉ xảy ra với khách hàng tạo trước tháng trước, và chỉ khi bạn thay email thanh toán.
Bắt đầu ở UI và giả định lỗi đơn giản nhất trước. Mở bản ghi, sửa, và tìm dấu hiệu rằng "không có gì" thực ra có thể là "có thứ gì đó": nút bị disabled, toast ẩn, thông báo xác thực không hiển thị. Mở console trình duyệt và tab Network.
Ở đây, khi bấm Save gửi một request, nhưng UI không hiển thị kết quả vì frontend chỉ coi 200 là thành công và bỏ qua lỗi 400. Tab Network hiển thị response 400 với body JSON như: {"error":"billingEmail must be unique"}.
Bây giờ xác thực API thật sự fail: lấy payload chính xác từ request và replay nó. Nếu nó cũng fail ngoài UI, đừng tiếp tục theo đuổi lỗi trạng thái frontend.
Rồi kiểm tra database: tại sao uniqueness chỉ fail với các bản ghi cũ? Bạn phát hiện các khách hàng legacy chia sẻ một billing_email placeholder từ nhiều năm trước. Một kiểm tra unique mới chặn lưu bất kỳ khách hàng nào vẫn dùng placeholder đó.
Minimal repro bạn có thể chuyển giao:
- Chọn một legacy customer với
billing_email = [email protected]. - Thay bất kỳ field nào và bấm Save.
- Quan sát API trả 400 với
billingEmail must be unique. - Quan sát UI không hiện lỗi và để form giữ nguyên.
Tiêu chí chấp nhận: khi API trả lỗi xác thực, UI hiện thông báo lỗi, giữ lại các sửa đổi của người dùng, và lỗi chỉ ra đúng trường bị lỗi.
Bước tiếp theo: yêu cầu một bản vá tối thiểu bạn có thể kiểm tra
Khi lỗi đã tái hiện và bạn đã xác định lớp khả nghi, hãy yêu cầu trợ giúp sao cho tạo ra một bản vá nhỏ, an toàn.
Đóng gói một "case file" đơn giản: các bước repro tối thiểu (với inputs, môi trường, role), mong đợi vs thực tế, lý do bạn cho là UI/API/DB, và đoạn log nhỏ nhất cho thấy lỗi.
Rồi làm yêu cầu hẹp:
- đề xuất thay đổi code nhỏ nhất sửa được repro
- tránh refactor trừ khi cần thiết
- giải thích nguyên nhân bằng ngôn từ đơn giản
- kèm theo kế hoạch kiểm thử ngắn (cách xác nhận bản vá và những gì có thể hỏng lân cận)
Nếu bạn dùng nền tảng vibe-coding như Koder.ai (koder.ai), cách làm case-file này giữ đề xuất tập trung. Snapshot và rollback của nó cũng giúp bạn thử các thay đổi nhỏ an toàn và quay lại baseline đã biết.
Bàn giao cho một developer có kinh nghiệm khi sửa ảnh hưởng tới bảo mật, thanh toán, migration dữ liệu, hoặc bất cứ gì có thể làm hỏng dữ liệu production. Cũng bàn giao nếu thay đổi ngày càng lớn hơn một patch nhỏ hoặc bạn không thể giải thích rủi ro bằng lời đơn giản.
Câu hỏi thường gặp
What’s the first thing I should do with a vague bug report like “it’s broken”?
Bắt đầu bằng cách viết lại nó thành một kịch bản có thể tái hiện: ai (vai trò), ở đâu (trang/quy trình), các dữ liệu đầu vào chính xác (ID, bộ lọc, payload), bạn mong đợi gì và bạn thấy gì. Nếu thiếu phần nào, hãy hỏi để lấy một tài khoản ví dụ và một ID bản ghi ví dụ để bạn có thể chạy cùng một kịch bản đầu-cuối.
How do I set a baseline so my tests actually mean something?
Chọn một môi trường và giữ nguyên nó cho đến khi bạn tái hiện được. Sau đó ghi lại build/phiên bản, feature flags, cấu hình, tài khoản/role thử nghiệm và dữ liệu chính xác bạn đã dùng. Điều này ngăn bạn “sửa” một lỗi chỉ vì thiết lập của bạn khác với người báo cáo.
How do I turn the report into a minimal reproduction that someone else can run fast?
Làm cho lỗi xuất hiện hai lần bằng cùng các bước và dữ liệu, rồi loại bỏ mọi thứ không cần thiết. Mục tiêu là 3–6 bước từ trạng thái bắt đầu sạch với một bản ghi hoặc body request có thể tái sử dụng. Nếu không thể thu gọn, đó thường là dấu hiệu liên quan đến khối lượng dữ liệu, thời gian hoặc job nền.
Should I start by guessing the cause or by reproducing it?
Đừng thay đổi gì vội. Chạy đúng các bước người báo cáo ghi, và ghi lại chỗ đầu tiên mà trải nghiệm của bạn khác (nhãn nút khác, trường thiếu, thông báo lỗi khác). Sự sai lệch đầu tiên đó thường là manh mối cho điều kiện thực sự gây lỗi.
How can I quickly tell if the bug is in the UI, API, or database?
Kiểm tra xem dữ liệu có thực sự thay đổi không. Nếu API trả về giá trị mới nhưng UI vẫn hiển thị giá trị cũ thì nhiều khả năng là trạng thái UI, cache hoặc việc refetch. Nếu API trả sai hoặc thao tác lưu không xảy ra, hãy tập trung vào API hoặc DB. Nếu bản ghi DB không cập nhật (hoặc cập nhật 0 hàng), vấn đề nằm ở lớp lưu trữ hoặc điều kiện truy vấn.
What evidence should I capture while reproducing the bug?
Xác nhận có một request mạng khi bạn bấm nút, rồi kiểm tra payload của request và body của response, không chỉ mã trạng thái. Ghi lại dấu thời gian (kèm timezone) và định danh người dùng để khớp với log backend. Một 200 “thành công” nhưng với body bất ngờ có thể quan trọng như một 400/500.
What’s the best way to test “it only happens sometimes” bugs?
Thay đổi từng yếu tố một: vai trò, bản ghi (mới vs legacy), trình duyệt/thiết bị, phiên sạch (incognito/xóa cache), và mạng. Thử từng biến đơn lẻ sẽ cho bạn biết điều kiện nào quan trọng và tránh việc theo đuổi những trùng hợp ngẫu nhiên do thay nhiều thứ cùng lúc.
What are the most common mistakes that waste time during debugging?
Thay đổi nhiều biến cùng lúc, test trên môi trường khác với người báo cáo, và bỏ qua vai trò/permission là những điều lãng phí thời gian nhất. Sai lầm phổ biến khác là sửa bề mặt UI trong khi lỗi thực sự nằm ở API/DB. Sau khi thay đổi, luôn chạy lại kịch bản gốc rồi thử một trường hợp lân cận.
What does “done” look like for a bug fix, beyond “it works on my machine”?
“Hoàn thành” nghĩa là: bản sao nhỏ gốc giờ chạy thành công, và bạn đã kiểm tra lại một luồng gần kề có thể bị ảnh hưởng. Giữ kiểm tra cụ thể, như một tín hiệu thành công hiển thị, HTTP response đúng, hoặc thay đổi bản ghi DB mong đợi. Tránh nói “tôi nghĩ đã sửa xong” nếu không chạy lại cùng các đầu vào trên cùng baseline.
How should I ask an AI builder (or a teammate) for a small, testable fix?
Đưa một case file chặt chẽ: các bước tối thiểu với dữ liệu chính xác, môi trường/build/flags, tài khoản thử nghiệm và role, mong đợi vs thực tế, và một bằng chứng (request/response, văn bản lỗi, hoặc đoạn log có timestamp). Rồi yêu cầu bản vá nhỏ nhất để làm cho repro pass và kèm theo kế hoạch kiểm thử ngắn.