Buổi 17: Systematic Debugging - 4 Phase + Memory Healing
![]()
Phần Lý Thuyết (50 phút)
Iron Law of Debugging
"NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST"
Bất kỳ fix nào mà không có root cause investigation đều là "band-aid" — tạm thời, dễ gây regression, lãng phí token và thời gian.
Phase 0.5: Memory Integrity Check — SUSPECT → INVESTIGATE → VERIFY → HEAL
Trước khi bắt đầu phase 1, kiểm tra xem LLM có "nhớ sai" không.
SUSPECT (Nghi ngờ)
- Xác định module nào có bug: error stack trace, test failure
- Đọc
learnings.jsonlọc theo scope (module đó) - Ghi nhận: AI từng gặp bug tương tự không? Có memory nào liên quan?
INVESTIGATE (Điều tra)
- Khi viết code sai, AI có follow memory không?
- Ví dụ: dùng
parseInt()vào UUID (lỗi từ buổi trước), hay magic number 86400000 mà quên tạo constant?
VERIFY (Xác minh)
- Mở file hiện tại so sánh với pattern cũ
- Memory có còn đúng không? Codebase thay đổi chưa?
- Ví dụ: timezone handling thay đổi, nhưng code vẫn dùng cách cũ?
HEAL (Chữa lành)
- Invalidate: Xoá memory sai khỏi learnings.json
- Correct: Viết memory mới, đúng
- Scope-reduce: Thu hẹp scope ("only use this in tests", "only for UTC")
- Record meta-learning: "AI tends to forget constant extraction"
Phase 1: Root Cause Analysis (5-10 phút)
Step 1: Đọc Error Message
- Full stack trace? Line number? Error type?
- Exception message tells 80% of the story
Step 2: Reproduce Bug
- Viết test case gọi lại bug
- Đảm bảo reproducible, không random
Step 3: Check Changes
git loghoặc git diff file có bug- So sánh code ngày hôm nay vs. ngày hôm qua
- Ghi nhận: ai, khi nào, commit message
Step 4: Gather Evidence
- Add console.log() tại critical points
- Trace variable values
- Lấy dữ liệu từ database nếu cần
Step 5: Trace Data Flow
Input → Transform → Store → Retrieve → Output
↓
Xác định stage nào lỗiPhase 2: Pattern Analysis (5-10 phút)
Step 1: Tìm Working Examples
- Có code khác dùng pattern tương tự không?
- Nếu có, nó làm sao?
Step 2: Compare References
// ❌ BUG
function isOverdue(dueDate) {
return Date.now() - dueDate > 86400000; // magic number!
}
// ✅ WORKING EXAMPLE (từ project khác)
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
function isOverdue(dueDate) {
return Date.now() - dueDate > ONE_DAY_MS;
}Step 3: Identify Differences
- Magic number vs. named constant?
- Missing null check?
- Timezone issue?
Step 4: Understand Dependencies
- Hàm này dùng ở đâu?
- Có side effects không?
Phase 3: Hypothesis (5 phút)
Step 1: Form Single Hypothesis
- NOT "maybe this or that"
- ONE hypothesis: "isOverdue() returns true always because 86400000 là milliseconds nhưng dueDate là seconds"
Step 2: Test Minimally
const dueDate = Date.now() - 100; // 100ms ago
console.log(isOverdue(dueDate)); // expect trueStep 3: Verify Before Continuing
- Hypothesis đúng → proceed to Phase 4
- Hypothesis sai → back to Phase 1
Step 4: When Don't Know → Say So
- "I don't know why this fails" ✅ (honest)
- "It should work" ❌ (speculative)
Phase 4: Implementation (10 phút)
Step 1: Create Failing Test FIRST
// tests/models/task.test.js
describe('Task.isOverdue()', () => {
it('should return true if task is overdue', () => {
const now = Date.now();
const twoDaysAgo = now - (2 * 24 * 60 * 60 * 1000);
const task = new Task({ dueDate: twoDaysAgo });
expect(task.isOverdue()).toBe(true);
});
it('should return false if task is due later', () => {
const now = Date.now();
const twoDaysLater = now + (2 * 24 * 60 * 60 * 1000);
const task = new Task({ dueDate: twoDaysLater });
expect(task.isOverdue()).toBe(false);
});
});Step 2: Single Fix
// src/models/task.js
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
class Task {
isOverdue() {
return Date.now() - this.dueDate > ONE_DAY_MS;
}
}Step 3: Verify Test Passes
npm test -- task.test.js
# ✅ 2 passedStep 4: If ≥3 Fails → Question Architecture
- "This pattern not working — is design wrong?"
- Maybe refactor larger scope
Phase 5: Record Learning (MANDATORY)
{
"id": "magic-number-86400000",
"pattern": "Using raw milliseconds without named constant",
"whatFailed": "isOverdue() returned inconsistent results across timezone",
"why": "86400000 là magic number, dễ nhầm units (ms vs s)",
"howToPrevent": "Always extract time constants: const ONE_DAY_MS = 24*60*60*1000",
"scope": "Time calculations in Task model",
"codeExample": "const ONE_DAY_MS = 24 * 60 * 60 * 1000;",
"tokenSavings": "Learning this = 50 tokens, full debug = 3000 tokens"
}12+ Red Flags — Dấu Hiệu Sắp Có Bug
| Red Flag | Ý Nghĩa | Cách Phòng |
|---|---|---|
| Magic numbers | 86400000, 1000, 999 | Extract named constants |
| Deep nesting (>3) | if → if → if → if | Guard clauses, early return |
| No null check | user.address.city | Defensive: user?.address?.city |
| Hardcoded paths | /home/user/data | Use env vars, config |
| Same code 3+ places | Copy-paste logic | Extract function |
| Console.log left | debug code in prod | Remove or use logger |
| Commented old code | "// TODO fix later" | Delete or issue tracker |
| Timezone assumptions | "UTC is always used" | Explicit timezone handling |
| Type coercion | "123" == 123 | Strict comparison === |
| Mutable defaults | function(arr = []) | Use const, immutable |
| Silent failures | try/catch, no rethrow | Log error, explicit handling |
| Regex without test | /^\d+$/ | Test all edge cases |
Rationalization Table — Lý Do Lỏng Lẻo Để Tránh
| Rationalization | Nguy Hiểm | Cách Chối |
|---|---|---|
| "It should work" | Speculative, untested | "Let me verify with a test" |
| "I think the bug is..." | Guess, not investigation | "Let me trace the data" |
| "This minor thing won't break" | Famous last words | Root cause first |
| "We can fix it later" | Debt accumulates | Fix now, record learning |
| "Tests are optional" | Regression guarantee | TDD: test first |
Phần Practice (60 phút)
Lab 1: Logic Bug — isOverdue() (Magic Number)
Bug #6: src/models/task.js
// ❌ BUGGY CODE
class Task {
constructor(data) {
this.id = data.id;
this.title = data.title;
this.dueDate = data.dueDate; // milliseconds timestamp
}
isOverdue() {
return Date.now() - this.dueDate > 86400000; // MAGIC NUMBER!
}
}Yêu cầu:
- Follow Phase 1-4 systematically
- Viết failing test trước
- Xác định root cause: magic number 86400000
- Fix: extract ONE_DAY_MS constant
- Verify test passes
- Record learning
Expected Output:
// ✅ FIXED CODE
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
class Task {
constructor(data) {
this.id = data.id;
this.title = data.title;
this.dueDate = data.dueDate;
}
isOverdue() {
return Date.now() - this.dueDate > ONE_DAY_MS;
}
}Lab 2: Nesting Bug — filterTasks() (Deep Nesting)
Bug #7: src/models/task.js
// ❌ BUGGY CODE — 4 levels nesting
class TaskManager {
filterTasks(tasks, filters) {
if (tasks && tasks.length > 0) {
if (filters) {
if (filters.status) {
if (filters.status === 'completed') {
return tasks.filter(t => t.completed === true);
}
}
}
}
return [];
}
}Problem:
- 4 levels nesting → pyramid of doom
- Missing other filters (priority, dueDate)
- Hard to read, easy to miss edge cases
- Early returns should happen at each level
Yêu cầu:
- Trace data flow: tasks → filters → filtered result
- Identify problem: deep nesting, guard clauses missing
- Refactor with guard clauses + early returns
- Keep same logic, improve readability
- Write tests covering all paths
Expected Output:
// ✅ FIXED CODE — Guard clauses
class TaskManager {
filterTasks(tasks, filters) {
// Guard: no tasks
if (!tasks || tasks.length === 0) {
return [];
}
// Guard: no filters
if (!filters) {
return tasks;
}
// Guard: no status filter
if (!filters.status) {
return tasks;
}
// Main logic — 1 level only
return tasks.filter(t => t.status === filters.status);
}
}Test Suite:
describe('TaskManager.filterTasks()', () => {
let manager;
beforeEach(() => {
manager = new TaskManager();
});
it('should return empty array if tasks is null', () => {
expect(manager.filterTasks(null, {})).toEqual([]);
});
it('should return all tasks if filters is empty', () => {
const tasks = [{ id: 1, status: 'active' }, { id: 2, status: 'completed' }];
expect(manager.filterTasks(tasks, {})).toEqual(tasks);
});
it('should filter by status', () => {
const tasks = [
{ id: 1, status: 'active' },
{ id: 2, status: 'completed' }
];
const result = manager.filterTasks(tasks, { status: 'completed' });
expect(result).toEqual([{ id: 2, status: 'completed' }]);
});
});Lab 3: Regression Bug — Memory Healing (Phase 0.5)
Scenario: AI "Remembers Wrong"
Giả sử AI học từ lỗi cũ và áp dụng nhầm:
// ❌ WRONG LEARNING
// "UUID needs parseInt" — LỖI từ context khác
const uuid = '550e8400-e29b-41d4-a716-446655440000';
const userId = parseInt(uuid); // ❌ parseInt('550e...') = NaN!Yêu cầu Phase 0.5:
SUSPECT:
- Module: src/utils/userService.js
- Error: userId is NaN
- learnings.json có note: "UUID requires parseInt"
INVESTIGATE:
- Trace code: UUID → parseInt → NaN
- Kiểm tra AI logic: "parseInt works for numbers, not UUID"
VERIFY:
- UUID format: string with hyphens
- parseInt("550e...") = NaN ✓ (confirmed wrong)
- Current codebase dùng uuid package, không parseInt
HEAL:
{
"id": "uuid-parseInt-wrong",
"invalidated": true,
"reason": "parseInt only works for numeric strings, not UUIDs",
"correction": "Use uuid-validate or regex /^[0-9a-f]{8}-...$/ for validation",
"scopeReduce": "parseInt only for base-10 numeric conversions, never for UUIDs"
}Phần Quiz & Homework
Quiz (5 câu)
Q1: Iron Law bảo gì về fixing bugs?
- A) Fix nhanh, test sau
- B) ✅ Phải investigation root cause trước fix
- C) Fix 3 cách, chọn cách nhanh nhất
Q2: Phase nào kiểm tra "AI nhớ sai"?
- A) Phase 1
- B) ✅ Phase 0.5 Memory Integrity
- C) Phase 4
Q3: Magic number 86400000 là gì?
- A) Random number
- B) ✅ 24 * 60 * 60 * 1000 (milliseconds in 1 day)
- C) Error code
Q4 (Situation): Code có 5 levels nesting if statements. Đâu là red flag?
- A) Color không match
- B) ✅ Deep nesting → hard to read, use guard clauses
- C) Too many variables
Q5 (Situation): Test says "user.isPremium should be true". Anh/chị sẽ:
- A) Trust the test comment
- B) ✅ Run test, see actual output, verify hypothesis
- C) Assume test is correct
Homework
- Refactor: Tìm 1 hàm trong dự án cá nhân có 3+ levels nesting. Refactor dùng guard clauses.
- Extract Constants: Scan code tìm 3 magic numbers. Extract thành named constants.
- Memory Record: Ghi 1 bug anh/chị gặp tuần này vào learnings.json format (whatFailed, why, howToPrevent).
- Test Coverage: Viết test cho Phase 1 root cause analysis (reproduce bug bằng test case).
Tài Liệu Tham Khảo
- TaskFlow Codebase: src/models/task.js
- Test Suite: tests/models/task.test.js
- Debugging Checklist: docs/debugging-phases.md