In which our heroes learn that copy-paste is a symptom, not a solution
Previously on The Hundred Forms…
In V3, The Hundred Forms - A Prophecy Revealed. A hundred forms, each taking 45 seconds instead of 45 minutes. The components were built. The StatusBar stuck.
Then came The Labyrinth of a Hundred Forms. Breadcrumbs that lied. Navigation that multiplied. The Squirrel was sedated with interfaces.
But V3 fought windmills. Client signals accumulated like acorns in a hoarder’s cheeks. The V3 Saga Final Chapter - Is It Fun To Fight Windmills.
Now, in V4, we build anew. HTMX returns. Server is truth. And today, the Squirrel woke up.
The Squirrel was debugging at 2 AM, as Squirrels do.
“The edited indicators don’t show up when I load the form,” she muttered, refreshing the page for the hundredth time. “But when I change ONE field, suddenly all the other changed fields light up too. It’s like they were sleeping!”
The Lizard opened one eye from his warm rock. “Show me the code.”
// Edit handler
cred, _ := h.api.Get(id)
formData := FormData{
Original: cred, // 👈 Points to cred
Current: cred, // 👈 Also points to cred
}
“You’re comparing the credential… to itself,” the Lizard observed. “Original and Current are the same pointer. Of course nothing looks changed.”
The Squirrel’s tail drooped. “But SaveFieldDraft works! It loads the draft separately from HEAD…”
“Show me.”
// SaveFieldDraft - the correct way
var current Credential
h.repo.Load(domain, id, ¤t) // Working copy
var original *Credential
var committed Credential
if err := h.repo.LoadFromHead(domain, id, &committed); err == nil {
original = &committed
} else {
original = &Credential{} // New record
}
isNew, _ := h.repo.IsNew(domain, id)
“Twelve lines,” the Lizard counted. “Repeated how many times?”
The Squirrel went pale. “We’re building a hundred forms.”
“So twelve hundred lines of the same dance. Twelve hundred chances to point both pointers at the same object. Twelve hundred opportunities to forget the IsNew check.”
The Extraction
They stared at the pattern. Load draft. Load HEAD. Check if new. Three operations that always travel together.
“What if,” the Squirrel suggested, “we taught the repository to do all three at once?”
// One call, both versions, isNew for free
var current, original Credential
isNew, err := h.repo.LoadDraftAndOriginal(domain, id, ¤t, &original)
The Lizard nodded slowly. “Five lines instead of twelve. But more importantly…”
“You CAN’T point them at the same object,” the Squirrel finished. “The API makes the bug impossible.”
They added a second helper for SaveFieldDraft’s case, where you already have the draft and just need the original:
var original Credential
isNew, _ := h.repo.LoadOriginal(domain, filename, &original)
Two lines instead of eight.
The Arithmetic
The Squirrel did the math on a napkin:
Edit pattern: 12 → 5 lines = 7 saved
SaveFieldDraft: 8 → 2 lines = 6 saved
Per form: = 13 saved
100 forms: = 1,300 lines
“Thirteen hundred lines,” she said. “But that’s not the real number.”
“No,” the Lizard agreed. “The real number is one hundred.”
“One hundred?”
“One hundred forms where someone won’t accidentally write Original: cred, Current: cred and spend three hours at 2 AM wondering why edited indicators don’t appear.”
The Lesson
The Squirrel pinned a note above her monitor:
When you copy-paste the same pattern three times, you’ve found an abstraction.
When that pattern has a subtle bug, you’ve found a REQUIRED abstraction.
The goal isn’t fewer lines. It’s fewer places to be wrong.
The Lizard returned to his rock. “The best API isn’t the one with the most features. It’s the one that makes mistakes impossible.”
“Like how LoadDraftAndOriginal forces you to provide two separate pointers?”
“Exactly. The shape of the function IS the documentation. Two pointers go in, two different values come out. You can’t mess it up.”
Next time: The Squirrel discovers that “just one more field” is how forms become unmaintainable
See also:
The Hundred Forms Saga:
- The Hundred Forms - A Prophecy Revealed - Where it all began: 45 seconds per form, 95% code savings
- The Labyrinth of a Hundred Forms - Navigation, breadcrumbs, and the domain interface that tamed them
- The Form That Remembered - SSE and the forms that tracked their own changes
The V4 Transition:
- The V3 Saga Final Chapter - Is It Fun To Fight Windmills - Why we moved on
- The Solid Convergence - V4 rises, three projects become one
- The Poor Man’s Signals - Headers as state, server as truth
The Characters:
- The Lizard Brain vs The Caffeinated Squirrel - The eternal struggle between YAGNI and over-engineering
The References:
- Go Interface Compliance -
var _ Interface = (*Type)(nil)pattern - Pit of Success - APIs that make the right thing easy and the wrong thing hard
