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.
|
||||
|
||||
```
|
||||
Problem: Form submission fails intermittently
|
||||
```javascript
|
||||
// Problem: Form submission fails intermittently
|
||||
// Competing hypotheses: network timeout, validation, race condition, rate limiting
|
||||
|
||||
Competing hypotheses:
|
||||
1. Network timeout
|
||||
2. Validation failure
|
||||
3. Race condition with auto-save
|
||||
4. Server-side rate limiting
|
||||
try {
|
||||
console.log('[1] Starting validation');
|
||||
const validation = await validate(formData);
|
||||
console.log('[1] Validation passed:', validation);
|
||||
|
||||
Design experiment that differentiates:
|
||||
- Add logging at each stage (validation → submission → response → UI update)
|
||||
- Observe which stage fails
|
||||
- One experiment, differentiates four hypotheses
|
||||
console.log('[2] Starting submission');
|
||||
const response = await api.submit(formData);
|
||||
console.log('[2] Response received:', response.status);
|
||||
|
||||
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>
|
||||
|
||||
<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
|
||||
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
|
||||
|
||||
**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
|
||||
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
|
||||
|
||||
**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.
|
||||
|
||||
**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
|
||||
|
||||
**When:** Always. Before making any fix.
|
||||
@@ -313,6 +374,15 @@ console.log('[updateUser] Called from:', new Error().stack);
|
||||
4. After each uncomment, test
|
||||
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
|
||||
|
||||
**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 |
|
||||
| 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>
|
||||
|
||||
<verification_patterns>
|
||||
@@ -408,6 +489,63 @@ done
|
||||
|
||||
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
|
||||
|
||||
```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"
|
||||
|
||||
## 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>
|
||||
|
||||
<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)
|
||||
**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>
|
||||
|
||||
<debug_file_protocol>
|
||||
|
||||
Reference in New Issue
Block a user