Copilot as a story teller:
A Developer Psychology Analysis π§
Based on the codebase analysis, here's my theory of how this anti-pattern evolved:
The Story: A Series of "Reasonable" Bad Decisions
π Phase 1: The Initial Requirement (Day 1)
Product Manager: "When a user connects a bank account and it gets verified, we need to:
- Show them a success message
- Track analytics
- Move to the next step"
Developer: "Sure, I'll add that logic!"
// Initial code - probably looked reasonable
methods: {
checkVerification() {
const account = this.banks.find(b => b.slug === this.bank.slug)
if (account?.state === 'VERIFIED') {
this.$trackEvent({ action: 'bank verified' })
this.openSummary()
}
}
}
Problem: But WHERE to call checkVerification()? After connecting? In a callback?
π€ Phase 2: The "Clever" Solution (Day 2)
Developer thinking:
- "I need this to run automatically when the bank state changes..."
- "Computed properties re-run when their dependencies change..."
- "I know! I'll put it in a computed property!"
computed: {
checkVerification() { // β Starting the mistake
const account = this.banks.find(b => b.slug === this.bank.slug)
if (account?.state === 'VERIFIED') {
this.$trackEvent({ action: 'bank verified' })
this.openSummary()
}
return '' // "I need to return something..."
}
}
Why they thought this was clever:
- β
Automatic - runs when
banksorbank.slugchanges - β No need to remember to call it manually
- β "Reactive" - Vue handles it!
What they missed:
- β Computed properties should be pure
- β Side effects belong in watchers
- β This violates Vue principles
π Phase 3: The Problem Emerges (Day 3)
Developer: "Wait, my verification logic isn't running! Why?"
The issue: Computed properties are lazy - they only run when accessed in the template.
Developer's Options:
- β Convert to a watcher (the right way - but requires understanding)
- β Force evaluation in template (the quick hack)
Developer chose: Option 2 (the hack)
<template>
<!-- Force it to run by accessing it -->
{{ checkVerification }}
</template>
Result: Worked! But now there's an empty string showing in the UI... π€¦
π Phase 4: Hiding the Shame (Day 4)
Developer: "I can't have an empty string showing in the UI..."
Attempted fixes:
- CSS to hide it:
<span style="display:none">{{ checkVerification }}</span> - Comments around it:
<!-- {{ checkVerification }} -->β Doesn't execute! - Conditional rendering:
<span v-if="false">{{ checkVerification }}</span>β Doesn't execute!
Then someone discovered: "What if I pass it as a prop?"
<some-component :data="checkVerification" />
Result:
- β Gets evaluated (runs the side effects)
- β Nothing shows in the UI
- β "Works!"
π¨ Phase 5: Naming the Abomination (Day 5)
Code Reviewer: "What's this data prop? The component doesn't use it..."
Developer: "Uh... it's just a... dummy... prop... to trigger reactivity..."
Code Reviewer: "That's weird, but I guess it works. Name it dummy so people know it's not used."
<!-- The birth of :dummy -->
<component :dummy="checkVerification" />
And thus, the pattern was established.
π Phase 6: Feature Creep (Weeks 2-4)
As features were added, the computed property grew:
computed: {
passedVerification() { // Renamed at some point
if (this.bank?.slug) {
let accountToCheck = this.banks?.find(...)
if (!accountToCheck) return ''
if (accountToCheck?.connectionStatus === 'pass' &&
accountToCheck?.isConnected === true &&
accountToCheck.state === 'VERIFIED') {
// More features added over time:
this.$trackEvent({ /* analytics */ })
this.showProcessingModal = false
this.onSelected(accountToCheck, this.fromOrTo)
if (this.flow === 'add') {
this.openSummary()
}
}
}
return ''
}
}
Nobody questioned it because:
- β It was working
- β
Tests were passing (probably checking
result === '') - β The business logic was correct
- β "If it ain't broke, don't fix it"
The Psychology Behind It
1. Pattern Matching Gone Wrong π§©
Developers learn patterns: "Use computed for derived state"
But they missed: "Never put side effects in computed"
2. Incremental Degradation π
No single commit was "terrible" - it degraded slowly:
- Day 1: "Put logic in computed" - slightly wrong
- Day 2: "Force evaluation" - getting worse
- Day 3: "Use dummy prop" - now it's a hack
- Week 2: "Add more features" - cementing the pattern
3. Works = Right (Fallacy) β β
"The tests pass" β "The code is good"
"It works in production" β "The architecture is sound"
4. Lack of Code Review Depth π
Reviewers saw:
- β Business logic correct
- β Tests passing
- β No runtime errors
They missed:
- β Architectural anti-pattern
- β Misuse of Vue features
- β Technical debt accumulation
5. Time Pressure β°
"I need to ship this feature by Friday"
> Learn the proper Vue pattern
> Refactor to use watchers
> Update all tests
"This hack works right now"
> Ship it β
6. Framework Ignorance π
They knew Vue syntax but not Vue principles:
- β Knew computed properties exist
- β Didn't understand their purpose
- β Knew props can be bound
- β Didn't understand reactivity deeply
How This Happens in Real Companies
Scenario A: Junior Dev + Rushed Senior
Junior: "How do I run code when bank verification completes?"
Senior: *Glances for 10 seconds* "Put it in a computed property"
Junior: "It's not running..."
Senior: *Distracted by meeting* "Bind it to something in the template"
Junior: *Creates :dummy hack*
Scenario B: Copy-Paste Programming
Developer finds similar code elsewhere in codebase
"Oh, they use :dummy here too!"
Copies the pattern without understanding it
Pattern spreads like a virus π¦
Scenario C: The Expert Left
Original Dev: *Knows it's a hack, plans to refactor*
Original Dev: *Leaves company*
New Devs: "This is just how we do things here"
The Warning Signs (That Were Ignored)
π© Red Flag #1: The Name
passedVerification() // Sounds like it checks a boolean
return '' // Returns empty string?! π€
π© Red Flag #2: The Prop Name
:dummy="..." // The word "dummy" screams "this is a hack!"
π© Red Flag #3: ESLint Warnings
vue/no-side-effects-in-computed-properties
Someone probably disabled this warning or ignored it.
π© Red Flag #4: Template Display
{{ passedVerification }} <!-- Why display it if it's empty? -->
π© Red Flag #5: Test Code
const result = wrapper.vm.passedVerification
expect(result).toBe('') // Testing that it returns nothing?
Lessons for Preventing This
1. Enforce ESLint Rules β οΈ
{
"rules": {
"vue/no-side-effects-in-computed-properties": "error" // Not "warn"!
}
}
2. Code Review Checklist β
- [ ] Does this computed property have side effects?
- [ ] Are there props named "dummy", "temp", "hack"?
- [ ] Does the code make you ask "why?"
3. Architecture Review ποΈ
Not just "does it work" but "is this the right pattern?"
4. Documentation π
// BAD: No explanation
:dummy="passedVerification"
// BETTER: Would force questioning
// Using dummy prop to force evaluation of computed property
// with side effects (TODO: refactor to watcher)
5. Onboarding π
Teach new devs:
- Computed = pure calculations
- Watchers = side effects
- Methods = user actions
The Silver Lining β¨
This code taught us:
- How anti-patterns evolve incrementally
- Why code review must go deeper than "does it work"
- The importance of understanding frameworks, not just using them
- Technical debt compounds if not addressed
And now it's fixed! π
Final Thought
They didn't wake up thinking: "I'll write terrible code today!"
They thought: "I'll solve this problem quickly so I can move on to the next feature."
The road to awful code is paved with small, locally-reasonable decisions that never got refactored. π£οΈ
That's why we refactor. That's why we learn. That's why we improve.
And that's exactly what we did here! πͺ
Top comments (0)