I have lived through a few waves of tooling changes. If you write software long enough you get comfortable with the ground moving under your feet. The latest shift is vibecoding: pointing a capable model in roughly the right direction and steering it with context, examples, and taste. Tools like Claude Code, Codex CLI, and Gemini Code make that feel effortless. This post is less about what the tools are doing and more about how to use them without losing your engineering brain.
These are the rules I use to keep my head clear and my output clean. They are opinionated, shaped by mistakes I have made, and meant for people who already build things for a living.
- Start with a system, not a snippet
Before I ask an assistant for code, I sketch the system. What are the inputs, outputs, and invariants. Where are the boundaries and failure modes. What gets to be stateful and who owns that state. What can be retried and what must be transactional. I write this down, even if it is only a few bullets in a design note. A model can produce a service or a function. It cannot invent your constraints. If you do the systems thinking first, the generated code has somewhere to land.
- Own the spec
“Generate X” is not a spec. I write acceptance criteria, examples, counter‑examples, and trade‑offs. I call out non‑goals. I list the rough shape of interfaces, the error codes, and the edge cases that bite. If I cannot write a short spec, I do not understand the problem. The tools work best when I give them a shape to fill, not a blank page to guess at.
- AI multiplies skill; it isn’t autopilot
These tools are a fast, eager pair who never tire of boilerplate. They accelerate good habits and amplify bad ones. I still need core skills: reading unfamiliar code, debugging, testing, naming, refactoring, and reasoning about complexity. When the conversation gets stuck in a death loop—answers drifting, hallucinating APIs, or repeating mistakes—I step in: restate constraints in three bullets, show a failing test, narrow the request, or reset the session. Multipliers need direction.
- Keep a strong mental model
Speed makes it easy to stop tracking the program in your head. I fight that by re‑describing the flow in plain language: here is the request, here is where we validate, here is where we branch, here is the one place we mutate state. I keep a small diagram for data shapes and state transitions. If I cannot explain the call graph and the data lifetimes without looking, I am not ready to ship.
- Write the scary parts yourself
There are areas where I ask the assistant for ideas, then I type the final code myself: concurrency, security boundaries, migrations, billing, anything that touches real money or irreversible actions. I might let a model propose a state machine or a locking approach, then I re‑derive it and write it by hand. It keeps my skills sharp and reduces the chance I ship a subtle footgun I do not fully understand.
- Make correctness observable
I aim for fast, boring tests. A couple of unit tests, a property or two where the logic is tricky, and a happy path that exercises the integration. I add lightweight preconditions and postconditions where it helps. Logs tell me what happened; metrics tell me how often; tracing tells me where it stalled. I ask the tool to generate tests, but I read them like I would a stranger’s PR and I delete noisy ones. Correctness that I can observe beats confidence that I cannot justify.
- Budget for latency, limits, and failure
Models have context windows, rate limits, and bad days. External APIs do the same. I design for retries, timeouts, and backoff. I cache what I safely can. I keep a fallback or a degraded mode for the core path if the smart thing is unavailable. I watch the costs. Latency and money are first‑class constraints today; pretending they are not is how you surprise a customer or finance.
- Build tiny, shippable slices (fight context drift)
I try to slice vertical, user‑visible wins. I ask the assistant for a minimal, diff‑only patch I can read in one glance. I wire it, run it, and ship it behind a flag if needed. Even with large context sizes, quality decays over long sessions—context drift and rot are real. My antidotes: reset the thread after a milestone, paste a 10‑line state of the world summary, keep a canonical spec snippet I can re‑inject, and checkpoint with small commits. Short loops compound.
- You ship it, you own it
Before AI, people copied code from StackOverflow and blogs. That never absolved anyone when a bad snippet took down prod. Same rule here: if I push it or merge it, I own the blast radius. I read diffs for hidden complexity, spooky globals, silent catches, odd sleeps, unbounded concurrency, and accidental exposure of secrets. I run the tests. If I merge it, I am accountable for the outcome, not the model.
- Fundamentals matter (more than ever)
The tools make average work faster. They make deep work possible only if I know the basics: data structures, complexity, memory and cache behavior, transactions and isolation, HTTP and timeouts, queuing, eventual consistency, indexes and query plans. When I recognize patterns and constraints, I can ask better questions and I get better output. Fundamentals are the difference between steering and being steered.
- Build guardrails: sandboxes, flags, canaries
I assume new code—especially generated code—will be a little wrong in production. I keep the blast radius small: run in a sandbox first, hide behind feature flags, ship dark, canary it to 1%, add circuit breakers and budgets, and watch it. Guardrails turn scary problems into small problems.
- Document decisions, not just interfaces
I keep short decision records (one file, a few paragraphs) for choices that shape the system: why we picked a queue, why we chose eventual consistency, why we set a timeout to 800 ms. I write “why”, not “what”. I link to issues and PRs. I include assumptions, constraints, and what would make us revisit the choice. Future me thanks present me every single time.
Vibecoding is not a shortcut around understanding. It is a multiplier on clarity. If I do the thinking, the tools give me speed. If I skip the thinking, the tools help me get lost faster. I still enjoy writing code. I also enjoy deleting code I no longer need because a smarter slice made it irrelevant. That mix is the job now.