- Khái niệm tìm kiếm nhị phân (binary search) không chỉ xuất hiện trong các bài toán phỏng vấn mà còn được ứng dụng trong Git, một công cụ phát triển thực tế
- Trong môi trường monorepo quy mô lớn, khi bài kiểm thử đột ngột thất bại, sẽ có những tình huống rất khó lần ra nguyên nhân chỉ bằng log
- Một đồng nghiệp đã chỉ định commit tốt và commit xấu rồi dùng
git bisect để tự động dò tìm, qua đó xác định chính xác commit gây ra lỗi từ thời điểm nó bắt đầu xuất hiện
- Ở mỗi bước, chạy script để tự động phân loại commit theo kết quả kiểm thử, từ đó nhận diện commit đầu tiên gây thất bại
git bisect, dựa trên nguyên lý tìm kiếm nhị phân, là một công cụ mạnh mẽ để truy vết nhanh nguyên nhân lỗi trong các codebase lớn
Thuật toán và trường hợp thực tế
- Thuật toán tìm kiếm nhị phân (binary search) không chỉ là một bài toán phỏng vấn đơn giản mà còn hoạt động như nguyên lý cốt lõi trong các công cụ gỡ lỗi thực tế
git bisect có thể được dùng như một công cụ sử dụng tìm kiếm nhị phân để tìm ra “commit đầu tiên đưa lỗi vào (first bad commit)”
- Nó hoạt động theo nguyên lý tương tự bài “First Bad Version” trên Leetcode
Vấn đề trong môi trường làm việc thực tế
- Trong môi trường sử dụng monorepo quy mô lớn, mỗi ngày có thể phát sinh từ hàng trăm đến hàng nghìn commit
- Rất khó lần theo nguyên nhân của một lỗi kiểm thử chỉ bằng log
- Nguyên nhân lỗi là một thay đổi chuỗi trong tệp cấu hình dùng để lấy token cần cho lệnh gọi từ xa, khiến nó tham chiếu sang tài khoản khác nên bài kiểm thử bị thất bại
- Thay đổi này đã vượt qua kiểm thử tích hợp nhưng trên thực tế vẫn gây ra vấn đề, và rất khó tìm xem nó xuất hiện từ thời điểm nào giữa vô số commit
Giải quyết vấn đề bằng git bisect
- Một đồng nghiệp từ nhóm khác đã dùng lệnh
git bisect để nhanh chóng xác định commit gây lỗi
- Sau khi chỉ định commit tốt (good) và commit xấu (bad), công cụ tự động checkout các commit ở giữa, chạy kiểm thử và thu hẹp dần nguyên nhân
- Mỗi lần chạy kiểm thử đều mất thời gian, nhưng cuối cùng vẫn tìm ra chính xác commit đã đưa lỗi vào
- Khi hoàn tác commit đó, toàn bộ bài kiểm thử trở lại bình thường
Quy trình chạy git bisect
Kết luận
git bisect là một công cụ thực tiễn áp dụng nguyên lý tìm kiếm nhị phân vào việc truy vết lịch sử mã nguồn
- Ngay cả với kho mã lớn hoặc lịch sử thay đổi phức tạp, nó vẫn có thể nhanh chóng truy ra thời điểm lỗi được đưa vào
- Khi kết hợp với tự động hóa kiểm thử, việc gỡ lỗi vẫn có thể được thực hiện ổn định ngay cả trong các codebase quy mô lớn
2 bình luận
Đó là lý do chúng tôi sử dụng TBD (trunk-based development).
Ý kiến Hacker News
Trước đây khi làm việc trên một codebase khổng lồ gần như không có độ bao phủ kiểm thử và mức độ trừu tượng cũng rất tệ,
git bisectgần như là công cụ hữu ích duy nhấtVì mã quá phức tạp nên gần như không thể lần theo bug bằng suy luận logic, do đó việc tìm ra commit nào đã gây ra vấn đề lại dễ hơn nhiều
Nhưng với codebase chất lượng cao thì không thực sự cần bisect đến vậy. Mỗi thành phần đều có thể được kiểm thử độc lập, và khả năng quan sát (observability) cũng tốt nên rất rõ cần xem ở đâu
git bisectlà thứ có thể hoàn toàn không cần đến. Nó không chỉ giúp tìm bug mà còn giúp hiểu vì sao bug đó xuất hiệnNếu là một dự án có commit message đầy đủ, thì thông qua bisect có thể nắm được ngữ cảnh của các commit trong quá khứ và phản ánh nội dung đó vào commit sửa lỗi. Vòng lặp này còn củng cố chính văn hóa commit
Không thể lần theo trực tiếp, nhưng viết một script bisect chạy khoảng 30 phút thì đã xác định chính xác commit gây ra vấn đề
git bisectban đầu là công cụ được đưa vào để tìm hồi quy (regression) của Linux kernelNgay cả trong những trường hợp không thể kiểm thử như driver phần cứng, người dùng bình thường vẫn có thể tự bisect kernel để xác định commit gây lỗi
Trước đây phải gửi email nhờ nhà phát triển giúp đỡ, nhưng giờ người dùng đã có thể tự thu hẹp vấn đề
Ví dụ, nó hữu ích khi truy vết phạm vi dữ liệu đã bị xử lý sai, hoặc khi cần phân biệt “đây là bug hay là tính năng”
Ví dụ, khi khách hàng đang gặp vấn đề ở phiên bản từ 6 năm trước, có thể kiểm tra xem nâng cấp lên phiên bản từ 4 năm trước có giải quyết được không
Hoặc khi mã đã được refactor lớn, cũng có thể xác định việc sửa bug là có chủ ý hay chỉ là ngẫu nhiên
git bisectrất tuyệt khi nó hoạt động tốt, nhưng không phải bug nào cũng tìm đượcCó những bug lúc được đưa vào thì chưa có triệu chứng, và chỉ bộc lộ ra sau này do một thay đổi khác
Trong trường hợp đó, tiền đề của bisect — bug chỉ xuất hiện đúng một lần giữa commit tốt và commit xấu — bị phá vỡ
Có thể
skipnhững commit không thể kiểm thử, nhưng nếu đó chính là commit gây lỗi thì kết quả sẽ trở nên mơ hồGần đây tôi lần đầu tiên dùng
git bisectmột cách nghiêm túc, và nó gần như phép màuCó hai hàm trùng tên, và trong lúc định dạng lại mã thì import của hàm đúng đã bị xóa nên vấn đề xuất hiện
Tôi đã xem lại mã nhiều lần nhưng hoàn toàn không biết nguyên nhân cho đến khi dùng bisect để chỉ ra commit gây lỗi
Tôi thường đã biết phạm vi file hoặc hàm nơi bug xảy ra nên không hay dùng bisect
Thay vào đó, tôi dùng lệnh
git log -L :func_name:path/to/file.cđể theo dõi lịch sử thay đổi của một hàm cụ thểCần có cấu hình
.gitattributes.gitattributes. Phản hồi là muốn biết cụ thể cần những nội dung gìgit log -Lkhá yếu. Vì khó theo dõi đúng một phiên bản cụ thể trong số các hàm overload cùng tên.gitattributes, dùnggit log -Sđể tìm các commit chứa một chuỗi cụ thể cũng là một cáchTrong script kiểm thử, nên biết về exit code 125
Nếu không thể xác định kết quả kiểm thử, chẳng hạn như build thất bại, thì trả về 125 sẽ khiến bisect bỏ qua commit đó
Tôi đã tổng hợp nội dung liên quan trong bài blog của tôi
git bisect --first-parentsẽ rất hữu íchCách này giúp nhanh chóng tìm ra “PR nào đã đưa bug vào”, rồi sau đó chỉ cần chạy bisect chi tiết thêm một lần nữa trên nhánh đó
Khi xuất hiện flaky test, bisect thực sự phát huy tác dụng
Nếu do race condition mà phải chạy test hàng trăm nghìn lần mới đủ chắc chắn, thì để script bisect chạy nền sẽ trở thành cách giải quyết thực tế
Gần đây tôi đã dùng bisect để tìm nguyên nhân bug trong một dự án trình phát nhạc làm bằng Svelte (lets-make-sweet-music.com)
Không có test, cũng không có log lỗi, mà các bản cập nhật
dependabotlại làm số lượng commit tăng lên nên rất khó lần theoNhờ bisect tôi đã tìm ra commit gây lỗi, và nguyên nhân là file tôi thay thế không triển khai tính năng gắn nhiều event
Nếu giữ commit nhỏ, có thể nhanh chóng thu hẹp nguyên nhân của vấn đề mà bisect tìm ra
Có người nói việc phải học tìm kiếm nhị phân (binary search) trong phỏng vấn là gượng ép, nhưng
git bisectlà một ví dụ rất hay cho thấy khái niệm đó trong thực tếTuy vậy không cần tự cài đặt nó. Hầu hết ngôn ngữ đều đã cung cấp trong thư viện chuẩn
Nếu tính chỉ số giữa bằng
(low + high) / 2thì có thể xảy ra tràn sốĐây là bài tập tốt nhất để rèn luyện tư duy dựa trên bất biến (invariant)
Ngoài bisect, Git còn có những công cụ khám phá mã rất hay như
log -L,log -S,blameTrước đây tôi từng viết một bài blog về chủ đề này