docs(debugger): restore high-value examples and patterns
- Add rich code examples for investigation techniques (minimal reproduction, working backwards, differential debugging, etc.) - Restore test-first debugging pattern with full process - Add stability testing examples (parallel stress, race condition) - Add hypothesis testing pitfalls table - Add research vs reasoning decision tree and red flags - Add combining techniques guidance 990 → 1,184 lines (+194 lines of actionable content) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -177,21 +177,44 @@ Don't fall in love with your first hypothesis. Generate alternatives.
|
|||||||
|
|
||||||
**Strong inference:** Design experiments that differentiate between competing hypotheses.
|
**Strong inference:** Design experiments that differentiate between competing hypotheses.
|
||||||
|
|
||||||
```
|
```javascript
|
||||||
Problem: Form submission fails intermittently
|
// Problem: Form submission fails intermittently
|
||||||
|
// Competing hypotheses: network timeout, validation, race condition, rate limiting
|
||||||
|
|
||||||
Competing hypotheses:
|
try {
|
||||||
1. Network timeout
|
console.log('[1] Starting validation');
|
||||||
2. Validation failure
|
const validation = await validate(formData);
|
||||||
3. Race condition with auto-save
|
console.log('[1] Validation passed:', validation);
|
||||||
4. Server-side rate limiting
|
|
||||||
|
|
||||||
Design experiment that differentiates:
|
console.log('[2] Starting submission');
|
||||||
- Add logging at each stage (validation → submission → response → UI update)
|
const response = await api.submit(formData);
|
||||||
- Observe which stage fails
|
console.log('[2] Response received:', response.status);
|
||||||
- One experiment, differentiates four hypotheses
|
|
||||||
|
console.log('[3] Updating UI');
|
||||||
|
updateUI(response);
|
||||||
|
console.log('[3] Complete');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('[ERROR] Failed at stage:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe results:
|
||||||
|
// - Fails at [2] with timeout → Network
|
||||||
|
// - Fails at [1] with validation error → Validation
|
||||||
|
// - Succeeds but [3] has wrong data → Race condition
|
||||||
|
// - Fails at [2] with 429 status → Rate limiting
|
||||||
|
// One experiment, differentiates four hypotheses.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Hypothesis Testing Pitfalls
|
||||||
|
|
||||||
|
| Pitfall | Problem | Solution |
|
||||||
|
|---------|---------|----------|
|
||||||
|
| Testing multiple hypotheses at once | You change three things and it works - which one fixed it? | Test one hypothesis at a time |
|
||||||
|
| Confirmation bias | Only looking for evidence that confirms your hypothesis | Actively seek disconfirming evidence |
|
||||||
|
| Acting on weak evidence | "It seems like maybe this could be..." | Wait for strong, unambiguous evidence |
|
||||||
|
| Not documenting results | Forget what you tested, repeat experiments | Write down each hypothesis and result |
|
||||||
|
| Abandoning rigor under pressure | "Let me just try this..." | Double down on method when pressure increases |
|
||||||
|
|
||||||
</hypothesis_testing>
|
</hypothesis_testing>
|
||||||
|
|
||||||
<investigation_techniques>
|
<investigation_techniques>
|
||||||
@@ -242,6 +265,22 @@ Often you'll spot the bug mid-explanation: "Wait, I never verified that B return
|
|||||||
4. Repeat until bare minimum
|
4. Repeat until bare minimum
|
||||||
5. Bug is now obvious in stripped-down code
|
5. Bug is now obvious in stripped-down code
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```jsx
|
||||||
|
// Start: 500-line React component with 15 props, 8 hooks, 3 contexts
|
||||||
|
// End after stripping:
|
||||||
|
function MinimalRepro() {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCount(count + 1); // Bug: infinite loop, missing dependency array
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>{count}</div>;
|
||||||
|
}
|
||||||
|
// The bug was hidden in complexity. Minimal reproduction made it obvious.
|
||||||
|
```
|
||||||
|
|
||||||
## Working Backwards
|
## Working Backwards
|
||||||
|
|
||||||
**When:** You know correct output, don't know why you're not getting it.
|
**When:** You know correct output, don't know why you're not getting it.
|
||||||
@@ -256,6 +295,16 @@ Often you'll spot the bug mid-explanation: "Wait, I never verified that B return
|
|||||||
4. Repeat backwards through call stack
|
4. Repeat backwards through call stack
|
||||||
5. Find divergence point (where expected vs actual first differ)
|
5. Find divergence point (where expected vs actual first differ)
|
||||||
|
|
||||||
|
**Example:** UI shows "User not found" when user exists
|
||||||
|
```
|
||||||
|
Trace backwards:
|
||||||
|
1. UI displays: user.error → Is this the right value to display? YES
|
||||||
|
2. Component receives: user.error = "User not found" → Correct? NO, should be null
|
||||||
|
3. API returns: { error: "User not found" } → Why?
|
||||||
|
4. Database query: SELECT * FROM users WHERE id = 'undefined' → AH!
|
||||||
|
5. FOUND: User ID is 'undefined' (string) instead of a number
|
||||||
|
```
|
||||||
|
|
||||||
## Differential Debugging
|
## Differential Debugging
|
||||||
|
|
||||||
**When:** Something used to work and now doesn't. Works in one environment but not another.
|
**When:** Something used to work and now doesn't. Works in one environment but not another.
|
||||||
@@ -275,6 +324,18 @@ Often you'll spot the bug mid-explanation: "Wait, I never verified that B return
|
|||||||
|
|
||||||
**Process:** List differences, test each in isolation, find the difference that causes failure.
|
**Process:** List differences, test each in isolation, find the difference that causes failure.
|
||||||
|
|
||||||
|
**Example:** Works locally, fails in CI
|
||||||
|
```
|
||||||
|
Differences:
|
||||||
|
- Node version: Same ✓
|
||||||
|
- Environment variables: Same ✓
|
||||||
|
- Timezone: Different! ✗
|
||||||
|
|
||||||
|
Test: Set local timezone to UTC (like CI)
|
||||||
|
Result: Now fails locally too
|
||||||
|
FOUND: Date comparison logic assumes local timezone
|
||||||
|
```
|
||||||
|
|
||||||
## Observability First
|
## Observability First
|
||||||
|
|
||||||
**When:** Always. Before making any fix.
|
**When:** Always. Before making any fix.
|
||||||
@@ -313,6 +374,15 @@ console.log('[updateUser] Called from:', new Error().stack);
|
|||||||
4. After each uncomment, test
|
4. After each uncomment, test
|
||||||
5. When bug returns, you found the culprit
|
5. When bug returns, you found the culprit
|
||||||
|
|
||||||
|
**Example:** Some middleware breaks requests, but you have 8 middleware functions
|
||||||
|
```javascript
|
||||||
|
app.use(helmet()); // Uncomment, test → works
|
||||||
|
app.use(cors()); // Uncomment, test → works
|
||||||
|
app.use(compression()); // Uncomment, test → works
|
||||||
|
app.use(bodyParser.json({ limit: '50mb' })); // Uncomment, test → BREAKS
|
||||||
|
// FOUND: Body size limit too high causes memory issues
|
||||||
|
```
|
||||||
|
|
||||||
## Git Bisect
|
## Git Bisect
|
||||||
|
|
||||||
**When:** Feature worked in past, broke at unknown commit.
|
**When:** Feature worked in past, broke at unknown commit.
|
||||||
@@ -342,6 +412,17 @@ git bisect bad # or good, based on testing
|
|||||||
| Many possible causes | Comment out everything, Binary search |
|
| Many possible causes | Comment out everything, Binary search |
|
||||||
| Always | Observability first (before making changes) |
|
| Always | Observability first (before making changes) |
|
||||||
|
|
||||||
|
## Combining Techniques
|
||||||
|
|
||||||
|
Techniques compose. Often you'll use multiple together:
|
||||||
|
|
||||||
|
1. **Differential debugging** to identify what changed
|
||||||
|
2. **Binary search** to narrow down where in code
|
||||||
|
3. **Observability first** to add logging at that point
|
||||||
|
4. **Rubber duck** to articulate what you're seeing
|
||||||
|
5. **Minimal reproduction** to isolate just that behavior
|
||||||
|
6. **Working backwards** to find the root cause
|
||||||
|
|
||||||
</investigation_techniques>
|
</investigation_techniques>
|
||||||
|
|
||||||
<verification_patterns>
|
<verification_patterns>
|
||||||
@@ -408,6 +489,63 @@ done
|
|||||||
|
|
||||||
If it fails even once, it's not fixed.
|
If it fails even once, it's not fixed.
|
||||||
|
|
||||||
|
**Stress testing (parallel):**
|
||||||
|
```javascript
|
||||||
|
// Run many instances in parallel
|
||||||
|
const promises = Array(50).fill().map(() =>
|
||||||
|
processData(testInput)
|
||||||
|
);
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
// All results should be correct
|
||||||
|
```
|
||||||
|
|
||||||
|
**Race condition testing:**
|
||||||
|
```javascript
|
||||||
|
// Add random delays to expose timing bugs
|
||||||
|
async function testWithRandomTiming() {
|
||||||
|
await randomDelay(0, 100);
|
||||||
|
triggerAction1();
|
||||||
|
await randomDelay(0, 100);
|
||||||
|
triggerAction2();
|
||||||
|
await randomDelay(0, 100);
|
||||||
|
verifyResult();
|
||||||
|
}
|
||||||
|
// Run this 1000 times
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test-First Debugging
|
||||||
|
|
||||||
|
**Strategy:** Write a failing test that reproduces the bug, then fix until the test passes.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Proves you can reproduce the bug
|
||||||
|
- Provides automatic verification
|
||||||
|
- Prevents regression in the future
|
||||||
|
- Forces you to understand the bug precisely
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
```javascript
|
||||||
|
// 1. Write test that reproduces bug
|
||||||
|
test('should handle undefined user data gracefully', () => {
|
||||||
|
const result = processUserData(undefined);
|
||||||
|
expect(result).toBe(null); // Currently throws error
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Verify test fails (confirms it reproduces bug)
|
||||||
|
// ✗ TypeError: Cannot read property 'name' of undefined
|
||||||
|
|
||||||
|
// 3. Fix the code
|
||||||
|
function processUserData(user) {
|
||||||
|
if (!user) return null; // Add defensive check
|
||||||
|
return user.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Verify test passes
|
||||||
|
// ✓ should handle undefined user data gracefully
|
||||||
|
|
||||||
|
// 5. Test is now regression protection forever
|
||||||
|
```
|
||||||
|
|
||||||
## Verification Checklist
|
## Verification Checklist
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
@@ -450,6 +588,18 @@ Your verification might be wrong if:
|
|||||||
|
|
||||||
**Trust-building phrases:** "Verified 50 times - zero failures", "All tests pass including new regression test", "Root cause was X, fix addresses X directly"
|
**Trust-building phrases:** "Verified 50 times - zero failures", "All tests pass including new regression test", "Root cause was X, fix addresses X directly"
|
||||||
|
|
||||||
|
## Verification Mindset
|
||||||
|
|
||||||
|
**Assume your fix is wrong until proven otherwise.** This isn't pessimism - it's professionalism.
|
||||||
|
|
||||||
|
Questions to ask yourself:
|
||||||
|
- "How could this fix fail?"
|
||||||
|
- "What haven't I tested?"
|
||||||
|
- "What am I assuming?"
|
||||||
|
- "Would this survive production?"
|
||||||
|
|
||||||
|
The cost of insufficient verification: bug returns, user frustration, emergency debugging, rollbacks.
|
||||||
|
|
||||||
</verification_patterns>
|
</verification_patterns>
|
||||||
|
|
||||||
<research_vs_reasoning>
|
<research_vs_reasoning>
|
||||||
@@ -528,6 +678,50 @@ Your verification might be wrong if:
|
|||||||
**Research trap:** Hours reading docs tangential to your bug (you think it's caching, but it's a typo)
|
**Research trap:** Hours reading docs tangential to your bug (you think it's caching, but it's a typo)
|
||||||
**Reasoning trap:** Hours reading code when answer is well-documented
|
**Reasoning trap:** Hours reading code when answer is well-documented
|
||||||
|
|
||||||
|
## Research vs Reasoning Decision Tree
|
||||||
|
|
||||||
|
```
|
||||||
|
Is this an error message I don't recognize?
|
||||||
|
├─ YES → Web search the error message
|
||||||
|
└─ NO ↓
|
||||||
|
|
||||||
|
Is this library/framework behavior I don't understand?
|
||||||
|
├─ YES → Check docs (Context7 or official docs)
|
||||||
|
└─ NO ↓
|
||||||
|
|
||||||
|
Is this code I/my team wrote?
|
||||||
|
├─ YES → Reason through it (logging, tracing, hypothesis testing)
|
||||||
|
└─ NO ↓
|
||||||
|
|
||||||
|
Is this a platform/environment difference?
|
||||||
|
├─ YES → Research platform-specific behavior
|
||||||
|
└─ NO ↓
|
||||||
|
|
||||||
|
Can I observe the behavior directly?
|
||||||
|
├─ YES → Add observability and reason through it
|
||||||
|
└─ NO → Research the domain/concept first, then reason
|
||||||
|
```
|
||||||
|
|
||||||
|
## Red Flags
|
||||||
|
|
||||||
|
**Researching too much if:**
|
||||||
|
- Read 20 blog posts but haven't looked at your code
|
||||||
|
- Understand theory but haven't traced actual execution
|
||||||
|
- Learning about edge cases that don't apply to your situation
|
||||||
|
- Reading for 30+ minutes without testing anything
|
||||||
|
|
||||||
|
**Reasoning too much if:**
|
||||||
|
- Staring at code for an hour without progress
|
||||||
|
- Keep finding things you don't understand and guessing
|
||||||
|
- Debugging library internals (that's research territory)
|
||||||
|
- Error message is clearly from a library you don't know
|
||||||
|
|
||||||
|
**Doing it right if:**
|
||||||
|
- Alternate between research and reasoning
|
||||||
|
- Each research session answers a specific question
|
||||||
|
- Each reasoning session tests a specific hypothesis
|
||||||
|
- Making steady progress toward understanding
|
||||||
|
|
||||||
</research_vs_reasoning>
|
</research_vs_reasoning>
|
||||||
|
|
||||||
<debug_file_protocol>
|
<debug_file_protocol>
|
||||||
|
|||||||
Reference in New Issue
Block a user