---
title: "Gỡ lỗi như thám tử: năm bước tìm lỗi trước khi hỏi AI"
url: https://www.stacklogs.io.vn/ky-thuat/go-loi-nhu-tham-tu/
language: vi
author: "Nhu Duy Thin (Thinnd)"
published: 2026-10-30
updated: 2026-10-30
section: "Kỹ thuật phần mềm"
tags: ["gỡ lỗi", "git bisect", "debugging", "AI agent", "bài tập", "danh sách kiểm"]
summary: "Năm bước gỡ lỗi: tái hiện lỗi chắc chắn, thu nhỏ ca lỗi, đặt một giả thuyết có thể sai, chia đôi để thu hẹp (git bisect), rồi sửa và thêm phép thử để lỗi không quay lại. Trong ví dụ chạy thật của bài, git bisect tìm ra commit gây lỗi trong 20 commit chỉ sau 4 lần kiểm. Sau đó mới đưa báo lỗi gọn cho AI."
license: "Cite with the author name and the canonical URL above."
---

# Gỡ lỗi như thám tử: năm bước tìm lỗi trước khi hỏi AI

> Dành cho bất kỳ ai viết hoặc đọc code, kể cả người mới. Không cần biết trước về Git nâng cao hay gỡ lỗi. Thuật ngữ nào cũng đi kèm một ví dụ đời thường, và phần nào cần tính toán được đánh dấu để bỏ qua nếu bạn không cần.
>
> Mọi lệnh và kết quả trong bài đều lấy từ một lần chạy thật trên kho thử (Git 2.56.0, Python 3.14.8), có script để bạn tự tạo lại. Các hash commit sẽ khác trên máy bạn vì chúng phụ thuộc thời gian. Cách người ta gỡ lỗi giỏi thế nào chưa có một lý thuyết thống nhất, nên năm bước dưới đây là **tổng hợp thực hành**, không phải quy trình đã được thử nghiệm đối chứng. Ví dụ ghi là "minh họa" được dựng lại, không nhắc đến người hay công ty cụ thể.

---

## Tóm tắt nhanh

- Gỡ lỗi là một kỹ năng riêng, và nó không mất đi khi có AI. Nếu chưa biết lỗi nằm đâu mà hỏi AI ngay, bạn sẽ không đủ cơ sở để phán xét câu trả lời.
- Năm bước: **tái hiện** lỗi chắc chắn, **thu nhỏ** ca lỗi, đặt **một giả thuyết** có thể sai, **chia đôi** để thu hẹp vùng nghi ngờ, rồi **sửa và thêm phép thử** để lỗi không quay lại.
- Bước chia đôi có công cụ sẵn trong Git là `git bisect`. Trong ví dụ chạy thật của bài, 19 commit nghi ngờ cần 4 lần kiểm để tìm ra thủ phạm, so với tối đa 19 lần nếu đi tuần tự.
- Thủ phạm trong ví dụ là một commit tên "refactor: simplify discount check", tức là một thay đổi trông vô hại. Lỗi chỉ là một ký tự: `>=` thành `>`.
- Sau khi làm xong năm bước, bạn đưa cho AI một **báo lỗi gọn** (mẫu ở cuối bài). Lúc đó AI giúp nhanh hơn và bạn kiểm được lời giải thích của nó.
- Giới hạn: cách này cần phép thử cho kết quả giống nhau mỗi lần. Lỗi phụ thuộc thời gian, dữ liệu hay môi trường khó hơn nhiều.

---

## Từ khó, nói dễ

| Thuật ngữ | Nói dễ hiểu |
|---|---|
| **Bug** (lỗi) | Chỗ chương trình làm khác điều ta mong đợi. |
| **Reproduce** (tái hiện) | Làm lỗi xảy ra theo ý mình, mỗi lần đều thế, như dựng lại hiện trường vụ án. |
| **Minimal reproduction** (ca lỗi tối thiểu) | Ví dụ nhỏ nhất mà lỗi vẫn xảy ra. Bỏ gì đi nữa thì lỗi biến mất. |
| **Hypothesis** (giả thuyết) | Một lời đoán **có thể bị chứng minh là sai** về nguyên nhân, như "nghi phạm là người cầm chìa khóa". |
| **Bisect** (chia đôi) | Mỗi lần thử loại bỏ một nửa vùng nghi ngờ, giống trò đoán số từ 1 đến 100 bằng cách hỏi "lớn hơn 50 không". |
| **Regression** (lỗi quay lại) | Thứ từng chạy đúng nay hỏng vì một thay đổi sau đó. |
| **Test** (phép thử) | Đoạn code nhỏ chạy rồi báo "đạt" hoặc "không đạt", không cần người đọc. |
| **Exit code** (mã kết thúc) | Con số một lệnh trả về khi chạy xong: 0 là ổn, số khác 0 là có lỗi. Git dùng nó để biết commit nào tốt. |

---

## Sau bài này bạn giải thích được

1. Vì sao một lỗi cần được **tái hiện và thu nhỏ** trước khi sửa, và thế nào là một giả thuyết "có thể sai".
2. `git bisect` tìm ra commit gây lỗi nhờ **chia đôi**, và vì sao 19 commit chỉ cần khoảng 5 lần thử.
3. Một báo lỗi tốt cho AI gồm những gì, và vì sao nên đưa nó **sau** khi tự thu hẹp vùng nghi ngờ.

---

## 1. Hình dung trước: thám tử ở hiện trường

Hình dung một thám tử đến hiện trường. Họ không lao vào bắt người đầu tiên thấy khả nghi. Họ giữ nguyên hiện trường, ghi lại điều đã xảy ra, bỏ bớt chi tiết thừa để thấy cái cốt lõi, rồi đặt một giả thuyết cụ thể: "nếu thủ phạm vào bằng cửa sau thì sẽ có dấu bùn ở sàn bếp". Họ đi kiểm đúng điều đó. Nếu sai, họ bỏ giả thuyết và thử cái khác. Cuối cùng họ có chứng cứ để người khác kiểm lại.

Gỡ lỗi cũng vậy. Mỗi bước trong năm bước dưới đây tương ứng một việc thám tử làm:

| Thám tử | Gỡ lỗi |
|---|---|
| Giữ nguyên hiện trường | **Tái hiện** lỗi chắc chắn |
| Bỏ chi tiết thừa | **Thu nhỏ** ca lỗi |
| Đặt giả thuyết có thể kiểm | Đặt **một giả thuyết** và đoán trước kết quả |
| Loại dần nghi phạm | **Chia đôi** vùng nghi ngờ |
| Lập hồ sơ để vụ án không mở lại | **Sửa, thêm phép thử**, ghi lại |

**Điều rút ra:** thám tử giỏi không phải người đoán đúng ngay, mà là người có cách loại dần các khả năng sai.

---

## 2. Vì sao vẫn phải học điều này khi đã có AI

### 2.1 Một khảo sát cho thấy kỹ năng này vẫn được coi trọng

Dev Barometer Q2 2026 của BairesDev (khảo sát 1.569 lập trình viên ở 77 quốc gia, gồm 1.059 dev trẻ và 510 dev cao cấp, công bố tháng 6/2026) ghi nhận 48% dev trẻ xếp **giải quyết vấn đề và tư duy phân tích** là kỹ năng quan trọng nhất để được tuyển, gần gấp ba mức 18% chọn thành thạo công cụ AI. Cũng trong khảo sát này, chỉ 16% dev cao cấp nói dev trẻ hiểu đầy đủ code do AI sinh ra mà họ nộp.

Cần thận trọng ba điểm. Thứ nhất, BairesDev là công ty thuê ngoài phần mềm có lợi ích thương mại trong chủ đề này. Thứ hai, trang gốc từ chối truy cập khi tôi mở, nên tôi đọc các con số qua kết quả tìm kiếm trích từ thông cáo báo chí và bộ slide của họ, chưa tự đọc bảng gốc. Thứ ba, 16% là đánh giá của dev cao cấp về dev trẻ, không phải số đo mức hiểu thật. Hãy đọc đây là một **tín hiệu**, không phải bằng chứng cứng.

### 2.2 Vì sao hỏi AI ngay có thể khiến bạn mất khả năng phán xét

Giả sử bạn dán cả tệp lỗi cho AI và nhận về một chỗ sửa. Nếu bạn chưa biết lỗi nằm đâu, bạn chỉ còn hai lựa chọn: tin hoặc không tin. Nghiên cứu của Perry và cộng sự (Stanford, 2022) với 47 người tham gia thấy nhóm dùng trợ lý AI viết code kém an toàn hơn ở bốn trên năm bài nhưng lại **tin hơn** rằng code của mình an toàn. Đó là mô hình đời cũ và nhóm nhỏ, nhưng nó gợi ý một rủi ro đáng để ý: cảm giác chắc chắn không đi cùng việc đúng.

**Điều rút ra:** dùng AI vẫn tốt. Nhưng bạn cần tự thu hẹp vùng nghi ngờ trước, để câu trả lời của AI có thứ để đối chiếu.

---

## 3. Năm bước, kèm một ví dụ chạy thật

Ví dụ: một chương trình tính giỏ hàng nhỏ. Quy tắc: đơn từ 100 trở lên được giảm 10%. Phép thử báo lỗi: đơn có tổng đúng 100 không được giảm. Kho có 20 commit và đúng một commit làm hỏng quy tắc. Script tạo kho ở phần 5.

### Bước 1. Tái hiện: một lệnh, cùng một kết quả

**Hình dung:** giữ nguyên hiện trường, không để ai dọn.

Việc đầu tiên không phải sửa, mà là có **một lệnh làm lỗi xảy ra mỗi lần**:

```bash
python3 test_cart.py
echo "exit=$?"
```

```text
FAIL total([(50, 2)]) = 100.0, want 90.0
exit=1
```

Hai dòng này là hiện trường: đơn gồm 2 món giá 50 (tổng 100) phải tính ra 90.0 nhưng chương trình ra 100.0. Mã kết thúc là 1, tức "không đạt". Nếu chạy hai lần mà ra hai kết quả khác nhau thì chưa tái hiện được, và đó là việc phải giải quyết trước (xem phần giới hạn).

### Bước 2. Thu nhỏ: bỏ chi tiết thừa

**Hình dung:** bỏ bớt người trong phòng cho đến khi chỉ còn những người liên quan.

Thử bỏ dần từng phần của ca lỗi và hỏi: lỗi còn không? Đây là kết quả của lần chạy thật, từ một giỏ hàng lớn thu về ca nhỏ nhất:

| Ca thử | Dữ liệu | Tổng trước giảm | Kết quả | Còn lỗi? |
|---|---|---|---|---|
| 3 dòng hàng | `[(25, 2), (25, 2), (0, 5)]` | 100 | 100.0 | có |
| Bỏ dòng giá 0 | `[(25, 2), (25, 2)]` | 100 | 100.0 | có |
| Gộp thành một dòng | `[(50, 2)]` | 100 | 100.0 | có |
| Một món | `[(100, 1)]` | 100 | 100.0 | có |

Số dòng hàng, số món, giá từng món đều không quan trọng. Thứ còn lại là **tổng trước giảm đúng bằng 100**. Ca lỗi tối thiểu bây giờ là một dòng: `total([(100, 1)])`.

### Bước 3. Đặt một giả thuyết có thể sai, và đoán kết quả trước

**Hình dung:** "nếu thủ phạm vào bằng cửa sau thì sẽ có bùn ở sàn bếp".

Từ bước 2, giả thuyết: **hàm giảm giá xử lý sai đúng tại biên 100**. Giả thuyết tốt cho phép đoán trước. Nếu đúng thì: dưới 100 không giảm, đúng 100 lẽ ra giảm mà không giảm, trên 100 có giảm. Rồi mới chạy:

```python
discount(99.99)   # 0.0
discount(100)     # 0.0 (lẽ ra phải là 10.0)
discount(100.01)  # 10.0
```

Đúng như dự đoán: giảm giá chỉ bắt đầu **sau** 100 chứ không phải từ 100. Giả thuyết đứng vững, và bạn biết mình đang tìm một so sánh "lớn hơn" lẽ ra là "lớn hơn hoặc bằng". Một giả thuyết mà kết quả nào cũng "khớp" thì không có ích. Nếu kết quả ở đây là `discount(100) = 10.0`, bạn phải bỏ giả thuyết này.

Đến đây có thể đã thấy ngay chỗ sửa. Nhưng trong dự án thật, hàm đó có thể bị đụng đến bởi nhiều người, và câu hỏi "**thay đổi nào** làm nó hỏng" quan trọng không kém câu hỏi "hỏng ở đâu". Đó là bước 4.

### Bước 4. Chia đôi: loại một nửa vùng nghi ngờ mỗi lần

**Hình dung:** đoán số từ 1 đến 100. Bạn không hỏi "có phải 1 không, có phải 2 không", mà hỏi "lớn hơn 50 không". Mỗi câu hỏi loại một nửa.

Lịch sử có 20 commit. Commit đầu chạy đúng, commit mới nhất sai. Có 19 commit nghi ngờ. `git bisect` chọn commit ở giữa để bạn thử, rồi loại một nửa. Đây là phiên chạy thật:

```bash
git bisect start HEAD $(git rev-list --max-parents=0 HEAD)
```

```text
status: waiting for both 'good' and 'bad' commits
status: waiting for 'good' commit(s), 'bad' commit known
Bisecting: 9 revisions left to test after this (roughly 3 steps)
[5f01574] feat: add free shipping limit
```

Git đã chọn commit ở giữa và chuyển bạn đến đó. Bạn chạy phép thử rồi đánh dấu `git bisect good` hoặc `git bisect bad`. Với **git bisect run**, Git tự chạy phép thử ở mỗi bước và dựa vào **mã kết thúc** (0 là tốt, 1 đến 127 trừ 125 là xấu, theo tài liệu Git):

```bash
git bisect run python3 test_cart.py
```

```text
running 'python3' 'test_cart.py'
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[f1da7cd] feat: add min order note
running 'python3' 'test_cart.py'
FAIL total([(50, 2)]) = 100.0, want 90.0
Bisecting: 2 revisions left to test after this (roughly 1 step)
[2004cf0] feat: add coupon placeholder
running 'python3' 'test_cart.py'
FAIL total([(50, 2)]) = 100.0, want 90.0
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[6b693ef] refactor: simplify discount check
running 'python3' 'test_cart.py'
FAIL total([(50, 2)]) = 100.0, want 90.0
6b693ef is the first 'bad' commit
commit 6b693ef

    refactor: simplify discount check

 cart.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first 'bad' commit
```

Phiên này kiểm **4 lần**: commit giữa (đạt), rồi ba commit đều không đạt, đến khi chỉ còn một commit. Thủ phạm là `refactor: simplify discount check`. Xem thay đổi của nó:

```bash
git diff 6b693ef~1 6b693ef
```

```diff
-    if total >= 100:
+    if total > 100:
```

Một ký tự. Tên commit nói là "làm gọn", nhưng nó đổi hành vi. Đây là chỗ mà tên commit và nội dung thật có thể lệch nhau, và vì sao bạn nên tìm commit bằng phép thử chứ không bằng đọc tên.

Đừng quên kết thúc phiên bằng `git bisect reset` để Git đưa bạn về nhánh cũ.

#### Chia đôi cũng dùng được ngoài Git

Ý tưởng "loại một nửa" dùng ở mọi nơi: tạm tắt nửa số dòng của một hàm dài xem lỗi còn không, in giá trị ở giữa luồng xử lý để biết hỏng trước hay sau điểm đó, hoặc tắt một nửa số tính năng bật cờ. Mỗi lần loại một nửa đưa bạn gần hơn nhiều so với nhìn từng dòng.

### Bước 5. Sửa, kiểm lại, và chặn lỗi quay lại

**Hình dung:** lập hồ sơ để vụ án không mở lại.

Sửa một ký tự (`>` thành `>=`), rồi chạy phép thử:

```bash
python3 test_cart.py
echo "exit=$?"
```

```text
exit=0
```

Đạt. Bạn cũng kiểm các biên liền kề, và kết quả thật là:

```python
total([(99.99, 1)])   # 99.99  (không giảm)
total([(100, 1)])     # 90.0   (giảm 10%)
total([(100.01, 1)])  # 90.01  (giảm 10%)
```

Phép thử **đã có sẵn** trong kho (kiểm đơn đúng 100), nên lỗi này sẽ bị bắt nếu ai đó làm hỏng lần nữa. Nếu chưa có thì việc thêm phép thử chính là bước này: lỗi nào cũng nên để lại một phép thử cho chính nó. Cách viết phép thử tốt hơn có trong bài [Kiểm thử code do AI viết](https://www.stacklogs.io.vn/ky-thuat/kiem-thu-code-do-ai-viet/).

**Điều rút ra:** năm bước đưa bạn từ "chương trình sai" đến "một ký tự trong một commit cụ thể", với bằng chứng ở mỗi bước. Phần còn lại là thao tác sửa.

---

## 4. Phép tính để bạn tự kiểm lại: chia đôi nhanh đến đâu (có thể bỏ qua)

Với N commit nghi ngờ, chia đôi cần tối đa khoảng log2(N) lần kiểm, làm tròn lên. Đây là kết quả tính thật:

```python
import math
for n in (19, 99, 999, 9999):
    print(n, math.ceil(math.log2(n)), round(math.log2(n), 2))
```

| Số commit nghi ngờ | Số lần kiểm tối đa khi chia đôi | log2 |
|---|---|---|
| 19 (kho thử của bài) | 5 | 4,25 |
| 99 | 7 | 6,63 |
| 999 | 10 | 9,96 |
| 9.999 | 14 | 13,29 |

Trong lần chạy thật, Git cần **4** lần, không phải 5. Con số 5 là mức tối đa, số lần thật phụ thuộc vị trí của lỗi. Nếu đi tuần tự từ commit cũ nhất, bạn cần 10 lần để chạm commit 11 (thủ phạm là commit thứ 11), và tối đa 19. Lưu ý: những con số này giả định mỗi lần kiểm cho kết quả đáng tin và mỗi commit đều chạy được. Nếu một số commit không build được, Git cho phép bỏ qua chúng bằng mã 125 nhưng sẽ cần thêm lần kiểm.

---

## 5. Bài tập: tự làm lại, kèm đáp án

Lưu script này thành `make_repo.sh`, chạy `bash make_repo.sh`. Nó tạo thư mục `cart-demo` với 20 commit, trong đó đúng một commit làm hỏng phép giảm giá.

```bash
#!/usr/bin/env bash
# Tạo kho thử có 20 commit, trong đó đúng một commit làm hỏng phép giảm giá.
set -e
rm -rf cart-demo
mkdir cart-demo
cd cart-demo
git init -q -b main
git config user.name "debug-demo"
git config user.email "demo@example.com"

c() { cat >> "$2"; git add -A; git commit -q -m "$1"; }   # c "thông điệp" tệp < nội dung

cat > test_cart.py <<'EOF'
import sys
from cart import total

checks = [
    ([(10, 3)], 30.0),     # dưới 100: không giảm
    ([(50, 2)], 90.0),     # đúng 100: giảm 10%
    ([(60, 2)], 108.0),    # trên 100: giảm 10%
]
bad = [(i, want, total(i)) for i, want in checks if total(i) != want]
for i, want, got in bad:
    print(f"FAIL total({i}) = {got}, want {want}")
sys.exit(1 if bad else 0)
EOF
c "feat: add subtotal, discount, total and a check" cart.py <<'EOF'
def subtotal(items):
    return sum(price * qty for price, qty in items)

def discount(total):
    if total >= 100:
        return round(total * 0.10, 2)
    return 0.0

def total(items):
    s = subtotal(items)
    return round(s - discount(s), 2)
EOF
c "feat: add shipping fee" cart.py <<'EOF'

SHIPPING = 5.0
EOF
c "docs: explain discount rule" cart.py <<'EOF'

# Giảm 10% cho đơn từ 100 trở lên.
EOF
c "feat: add currency format" cart.py <<'EOF'

def money(x):
    return f"{x:,.2f}"
EOF
c "feat: add empty cart helper" cart.py <<'EOF'

def is_empty(items):
    return len(items) == 0
EOF
c "feat: add item count" cart.py <<'EOF'

def count(items):
    return sum(qty for _, qty in items)
EOF
c "feat: add max quantity" cart.py <<'EOF'

MAX_QTY = 99
EOF
c "feat: add tax rate constant" cart.py <<'EOF'

TAX_RATE = 0.08
EOF
c "feat: add receipt title" cart.py <<'EOF'

def title(name):
    return f"Receipt for {name}"
EOF
c "feat: add free shipping limit" cart.py <<'EOF'

FREE_SHIPPING_FROM = 200
EOF
perl -pi -e 's/total >= 100/total > 100/' cart.py
git add -A; git commit -q -m "refactor: simplify discount check"
c "feat: add coupon placeholder" cart.py <<'EOF'

COUPONS = {}
EOF
c "feat: add rounding helper" cart.py <<'EOF'

def r2(x):
    return round(x, 2)
EOF
c "feat: add item label" cart.py <<'EOF'

def label(price, qty):
    return f"{qty} x {price}"
EOF
c "feat: add min order note" cart.py <<'EOF'

MIN_ORDER = 1
EOF
c "feat: add currency code" cart.py <<'EOF'

CURRENCY = "USD"
EOF
c "feat: add store name" cart.py <<'EOF'

STORE = "Demo Shop"
EOF
c "feat: add version string" cart.py <<'EOF'

VERSION = "1.0"
EOF
c "feat: add free item note" cart.py <<'EOF'

FREE_ITEM_NOTE = "none"
EOF
c "chore: add end marker" cart.py <<'EOF'

# end of file
EOF
```

Hãy **thử trước khi đọc đáp án**:

1. Vào `cart-demo`, chạy `python3 test_cart.py` và ghi lại kết quả (bước 1).
2. Thu nhỏ ca lỗi bằng tay, thử vài giá trị khác (bước 2).
3. Viết giả thuyết của bạn thành một câu, kèm kết quả bạn **đoán** cho ba giá trị 99.99, 100 và 100.01 (bước 3).
4. Chạy `git bisect` bằng tay (đánh dấu từng commit) một lần, rồi chạy lại bằng `git bisect run python3 test_cart.py`. Đếm số lần kiểm (bước 4).
5. Sửa, chạy lại phép thử (bước 5).

**Đáp án:**

- Commit gây lỗi có tên `refactor: simplify discount check`, là commit thứ 11 trong 20 commit.
- Thay đổi của nó: `>=` thành `>` trong hàm `discount` của `cart.py`.
- Số lần kiểm: tối đa 5, trong lần chạy của tôi là 4. Của bạn có thể là 4 hoặc 5 và hash khác, đó là bình thường.
- Một cái bẫy: phép thử phải **có mặt ở mọi commit bạn kiểm**. Ở đây `test_cart.py` có từ commit đầu nên an toàn. Nếu phép thử chỉ được thêm ở commit giữa, các commit cũ hơn sẽ báo "xấu" vì thiếu tệp, và Git kết luận sai. Cách tránh: để phép thử ngoài kho (tài liệu Git cũng khuyên giữ script kiểm ở ngoài kho) hoặc chọn commit tốt ở sau khi phép thử đã có.

---

## 6. Báo lỗi gọn cho AI: làm sau năm bước

Sau năm bước, bạn có đủ thứ để viết một báo lỗi mà AI dùng được và bạn kiểm được. Mẫu để dán:

```text
Lỗi: <một câu>
Tái hiện (nhỏ nhất): <lệnh hoặc dữ liệu>
Mong đợi: <kết quả đúng>
Thực tế: <đầu ra nguyên văn>
Đã loại trừ: <các giả thuyết đã thử và kết quả>
Nghi ngờ: <giả thuyết hiện tại, kèm bằng chứng>
Phạm vi: <tệp, commit nghi ngờ>
Yêu cầu: giải thích nguyên nhân trước khi sửa,
đề xuất thay đổi nhỏ nhất, và nói rõ điều gì chưa kiểm.
```

Với ví dụ của bài, báo lỗi điền như sau:

```text
Lỗi: đơn có tổng đúng 100 không được giảm 10%.
Tái hiện (nhỏ nhất): total([(100, 1)])
Mong đợi: 90.0
Thực tế: 100.0
Đã loại trừ: số dòng hàng và giá từng món không ảnh hưởng.
discount(99.99) = 0.0, discount(100) = 0.0, discount(100.01) = 10.0.
Nghi ngờ: so sánh tại biên 100 trong discount().
Phạm vi: git bisect cho thấy commit "refactor: simplify discount check".
Yêu cầu: giải thích nguyên nhân trước khi sửa và đề xuất thay đổi nhỏ nhất.
```

Với báo lỗi này, câu trả lời đúng gần như quá rõ: AI sẽ chỉ vào so sánh ở biên. Điều quan trọng hơn: **bạn kiểm được câu trả lời** bằng chính phép thử và các giá trị biên đã có. Nếu AI đề xuất sửa chỗ khác, bạn biết ngay là lệch. Về cách đòi AI nộp bằng chứng cho phần sửa, xem bài [Làm cho kết quả của AI tự chứng minh](https://www.stacklogs.io.vn/ky-thuat/ket-qua-ai-tu-chung-minh/).

---

## 7. Chỗ người mới hay vấp

| Lỗi hay gặp | Vì sao hỏng | Cách tránh |
|---|---|---|
| Sửa nhiều thứ cùng lúc | Lỗi hết nhưng không biết thay đổi nào có tác dụng, và có thể sinh lỗi mới | Mỗi lần đổi một thứ, chạy lại phép thử |
| Tin giả thuyết đầu tiên | Giả thuyết đầu tiên thường là điều bạn muốn tin | Đoán trước kết quả, và sẵn sàng bỏ giả thuyết khi kết quả khác |
| Không lưu lại ca lỗi | Mai mốt không tái hiện được, người khác cũng không | Ghi lệnh và dữ liệu tái hiện, thêm phép thử |
| Sửa triệu chứng, không sửa nguyên nhân | Thêm điều kiện đặc biệt cho đúng ca lỗi (ví dụ cứ thấy 100 thì trả 90) | Hỏi "vì sao" cho đến khi gặp nguyên nhân |
| Chạy bisect với phép thử chập chờn | Một lần kiểm sai là cả cuộc chia đôi lạc hướng | Chạy phép thử vài lần mỗi bước, hoặc sửa phép thử trước |
| Phép thử không có ở các commit cũ | Git coi thiếu tệp là "xấu" | Để phép thử ngoài kho, hoặc chọn mốc tốt sau khi nó xuất hiện |
| Dán cả tệp lỗi cho AI ngay | Không có thứ để phán xét câu trả lời | Làm năm bước, rồi đưa báo lỗi gọn |

### 7.1 Minh họa: một buổi sáng hai cách làm

Đây là tình huống minh họa, không nhắc đến người hay sản phẩm thật.

**Cách một: hỏi AI ngay.**

- **09:30.** Một người dùng báo: "Đơn 100 đồng của tôi không được giảm." Dev dán cả tệp `cart.py` cho AI và hỏi cách sửa.
- **09:40.** AI đề xuất thêm một điều kiện đặc biệt cho tổng bằng 100 trong hàm `total`. Phép thử qua, dev gộp nhánh.
- **11:15.** Hàm `discount` vẫn dùng `>`, nên một chỗ khác gọi `discount` trực tiếp (báo cáo cuối ngày) vẫn không giảm đúng. Dev lại mất thời gian đi tìm.

**Cách hai: năm bước rồi mới hỏi.**

- **09:30.** Cùng báo cáo. Dev chạy phép thử, thấy lỗi tái hiện được bằng một lệnh.
- **09:40.** Thu nhỏ về `total([(100, 1)])`, đoán và thử ba giá trị biên, giả thuyết đứng vững.
- **09:50.** `git bisect run` chỉ ra commit "refactor: simplify discount check", thay đổi một ký tự.
- **10:00.** Dev đưa báo lỗi gọn cho AI để chắc không còn chỗ nào dùng cùng điều kiện, rồi sửa tại gốc, gộp nhánh.

Việc nên làm: tập thói quen thu nhỏ và đặt giả thuyết **trước** khi nhờ AI, để bạn kiểm được điều nó nói. Đây là minh họa nên không chứng minh điều gì. Bằng chứng nằm ở các nghiên cứu đã dẫn: Perry và cộng sự (2022) cho thấy dễ chủ quan khi dùng trợ lý AI, và khảo sát BairesDev cho thấy giải quyết vấn đề vẫn được coi trọng. Xem thêm bài [Git cho người dùng agent](https://www.stacklogs.io.vn/ky-thuat/git-cho-nguoi-dung-agent/) về cách để lại các điểm lưu giúp bisect có việc để làm.

---

## 8. Tám câu hỏi trước khi đưa lỗi cho AI

1. Tôi có **một lệnh** làm lỗi xảy ra mỗi lần chưa?
2. Ca lỗi đã **nhỏ nhất** chưa: bỏ thêm gì nữa thì lỗi biến mất?
3. Giả thuyết của tôi là gì, và tôi đã **đoán trước** kết quả chưa?
4. Kết quả có khớp dự đoán không, hay tôi đang gượng ép cho khớp?
5. Lỗi này có phải **lỗi quay lại** (từng chạy đúng) không, và có commit tốt để làm mốc chưa?
6. Tôi đã loại trừ những khả năng nào, và ghi lại chưa?
7. Báo lỗi của tôi có đủ **mong đợi, thực tế nguyên văn, đã loại trừ**?
8. Khi AI trả lời, tôi dùng **cái gì** để kiểm nó: phép thử, giá trị biên, hay chỉ cảm giác?

---

## 9. Nhìn cân bằng: cách này làm được gì, chưa làm được gì

**Làm được:** biến "chương trình sai" thành một chỗ cụ thể kèm bằng chứng, thu hẹp vùng nghi ngờ rất nhanh nhờ chia đôi, giúp báo lỗi gọn và kiểm được, và để lại phép thử chặn lỗi quay lại.

**Chưa làm được:**

- `git bisect` cần **phép thử cho kết quả giống nhau mỗi lần** và lịch sử có các commit chạy được. Lỗi phụ thuộc thời gian, dữ liệu thật, thứ tự chạy hay môi trường khó tái hiện hơn nhiều.
- Nếu lỗi có từ commit đầu tiên thì không có commit tốt để làm mốc. Nếu lịch sử bị gộp thành vài commit lớn thì chia đôi chỉ cho bạn một khối lớn.
- Một số lỗi cần đọc code kỹ hoặc hiểu nghiệp vụ, không phải lỗi hồi quy.
- AI đôi khi tìm ra nguyên nhân nhanh hơn người, kể cả khi bạn chưa làm đủ các bước. Bài không nói là đừng hỏi AI, mà là hỏi sau để kiểm được.
- Tôi **chưa tìm thấy** nghiên cứu đối chứng đo xem năm bước này giúp tìm lỗi nhanh hơn bao nhiêu. Nó là tổng hợp thực hành, kèm một ví dụ chạy thật trên một kho nhỏ do tôi dựng.
- Ví dụ chạy thật chỉ có một lỗi trong một kho 20 commit nhỏ. Kho thật phức tạp hơn nhiều.

**Đừng thổi phồng:** năm bước không đảm bảo tìm ra mọi lỗi. **Đừng coi nhẹ:** chi phí của việc sửa mà không hiểu thường nằm ở lần sau, khi lỗi quay lại ở chỗ khác.

---

## 10. Kết luận

Gỡ lỗi giống việc của thám tử: giữ hiện trường, thu nhỏ, đặt giả thuyết có thể sai, loại dần bằng cách chia đôi, rồi lập hồ sơ để lỗi không quay lại. Khi bạn đã làm những bước đó, báo lỗi cho AI trở nên ngắn, và bạn có thứ để kiểm câu trả lời của nó. Hãy thử bài tập với kho 20 commit: nó mất khoảng nửa giờ và để lại cho bạn một thói quen dùng được suốt nghề.

---

## Nguồn tham khảo

Số liệu lấy từ nguồn công khai tính đến tháng 10/2026. Mọi lệnh và kết quả trong bài chạy thật trên Git 2.56.0 và Python 3.14.8, hash commit khác nhau giữa các lần chạy.

- Git, [git-bisect documentation](https://git-scm.com/docs/git-bisect) (cách dùng, `git bisect run`, mã kết thúc 0, 1 đến 127 trừ 125, 125 để bỏ qua)
- BairesDev, [Dev Barometer Q2 2026](https://www.bairesdev.com/blog/dev-barometer-q2-2026-ai-career-reset/) và [thông cáo "16 percent of juniors fully understand AI code"](https://www.bairesdev.com/press/16-percent-of-juniors-fully-understand-ai-code/) (khảo sát của một công ty thuê ngoài, các con số tôi đọc qua kết quả tìm kiếm vì trang gốc chặn truy cập, hãy mở bản gốc trước khi trích)
- Perry và cộng sự, [Do Users Write More Insecure Code with AI Assistants?](https://arxiv.org/pdf/2211.03622) (Stanford, 2022)
