I’ve done my fair share of grumbling about vibe coders. I said so, in print, in the first version of this article, but I also said the gate was open and you should come in. In the months since that article, I’ve watched a number of you growing as developers, taking on projects that would have made you sweat in January, and shipping things that actually work. So this one is a little more technical than the last one, because you’ve earned the harder version.
Maybe someday LLMs will magically generate flawless enterprise software on the first prompt. Today is not that day.
If you read the original Vibe Coder’s Assistant, you’ll remember the core of it: the most important part of building software isn’t the code, it’s learning to think like an engineer. Thinking in 3S architecture; sustainability, scalability, security. Knowing the shape of the work, building through debugging through deploying, keeping version control so you can crawl back out of a hole you dug. Since then, two things have happened in parallel. You got better. And Claude got better (yes, yes, and the other LLMs too, settle down). The automated coding terminal and the swarm of agents mean you can now break big things even faster than before.
There’s good news and bad news in this. The good news is that you’re more capable than ever of deploying fully fleshed out applications, with all the complexity that entails. The bad news is that you’re more capable than ever of deploying fully fleshed out applications, with all the complexity that entails. The more robust the application, the more surface area it has for bugs and pitfalls to hide in. Larger codebase, more moving parts, real deployment, real users. And every one of those pitfalls is one you’re walking toward without the experience and architectural reflexes an engineer spent a decade acquiring on the job.
Here’s what that decade actually buys. The single greatest difference between a senior engineer and a junior developer is NOT their grasp of the language or the syntax. It’s knowing which questions to ask, and when; before the build, during the build, after the build, to make sure the thing conforms to 3S. The junior writes code and hopes. The senior interrogates the plan before a line is written, because they’ve already seen how this particular movie ends.
This came up in my Discord this week. A newer dev asked, “how do you run audits?” Great question, and great precisely because of what it reveals: they’ve sensed that “audit” is a lever senior people know how to pull, and they don’t yet. That instinct, knowing there’s a question you can’t quite ask yet, is the whole game. You can’t download ten years of scar tissue. But you can learn the questions, and let the machine supply the rigor the experience would have triggered.
So we’re going deeper, into architecture and audits; the engineering concepts you should be building in from the start, and how to raise them conversationally. At the end, there’s an updated Skill file to carry these into your work.
Let’s start by talking about two principles you probably haven’t heard about unless you’ve been digging into engineering tutorials: DRY and SSOT.
DRY governs the functional side; the code itself. The word you’ll hear engineers use constantly around it is abstraction, which sounds loftier than it is. Abstraction just means thinking one level higher than the thing in front of you.
Take database operations. A database needs CRUD: create, read, update, delete. The most obvious implementation is four separate functions, one for each:
create(data)
read(data)
update(data)
delete(data)Looks clean, but isn’t. Every one of those functions needs to open a connection to the database before it can do anything, which means you’ve written the same connection logic four times. Now imagine the connection details change. You’re editing it in four places and praying you didn’t miss one (and I have written a ton of code in my career that looked exactly like this).
The abstraction is to write the connection ONCE and pass the operation into it:
// One door into the database; the operation rides in.
function connectDB(operation, data) {
// open the connection
switch (operation) {
case 'create': // ...
case 'read': // ...
case 'update': // ...
case 'delete': // ...
}
// close the connection
}
Four functions collapse into one function with a switch. Less code, one place to change the connection, and, this is the part that matters: one place to debug when something breaks.
That last part is the whole reason to care. The more complex your codebase, the harder it is to debug, and complexity compounds. When you have dozens of little functions each doing a small thing, you’ve created dozens of places for a logic bug to hide, and worse, you’ve created a web of interactions between them where the bug isn’t in any single function but in the chain. This is the failure mode that eats vibe coders alive: the LLM examines each function in isolation, pronounces each one correct, and never sees that the error lives in the handoff between function three and function seven.
In the first article I preached full code path debugging; making the agent trace the entire path connected to an error instead of patching the first symptom it sees. DRY makes that path shorter. Every duplication you abstract away is a stretch of path the agent doesn’t have to walk. You’re not just writing less code; you’re pre-shrinking the haystack before you ever have to look for the needle.
So bake it in from the start. Before you build, say something like:
“Incorporate a DRY approach to this app’s architecture. Abstract functions into logical groupings to reduce the codebase size wherever it makes sense.”
Now, yes, I can hear the pedants cracking their knuckles; technically DRY is about every piece of knowledge having a single authoritative representation, not merely “don’t write the same lines twice,” and technically you can over-abstract yourself into a single godlike function with nine callers and a forest of conditionals that’s far harder to trace than the four honest functions you started with. Both objections are correct. Note the phrase “wherever it makes sense” doing quiet work in that prompt. Abstraction has a sweet spot, and finding it is itself one of those senior-dev judgment calls you’re here to develop. Don’t DRY until it’s bone dry. Aim for the spot where the code stops repeating itself but can still explain itself.
SSOT is DRY for the data layer, abstracting data use the same way we abstract functions.
It’s extremely common for several tables in a database to need the same piece of information. Say you’re building an accounting app, and daily gross revenue needs to appear in both a grossTotalRevenue table and a revenueSalesTax table, because both do calculations on it. The naive approach stores the number in both places.
And then, one day, for reasons (and believe me, there will be reasons), those two values disagree. Now you have a reconciliation error with no obvious origin, because as far as you knew, your app recorded total revenue once. So why are two tables looking at the same revenue and reporting different numbers? Welcome to one of the most maddening afternoons in software.
The fix is normalization, which is DRY for database design. Instead of storing the gross revenue value in every table that needs it, you store the value once, in its own table, and everywhere else stores a reference to it:
-- The value lives in exactly one place.
grossRevenue (revenueID, amount, date)
-- Everyone else points at it.
revenueSalesTax (taxID, revenueID, rate)
grossTotalRevenue (totalID, revenueID, period)
Every table that depends on gross revenue pulls from the same source of truth, so there’s no second copy to drift out of sync. The number is either right or wrong in one place, and one place is a problem you can actually solve.
A well-normalized dataset is usually described as being in 3NF, third normal form. You don’t need to learn the levels of normalization. You only need to know they exist, and that someone smarter than both of us worked them out so you wouldn’t have to. (It doesn’t help to read about it. It’s just words and words and words). During planning, raise it directly:
“This app should follow SSOT principles. In the database design, make sure no value is duplicated across tables, and the data model should conform to at least 3NF.”
Here’s the answer to the Discord question. An audit is just the senior dev’s habit of going back and checking the work against the principles, except you’re asking the machine to do the checking against principles you now know to name. And specificity matters a lot.
And the fact is, even with good architectural intentions at the beginning, drift still happens. You start clean, you add a feature at 11 PM, the agent takes a shortcut, and three versions later you’ve got a duplicated value in two tables and a function that got copy-pasted instead of abstracted. Nobody decided to violate DRY and SSOT. It happened the way entropy always happens: quietly, while you were busy shipping.
So you audit. First when your primary development is done, and then again after every major version bump:
“Audit the codebase for the following: DRY and SSOT violations, and any visible security flaws that may allow for unsanitized inputs, escalation of privileges, or other unexpected behavior.”
That single prompt is doing three jobs: catching functional drift, catching data-layer drift, and running the security pass that, in the first article, I begged you to think about before your app got popular and famous for all the wrong reasons. It’s not the whole of security; nothing in one prompt is. But it’s the lever the junior dev didn’t know was there, and now you do.
I know how this kind of article gets used. The prompts get screenshotted, saved to a notes file, and pasted verbatim into every project forever, like incantations. So let me be unambiguous: these are NOT magic prompts. They’re not spells. Saving them and chanting them at your codebase will not protect you.
They are examples of how to shape a conversation; how an engineer thinks about architecture, translated into language you can actually use with your Claude. The value was never in the exact words. It’s in understanding why you’re asking, so that six months from now you’re writing audits I never showed you, for failure modes I never named, because you finally see where the code rots. No set of prompts replaces you thoughtfully engaging with the work. That was the whole point in version one, and it’s the whole point here.
Knowing these high level principles front to back is what makes a CTO, not the ability to wrangle code directly. And when you’re using an LLM to build a software product for yourself, that’s the role you’re playing. Play it well.
The updated Vibe Coder’s Assistant v2 is linked below. It’s expanded, enhanced, and now configured to work across Claude Chat, Claude Code, and Cowork, so it follows you wherever you’re building.
The Vibe Coder’s Assistant, v2 (Zip file, download to desktop and install in Claude Skills directory via the Customize screen). And if you’re looking for a helpful community to share your work and get help or feedback, come join ours.
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.