14 điểm bởi GN⁺ 2025-11-04 | 2 bình luận | Chia sẻ qua WhatsApp
  • 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)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

  • Ví dụ lịch sử commit
    • Commit 1: commit ban đầu (bình thường)
    • Commit 2: refactor (bình thường)
    • Commit 3: đưa lỗi vào (phát sinh lỗi)
    • Commit 4~10: thay đổi không liên quan chức năng (lỗi vẫn còn)
  • Ví dụ lệnh thực thi
    git bisect start  
    git bisect bad HEAD  
    git bisect good HEAD~9  
    git bisect run ./test_script.sh  
    
  • Script kiểm thử (test_script.sh) trả về 0 khi thành công, và mã bất thường khi thất bại
  • git bisect tự động checkout các commit ở giữa và chạy script kiểm thử,
    rồi dựa trên thời điểm kiểm thử bắt đầu thất bại để xác định commit xấu đầu tiên
  • Trong kết quả đầu ra, commit b982ed9373fe235fe61c74b15faf264bc7142398 được xác nhận là commit lỗi đầu tiên

Kết luận

  • git bisectmộ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

 
kandk 2025-11-04

Đó là lý do chúng tôi sử dụng TBD (trunk-based development).

 
GN⁺ 2025-11-04
Ý 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 bisect gần như là công cụ hữu ích duy nhất
    Vì 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

    • Tôi không nghĩ git bisect là 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ện
      Nế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
    • Trước đây tôi từng tìm một bug có chuỗi ký tự lạ xuất hiện trong một chương trình OSS. Đó là mã C, và nguyên nhân là một biến chưa được khởi tạo
      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 bisect ban đầu là công cụ được đưa vào để tìm hồi quy (regression) của Linux kernel
      Ngay 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 đề
    • Nếu mục tiêu chỉ là sửa bug thì có thể không cần bisect. Nhưng có những lúc cần biết bug đó đã tồn tại từ khi nào
      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”
    • Đôi khi cũng cần biết bug đó được sửa từ khi nào
      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 bisect rất tuyệt khi nó hoạt động tốt, nhưng không phải bug nào cũng tìm được
    Có 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ể skip nhữ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 bisect một cách nghiêm túc, và nó gần như phép màu
    Có 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

    • Có người hỏi về cấu hình .gitattributes. Phản hồi là muốn biết cụ thể cần những nội dung gì
    • Cũng có người nói họ dùng bisect hằng ngày. Workflow hoàn toàn khác nhau
    • Với các hàm đa hình như trong C++, git log -L khá 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
    • Nếu không có .gitattributes, dùng git log -S để tìm các commit chứa một chuỗi cụ thể cũng là một cách
  • Trong 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

    • Ở những repository mà commit merge biểu thị điểm đã qua CI, dùng git bisect --first-parent sẽ rất hữu ích
      Cá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ế

    • Trong trường hợp này, có vẻ áp dụng tìm kiếm nhị phân Bayesian có thể giảm số lần chạy test đi rất nhiều
  • 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 dependabot lại làm số lượng commit tăng lên nên rất khó lần theo
    Nhờ 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 bisect là 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

    • Điều thú vị là, dù tìm kiếm nhị phân đã được đề xuất lần đầu từ thập niên 1940, nhưng mãi đến thập niên 1960 mới có một cách triển khai không lỗi
      Nếu tính chỉ số giữa bằng (low + high) / 2 thì có thể xảy ra tràn số
    • Cá nhân tôi nghĩ mọi lập trình viên nên thử tự triển khai tìm kiếm nhị phân ít nhất một lần bằng một ngôn ngữ số nguyên độ chính xác tùy ý như Python
      Đâ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, blame
    Trước đây tôi từng viết một bài blog về chủ đề này