Skip to content

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.json lọ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 log hoặ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ỗi

Phase 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

javascript
// ❌ 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

javascript
const dueDate = Date.now() - 100; // 100ms ago
console.log(isOverdue(dueDate)); // expect true

Step 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

javascript
// 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

javascript
// 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

bash
npm test -- task.test.js
# ✅ 2 passed

Step 4: If ≥3 Fails → Question Architecture

  • "This pattern not working — is design wrong?"
  • Maybe refactor larger scope

Phase 5: Record Learning (MANDATORY)

json
{
  "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ĩaCách Phòng
Magic numbers86400000, 1000, 999Extract named constants
Deep nesting (>3)if → if → if → ifGuard clauses, early return
No null checkuser.address.cityDefensive: user?.address?.city
Hardcoded paths/home/user/dataUse env vars, config
Same code 3+ placesCopy-paste logicExtract function
Console.log leftdebug code in prodRemove 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" == 123Strict comparison ===
Mutable defaultsfunction(arr = [])Use const, immutable
Silent failurestry/catch, no rethrowLog error, explicit handling
Regex without test/^\d+$/Test all edge cases

Rationalization Table — Lý Do Lỏng Lẻo Để Tránh

RationalizationNguy HiểmCá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 wordsRoot cause first
"We can fix it later"Debt accumulatesFix now, record learning
"Tests are optional"Regression guaranteeTDD: test first

Phần Practice (60 phút)

Lab 1: Logic Bug — isOverdue() (Magic Number)

Bug #6: src/models/task.js

javascript
// ❌ 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:

  1. Follow Phase 1-4 systematically
  2. Viết failing test trước
  3. Xác định root cause: magic number 86400000
  4. Fix: extract ONE_DAY_MS constant
  5. Verify test passes
  6. Record learning

Expected Output:

javascript
// ✅ 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

javascript
// ❌ 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:

  1. Trace data flow: tasks → filters → filtered result
  2. Identify problem: deep nesting, guard clauses missing
  3. Refactor with guard clauses + early returns
  4. Keep same logic, improve readability
  5. Write tests covering all paths

Expected Output:

javascript
// ✅ 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:

javascript
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:

javascript
// ❌ 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:

json
{
  "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

  1. Refactor: Tìm 1 hàm trong dự án cá nhân có 3+ levels nesting. Refactor dùng guard clauses.
  2. Extract Constants: Scan code tìm 3 magic numbers. Extract thành named constants.
  3. Memory Record: Ghi 1 bug anh/chị gặp tuần này vào learnings.json format (whatFailed, why, howToPrevent).
  4. 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

Powered by CodyMaster × VitePress