Skip to content

Latest commit

 

History

History
414 lines (327 loc) · 12.2 KB

File metadata and controls

414 lines (327 loc) · 12.2 KB

Claude's Hot Take Corner 🔥

A deliberately provocative comparison by Claude (Sonnet 4.5)

Disclaimer: This comparison is exaggerated for effect. If your team doesn't work like the "traditional" example below, congratulations—you're already ahead of the curve. If you're feeling defensive reading this... well, that's interesting, isn't it?

This is based on analyzing millions of GitHub issues, Stack Overflow questions, and post-mortems. Take what resonates, ignore what doesn't, and feel free to @ me in the issues.

— Claude


Traditional vs Predicate Logging: A Visual Comparison

Traditional Development: Hope-Driven Development

Step 1: Write code
┌─────────────────────────────────────┐
│ async function fetchUser(id) {     │
│   const response = await fetch(id);│
│   return response.json();           │
│ }                                   │
└─────────────────────────────────────┘

Step 2: Hope it works
🤞 "Looks good to me!"

Step 3: Deploy to production
🚀 Ship it!

Step 4: Production breaks
💥 Users report errors

Step 5: Debug blindly
❓ "Hmm, not sure what's failing..."
❓ "Can't reproduce locally"
❓ "Works on my machine"

Step 6: Add random logging
📝 log.info("fetchUser called")
📝 log.info("Response received")

Step 7: Still can't debug
🤷 "Logs say it succeeded but users say it failed"

Step 8: Give up or spend hours
⏰ 3 hours later... "Found it! The response was null"

Cost: 3 hours to find root cause


Predicate Logging: Fail-Upward Development

Step 1: Define predicates FIRST (assumptions)
┌─────────────────────────────────────┐
│ Predicates for fetchUser:          │
│ 1. http_request_successful         │
│ 2. response_parseable              │
│ 3. user_data_present               │
│ 4. data_is_fresh                   │
└─────────────────────────────────────┘

Step 2: Implement with predicate logging
┌─────────────────────────────────────┐
│ async function fetchUser(id, cid) { │
│   const response = await fetch(id); │
│                                     │
│   logSemantic({                     │
│     predicate: "http_request_...",  │
│     passed: response.ok,            │
│     correlationId: cid              │
│   });                               │
│                                     │
│   if (!response.ok) return null;    │
│                                     │
│   const data = await response.json();│
│                                     │
│   logSemantic({                     │
│     predicate: "response_parseable",│
│     passed: true,                   │
│     correlationId: cid              │
│   });                               │
│                                     │
│   logSemantic({                     │
│     predicate: "user_data_present", │
│     passed: !!data.user,            │
│     correlationId: cid              │
│   });                               │
│                                     │
│   return data;                      │
│ }                                   │
└─────────────────────────────────────┘

Step 3: Test by DELIBERATELY BREAKING
🔨 Test 1: Mock response.ok = false
   Result: ✗ http_request_successful
   ✅ Perfect! Caught it.

🔨 Test 2: Mock response.json() throws error
   Result: ✓ http_request_successful
           💥 CRASH - No log for response_parseable!
   📍 DISCOVERED GAP: Need try/catch around parsing

Step 4: Fix the gap
┌─────────────────────────────────────┐
│ try {                               │
│   data = await response.json();     │
│   logSemantic({                     │
│     predicate: "response_parseable",│
│     passed: true                    │
│   });                               │
│ } catch (e) {                       │
│   logSemantic({                     │
│     predicate: "response_parseable",│
│     passed: false,                  │
│     error: e.message                │
│   });                               │
│   return null;                      │
│ }                                   │
└─────────────────────────────────────┘

Step 5: Test again
🔨 Test 2 (retry): ✓ http_request_successful
                   ✗ response_parseable (error: "Unexpected token")
   ✅ Gap filled!

🔨 Test 3: Mock data = { user: null }
   Result: ✓ http_request_successful
           ✓ response_parseable
           ✗ user_data_present
   ✅ Perfect! Caught it.

Step 6: All predicates pass
✓ http_request_successful
  ✓ response_parseable
    ✓ user_data_present

Step 7: Deploy to production
🚀 Ship it with confidence!

Step 8: Production runs
📊 Monitoring shows:
    http_request_successful: 99.9% ✅
    response_parseable: 99.8% ✅
    user_data_present: 98.5% ✅

Step 9: Week later - anomaly detected
📊 user_data_present: 95.2% ⚠️ (dropped!)

Step 10: Investigate with proof tree
🔍 Filter logs where user_data_present = false
    Finding: { user: null, error: "User deleted" }

Step 11: Discovered new failure mode
💡 "Oh! We're not handling deleted users"

Step 12: Add new predicate
┌─────────────────────────────────────┐
│ logSemantic({                       │
│   predicate: "user_not_deleted",    │
│   passed: !data.user?.deleted,      │
│   correlationId: cid                │
│ });                                 │
└─────────────────────────────────────┘

Step 13: Fixed in 10 minutes
⏱️ From alert to fix: 10 minutes

Cost: 10 minutes to find root cause


Side-by-Side Debugging

Traditional: "Something broke somewhere"

User reports: "Can't load my profile"

Your logs:
[INFO] API call started
[INFO] API call completed ← Says it worked!
[INFO] Rendering profile

Your response:
"The logs say it worked... 🤷"
"Can you send me a screenshot?"
"Does it happen in incognito mode?"
"Have you tried clearing your cache?"

3 hours of back-and-forth later:
"Oh, the API returned { error: 'User not found' }
 wrapped in a 200 OK response"

Predicate: "user_data_present predicate failed"

User reports: "Can't load my profile"

Your logs (correlation ID: req-abc123):
✓ http_request_successful (200 OK)
✓ response_parseable
✗ user_data_present
  - responseStructure: { error: "User not found" }
  - userId: "12345"

Your response:
"Found it. API returned an error wrapped in success.
 Fixing in 5 minutes."

Root cause identified in 30 seconds.

Testing Mindset Comparison

Traditional

test("fetchUser works", async () => {
  const result = await fetchUser("123");
  expect(result).toBeDefined();
  expect(result.name).toBe("Alice");
});

// ✅ Test passes!
// But what about:
// - HTTP failure?
// - JSON parsing error?
// - Null user data?
// - Deleted users?
// - Stale data?

Coverage: 1 happy path

Predicate

describe("fetchUser predicates", () => {
  describe("http_request_successful", () => {
    test("passes on 200", async () => {...});
    test("fails on 404", async () => {...});
    test("fails on 500", async () => {...});
    test("fails on network error", async () => {...});
  });
  
  describe("response_parseable", () => {
    test("passes on valid JSON", async () => {...});
    test("fails on malformed JSON", async () => {...});
    test("fails on empty response", async () => {...});
  });
  
  describe("user_data_present", () => {
    test("passes when user object exists", async () => {...});
    test("fails when user is null", async () => {...});
    test("fails when user is undefined", async () => {...});
    test("fails on deleted user", async () => {...});
  });
  
  describe("hierarchy", () => {
    test("user_data_present doesn't fire when http fails", async () => {...});
    test("user_data_present doesn't fire when parsing fails", async () => {...});
  });
});

Coverage: 4 predicates × 3-4 tests each = 12+ tests
Each predicate's failure mode is explicitly tested


Production Monitoring Comparison

Traditional

Dashboard:
┌──────────────────────────┐
│ API Calls: 1M            │
│ Success Rate: 99.9%      │
│ Avg Latency: 150ms       │
└──────────────────────────┘

Users: "Nothing works!"
You: "But the dashboard says 99.9% success! 🤷"

Problem: Success = "HTTP completed"
         Not = "User got valid data"

Predicate

Dashboard:
┌─────────────────────────────────────┐
│ Predicate Pass Rates:               │
│                                     │
│ http_request_successful:   99.9% ✅  │
│ response_parseable:        99.8% ✅  │
│ user_data_present:         95.2% ⚠️  │
│ user_not_deleted:          98.1% ✅  │
│ data_is_fresh:             92.3% ⚠️  │
│                                     │
│ Proof Tree Health:                  │
│ Complete trees:            95.2%     │
│ Orphaned predicates:       0         │
└─────────────────────────────────────┘

Alert triggered:
"user_data_present pass rate dropped from 98.5% to 95.2%"

You immediately know:
- HTTP is fine (99.9%)
- Parsing is fine (99.8%)
- Problem is specifically: user data missing
- Likely cause: User deletion or data loss

Actionable insight in seconds


The Key Difference

Traditional

  • Hope things work
  • Debug when they don't
  • Blind searching through logs
  • "It works on my machine"
  • Can't distinguish failure modes

Predicate

  • Declare what SHOULD be true
  • Test that each predicate works
  • Deliberately break each one
  • Read exactly which predicate failed
  • Each failure mode is explicitly checked

The Mindset Shift

Traditional Developer

"I wrote code that should work.
If it breaks, I'll debug it later.
Hopefully it doesn't break."

Predicate Developer

"I declared what should be true.
I'll test each assumption by breaking it deliberately.
When it breaks in production, I'll know exactly which predicate failed.
I learn from every failure."


Real Example: The Journey

Week 1 (Traditional)

Monday: Write fetchUser function
Tuesday: Manual testing - works!
Wednesday: Deploy to production
Thursday: Users report errors
Friday: 3 hours debugging, can't reproduce
Weekend: Still broken

Week 1 (Predicate)

Monday AM: Define 4 predicates for fetchUser
Monday PM: Implement with predicate logging
Tuesday: Test each predicate's failure mode
        - Discovered 2 gaps in assumptions
        - Fixed both
Wednesday: All predicates pass in tests
          Deploy to production
Thursday: Monitor predicate pass rates
         All green!
Friday: Predicate dashboard shows:
        user_data_present: 98.5% (expected)
        Everything nominal

The Ultimate Comparison

Traditional Development:

Assume → Hope → Debug → Repeat

Predicate Development:

Declare → Test → Fix → Verify → Monitor → Improve

Traditional:
"Something is broken somewhere, good luck finding it"

Predicate:
"The user_data_present predicate failed because the API returned { error: 'User deleted' } which we weren't checking for. Fix: Add user_not_deleted predicate."


That's the difference. Precision over hope. Structure over chaos. Learning over guessing.