Skip to content
Santekno.com | Level Up Your Engineering Skills
EN
📖 0%
13 Jul 2026 · 19 min read ·Article 1 / 208
Go

Vibe Coding vs Specification-Driven Development: Why Senior Engineers Are Making the Switch

Learn the fundamental difference between vibe coding and SDD. Why senior engineers are switching to specification-driven development with Claude Code for their Golang projects.

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

Vibe Coding vs SDD: Why Senior Engineers Are Making the Switch

We’ve all been there. Open the terminal, type claude, then write: “Create a REST API for order management using Golang, PostgreSQL, clean architecture.” Claude immediately generates hundreds of lines of code. Copy-paste, run — it works. It feels like magic.

Two weeks later, when adding a payment gateway feature, things change. The folder structure is slightly different. Naming conventions shift. Patterns are inconsistent with older code. And most painfully: we can’t explain to the AI why the previous code was written that way — because we don’t know either. The AI wrote it, not us.

This is the core problem of vibe coding — and Specification-Driven Development (SDD) is the answer. In this first article of the series, we dissect the difference between vibe coding vs specification driven development thoroughly, from real-world experience with both.


01.1 What Is Vibe Coding and Why It Feels Great at First

The term vibe coding was popularized by Andrej Karpathy in early 2025. The concept is simple: describe what you want to the AI in natural language, the AI executes, you briefly review, continue. The short dialog below captures the entire loop in three lines.

text
1You:  "Create a POST /orders endpoint that accepts JSON, validates it,
2       saves to DB, then sends an event to Kafka"
3AI:   [writes 200 lines of code]
4You:  [copy-paste, run, works, commit]

Notice there is no verification step in that loop — you accept the output because it runs, not because you checked it against anything. This feels revolutionary — and it is for certain use cases. Rapid prototyping, exploring new technology, PoCs for demos? Vibe coding is perfect. The problem emerges when the same approach is used to build systems that live in production for years.

Three reasons vibe coding feels great at first:

Instant feedback. You get working code in seconds. The dopamine hit is real and undeniable.

Dramatically lower barrier to entry. A junior developer can produce code that looks senior-level. No need to memorize every API or pattern.

Exploration without burden. Want to try a new library? Ask the AI. Want to see a pattern implemented? Ask while having coffee.

All of this is real and valuable. The problem is when vibe coding stops being exploration and becomes the primary way of building production systems.


01.2 Where Vibe Coding Starts to Break Down

Vibe coding has systematic failure points — not by accident, but as a logical consequence of how it works.

Ambiguity Accumulates Silently

When you say “create an order management feature,” the AI doesn’t know:

  • Can an order be cancelled after confirmation?
  • Who can cancel — only the user or also admins?
  • What happens to stock when an order is cancelled?
  • Price format: integer cents or float decimal?
  • Is there a cancellation time limit?

The AI will guess. Sometimes the guess is good. Sometimes not. The more complex the system, the more often the guess misses.

Context Evaporates as Sessions Continue

In long sessions, the AI starts “forgetting” architectural decisions made at the beginning. Compare the same handler written early and late in a session — the code below shows how the layer boundary erodes over time.

go
 1// Session 1: AI correctly follows clean architecture
 2func (h *OrderHandler) Create(c echo.Context) error {
 3    return h.useCase.Create(c.Request().Context(), req)
 4}
 5
 6// Session 3: AI starts mixing up layers
 7func (h *OrderHandler) Cancel(c echo.Context) error {
 8    // business logic leaking into handler — code works but architecture is broken
 9    order, _ := h.db.QueryRow("SELECT * FROM orders WHERE id = $1", id)
10    if order.Status != "PENDING" {
11        return c.JSON(400, "cannot cancel")
12    }
13}

The Cancel handler still runs, but the architecture is breaking down silently — a direct DB call now lives where only delegation should. This is the kind of decay you don’t notice until technical debt has already piled up.

No Objective Verification Checkpoint

How do you know the AI-generated code is correct? In vibe coding, the answer is: “it looks right.” There’s no spec to use as a checklist, no acceptance criteria to verify against. Just feeling.


01.3 Operational Definition of SDD

Specification-Driven Development (SDD) is an approach where written specifications exist before code, serving as the single source of truth that explicitly defines system behavior.

There are three mandatory components. The first is the User Story — who needs what and why — captured in a single sentence like the one below.

text
1As a Santekno Shop customer, I want to create an order from items in my cart
2so that the items are ordered and shipped to my address.

That sentence anchors every decision that follows: it names the actor, the action, and the value. Next come the Acceptance Criteria (AC) — the concrete conditions that must be met for the feature to be considered complete.

text
1AC1: System accepts POST /orders with cart_id and shipping_address_id
2AC2: System validates all cart items are still available (stock > 0)
3AC3: System creates order record with PENDING status
4AC4: System atomically decrements product stock
5AC5: System publishes ORDER_CREATED event to Kafka topic "orders"
6AC6: System returns order_id and total_amount_cents

Each AC is verifiable — you can turn every line into a test. Finally, the Edge Cases capture the boundary conditions that are most often missed.

text
1EC1: If any item runs out of stock at checkout, reject the entire order
2EC2: If user has no shipping address, return 422
3EC3: Concurrent checkout for last stock item — only one succeeds

With these three components in place, every implementation decision has a clear basis. The AI no longer needs to guess.


01.4 Direct Comparison: Real Scenario at Santekno Shop

Let’s see the concrete difference with the same scenario in both styles: implementing “add product to cart.”

Vibe Coding Approach

With vibe coding we hand the AI a one-line prompt and let it fill in the unknowns. The list below shows exactly what remains undefined at generation time.

text
1Prompt: "Create a POST /cart/items endpoint to add a product to cart"
2
3[AI generates code without knowing:]
4- Can the same product be added multiple times (qty += 1) or create new entry?
5- What's the maximum items per cart?
6- What happens if the product is inactive?
7- Can non-logged-in users have a cart (guest cart)?
8- Response format after adding — just cart_item_id or entire cart?

The result might work, but its behavior is undefined. Two months later, when there’s a bug where users can add inactive products, nobody knows if that’s a bug or intentional design.

SDD Approach

In SDD we write the spec first at specs/cart/add-to-cart.md. The markdown below turns every one of those open questions into an explicit, testable statement.

markdown
 1### User Story
 2As a customer, I want to add products to my cart
 3so I can purchase them later in one transaction.
 4
 5### Acceptance Criteria
 6- AC1: POST /cart/items accepts { product_id, quantity }
 7- AC2: If product already in cart, increment quantity (qty += input.quantity)
 8- AC3: If product not in cart, create new cart_item
 9- AC4: Maximum 50 unique items per cart — reject with 422 if exceeded
10- AC5: INACTIVE products cannot be added — 422
11- AC6: Stock not decremented here, only at checkout
12- AC7: Response: cart_item { id, product_id, product_name, quantity, price_cents }
13
14### Edge Cases
15- EC1: quantity input <= 0 → 400 Bad Request
16- EC2: quantity input > available stock → 422 "insufficient stock"
17- EC3: product_id doesn't exist → 404
18- EC4: User not logged in → 401 (no guest cart for now)

Now the prompt to Claude Code simply points at that spec and the pattern to follow, as shown below.

text
1Based on the spec at specs/cart/add-to-cart.md, implement POST /cart/items.
2Stack: Echo v4, pgx/v5, Clean Architecture.
3Reference pattern: internal/usecase/product/add_product.go

The result is code that traces back to every AC. There’s no ambiguity left for the AI — or for the next engineer reading it — to resolve.


01.5 Why Senior Engineers Are More Productive with SDD

Counter-intuitive but true: senior engineers who are already experts are more enthusiastic about adopting SDD than juniors new to AI. Why?

They know the cost of ambiguity. Senior engineers have experienced production bugs that happened not because code was wrong, but because assumed behavior differed between teams. Spec eliminates this entire class of bugs.

They think in systems, not functions. SDD forces thinking about how components interact before implementation details — exactly how senior engineers naturally think.

They value reviewability. “Does this code match the spec?” is far more objective than “is this what we want?” SDD makes code review faster and less political.

They’ve already paid technical debt. 10 minutes writing a spec now saves 2 days of debugging later. The math is undeniable.

They know AI needs context. After months using AI coding assistants, experienced engineers know AI output quality depends heavily on the clarity of context provided. Spec is the most efficient way to provide that context.


01.6 Comprehensive Comparison Table

To make the trade-offs concrete, the table below lines up both approaches across the dimensions that matter for a real engineering team.

DimensionVibe CodingSpecification-Driven Development
Starting PointDirect prompt to AIWrite spec first, then prompt
AI ContextDepends on session memoryAlways in spec files
Verification“Looks right”Measurable AC checklist
Edge CasesOften missedExplicitly defined
DebuggingNo “correct behavior” baselineCompare against spec
Team OnboardingRead code, guess intentRead spec, intent immediately clear
RefactoringDangerous without testsSafe — spec is the safety net
Code ReviewSubjective and politicalObjective — vs spec
ScalabilityDifficult in large teamsDesigned for teams
Best forPrototyping, explorationProduction systems
Learning curveLowMedium (worth it)

Read the table top to bottom and a pattern emerges: vibe coding optimizes for the first five minutes, while SDD optimizes for every week after that. That single trade-off explains most of the migration senior engineers are making.


01.7 When Vibe Coding Still Makes Sense

SDD isn’t the right solution for every situation. There are contexts where vibe coding is still the better fit:

Prototyping in 2-4 hours. When you just want to validate whether a technical idea is feasible, the spec can wait.

One-time scripts. One-time data migrations, internal utilities with a one-day lifespan — no formal spec needed.

Exploring new libraries. Vibe coding to learn, SDD to build.

Solo projects that won’t go to production. Personal experimental side projects — free to choose any approach.

The practical rule: If the code will be touched by more than one person, or will run in production for more than one month, use SDD.


01.8 Map of This Series: 20 Articles We’ll Cover

Topic 1 consists of 20 articles in four parts:

  • Part 1 — Mindset Shift (Articles 01–04): Why SDD, spec as source of truth, 2025 tools landscape, CLAUDE.md setup.
  • Part 2 — Writing Effective Specs (Articles 05–09): Spec anatomy, prompt engineering, clarification techniques, OpenAPI contract-first, non-functional specs.
  • Part 3 — From Spec to Go Code (Articles 10–15): Plan Mode, task breakdown, iterative code generation, TDD from spec, code review, safe refactoring.
  • Part 4 — SDD at Team Scale (Articles 16–20): Microservice contracts, spec drift, SDD in teams, end-to-end case study, bridge to next topic.

The project we build throughout the series is Santekno Shop — an e-commerce platform using Go 1.22+, pgx/v5, Echo v4, and Kafka.


01.9 The Mindset Shift Required

Adopting SDD isn’t just a workflow change — it’s a change in how you think about what it means to “start” a feature.

Before SDD: “A feature starts when the first line of code is written.” After SDD: “A feature starts when the first spec is clarified.”

The two timelines below make the difference visible: where the time actually goes in each approach.

text
1Vibe Coding Timeline:
2[Prompt] → [Code] → [Test] → [Bug] → [Fix] → [More Bugs] → [Prod]
3   5m        5m       30m      2h      4h        2h           ???
4
5SDD Timeline:
6[Spec] → [Review] → [Plan] → [Code] → [Test vs Spec] → [Prod] ✓
7  20m      15m       10m      30m          30m

Notice SDD carries more upfront investment but eliminates the repeated bug cycle — the tail of question marks in the first timeline is exactly what the spec removes. The net result is faster time to production with higher quality.

The second key shift is just as important: AI is an executor, not a decision-maker. In vibe coding, AI decides how features work. In SDD, you decide (via spec), AI executes. This difference is fundamental — and determines who is actually “in control.”


01.10 Starting SDD Without Overhauling Your Entire Workflow

You don’t need a grand transformation to start. There’s one simple habit: before writing any prompt to the AI, write these three things:

  1. One user story sentence (who, what, why)
  2. Minimum three acceptance criteria (AC1, AC2, AC3)
  3. Minimum one edge case

That’s it — even without special tools, just text in a notepad, 10 minutes before coding. The minimal spec below is all it takes to make the AI’s output dramatically more accurate.

markdown
 1# Feature: Update user profile
 2
 3User story: As a registered user, I want to update my name and profile photo
 4so that my account information stays current.
 5
 6AC1: PUT /users/me accepts { name?, avatar_url? } — both optional
 7AC2: name cannot be empty if provided (min 2, max 100 chars)
 8AC3: After update, return the latest profile with all fields
 9
10EC1: avatar_url must be a valid URL — 422 with clear message if not

Minimal, but enough to remove all guessing about validation rules and response format. This one habit is the smallest possible on-ramp to SDD.


01.11 Santekno Shop: The Project We’ll Build Together

Throughout these 20 articles, we build Santekno Shop, a simple e-commerce platform with a microservice architecture. The stack summary below is the toolset we’ll assume in every code sample.

text
1Language:   Go 1.22+
2Framework:  Echo v4
3Database:   PostgreSQL (pgx/v5), Redis
4Messaging:  Apache Kafka
5Auth:       JWT (golang-jwt/jwt/v5)
6Testing:    testify/suite, gomock
7ID:         github.com/google/uuid

Keep that stack in mind — it’s the shared vocabulary for the whole series. The two core domain models below are the entities every later feature builds on.

go
 1// product domain
 2type Product struct {
 3    ID         uuid.UUID
 4    Name       string
 5    SKU        string
 6    PriceCents int64
 7    Stock      int
 8    CategoryID uuid.UUID
 9    Status     ProductStatus
10    CreatedBy  uuid.UUID
11    CreatedAt  time.Time
12    UpdatedAt  time.Time
13}
14
15// order domain
16type Order struct {
17    ID               uuid.UUID
18    UserID           uuid.UUID
19    Status           OrderStatus
20    TotalAmountCents int64
21    Items            []OrderItem
22    CreatedAt        time.Time
23    UpdatedAt        time.Time
24}

Notice PriceCents and TotalAmountCents are int64, not floats — a deliberate money-handling decision that a good spec makes explicit. Every feature we build in the series will come from a clear specification, exactly as you’d do in a real project.


01.12 SDD and Speed: Myth vs Reality

The most common objection is: “Writing spec first is slow, vibe coding is faster.” Let’s measure it honestly with the “cancel order” feature. The breakdown below tallies the real cost of the vibe-coding path.

text
1Vibe Coding for "cancel order":
2Prompt + AI generate:          5  min
3Revision for wrong behavior:  30  min
4Bug found by QA:               2  hr debug
5Production edge case bug:      4  hr + rollback + hotfix
6Total:                        ~7  hours

Almost all of that time is spent after the code “worked” — in rework and firefighting. Now compare the SDD path for the same feature.

text
1SDD for "cancel order":
2Write spec + review:          20  min
3Prompt to AI with spec:        5  min
4Review code vs spec:          15  min
5Test from spec:               20  min
6Total:                        ~1  hour

These aren’t theoretical numbers — they reflect real experience from teams that adopted SDD. The upfront investment in the spec always costs less than the downstream bug remediation it prevents.


01.13 SDD in High-Traffic System Context

At an Indonesian e-commerce company with millions of daily transactions, SDD isn’t a luxury — it’s a necessity. Consider what happens without a spec in a high-traffic system:

  1. Checkout deployed without a spec about concurrent order handling
  2. Flash sale: 10,000 concurrent requests in 10 seconds
  3. Two requests “buy” the last stock item due to missing pessimistic locking
  4. Result: oversell, manual refunds, damaged reputation, emergency rollback

With SDD, the concurrent-checkout edge case must be in the spec before coding. The spec fragment below pins down exactly how the race is resolved.

markdown
1- EC3: Concurrent checkout for last stock item
2  → Use SELECT ... FOR UPDATE inside transaction
3  → Behavior: only one request succeeds, others get 409 Conflict
4  → Error message: "product {name} is now out of stock"

Once this edge case lives in the spec, the developer knows exactly what to implement — no assumption that “it’ll handle itself.” That single line is the difference between a controlled 409 and a production oversell incident.


01.14 Integrating SDD with Existing Workflows

SDD doesn’t need to replace your entire workflow. You can integrate it incrementally:

  • Sprint Planning: Add “spec review” to the Definition of Done — tickets can’t enter a sprint without a reviewed spec.
  • Code Review: Add spec compliance to the PR template.
  • Standup: Report progress in terms of ACs implemented, not just “working on X.”
  • Retrospective: Track how many bugs came from “unclear spec” versus “wrong implementation.”

These changes are incremental and non-disruptive. Within 2-3 sprints, the team starts feeling the difference.


01.15 SDD’s Relationship with TDD and BDD

SDD is complementary to both TDD and BDD, not a replacement. With SDD + TDD, each AC becomes a test function and each EC becomes a test scenario — so TDD becomes more focused because you know exactly what to test. The test skeletons below are generated directly from the spec’s ACs and ECs.

go
1// From AC2: "if product already in cart, increment quantity"
2func TestAddToCart_ExistingProduct_IncrementQuantity(t *testing.T) { ... }
3
4// From EC1: "quantity input <= 0 → 400 Bad Request"
5func TestAddToCart_ZeroQuantity_Returns400(t *testing.T) { ... }

Notice each test name maps back to a specific line in the spec — the spec effectively becomes your test plan. In this series we use a pragmatic approach: specs as Markdown with clear, explicit ACs. No full BDD frameworks are required, but the concepts mutually reinforce each other.


01.16 Tooling: What You Need to Get Started

The minimum tooling for SDD is almost zero. The directory tree below is genuinely all you need to begin.

text
1├── specs/          ← this folder alone is enough to start
2│   └── domain/
3│       └── cart/
4│           └── add-to-cart.md
5├── CLAUDE.md       ← AI guidance file (Article 04)
6└── [your code]

Everything in that tree already lives in your filesystem and editor. Not required: dedicated spec management tools, Jira plugins, BDD frameworks, or code generators. SDD is an approach, not a tool.


01.17 Common Mistakes to Avoid

Two of the most damaging mistakes are about how you write ACs, and both are easy to spot once you compare a bad line with a good one. The pairs below contrast an over-specified AC with a right-sized one.

text
1Mistake 1 — Spec too detailed about implementation:
2❌ "Use SELECT FOR UPDATE with SERIALIZABLE isolation"
3✅ "Concurrent checkout for last stock — only one succeeds"
4
5Mistake 2 — AC that isn't measurable:
6❌ "System must be fast"
7✅ "Response time < 500ms at p95 under 100 RPS load"

The rule embedded in both examples: describe what must be true, in terms you can verify. The remaining pitfalls are just as common:

Mistake 3: Not updating spec after implementation changes — a stale spec is more dangerous than no spec.

Mistake 4: Writing specs that are too long — target a 5-10 minute read time.

Mistake 5: Skipping spec for “small” features — no feature is too small for ACs.


01.18 Tips & Gotchas

💡 Tip 1: Start from edge cases, not the happy path — that’s where production bugs live.

💡 Tip 2: Use specific numbers in ACs — “< 200ms at p95,” not “fast response.”

💡 Tip 3: One PR, one spec — it makes code review and rollback dramatically easier.

💡 Tip 4: Review specs with non-technical people — if they can’t understand it, it’s too technical.

💡 Tip 5: Make spec review a team ritual, not an afterthought — block 15-30 minutes at sprint start.

⚠️ Gotcha 1: A spec isn’t a communication replacement — it’s the output of discussion, not a substitute for it.

⚠️ Gotcha 2: AI can misinterpret even clear specs — always manually review AI output against the spec for critical ACs.

⚠️ Gotcha 3: Spec debt is as dangerous as technical debt — budget time to maintain it.

⚠️ Gotcha 4: Don’t let the AI write spec content from ambiguous descriptions — you make the business decisions, the AI helps with format.


01.19 SDD for Engineers with a Laravel/PHP Background

Many Santekno readers come from Laravel, and a few analogies make the transition easier. In Laravel, a FormRequest defines what is valid via rules(); in SDD, an AC expresses the same intent but more explicitly. The rule below is a typical Laravel validation line.

php
1// Laravel rules()
2'quantity' => 'required|integer|min:1'

That single validation rule becomes an AC plus its failure behavior, spelled out below.

markdown
1// SDD AC equivalent
2// AC1: quantity must be a positive integer, minimum 1
3// EC1: quantity <= 0 → 400 with message "quantity must be greater than 0"

The SDD version is more verbose, but that verbosity is exactly what removes ambiguity. The same mapping applies to authorization — a Laravel Policy::authorize() becomes an explicit AC, as shown next.

markdown
1// SDD equivalent of Policy::authorize()
2AC: Only users with ADMIN or PRODUCT_MANAGER role can change product status.
3    Other roles → 403 Forbidden.

The key difference: Laravel has strong conventions that often replace explicit specs, while Go is far more minimal on conventions — which makes explicit specs more important for team consistency. Transition advice: for old Laravel projects being ported to Go, make the existing behavior your starting spec — “How does Laravel currently handle this?” becomes the first AC.


01.20 Summary

In this opening article, we’ve built the foundation for understanding SDD:

Vibe coding is effective for exploration but dangerous for production — because ambiguity accumulates, AI context evaporates, and there’s no objective verification baseline.

SDD reverses the order: spec first, code second. Its three mandatory components — User Story, Acceptance Criteria, and Edge Cases — turn the AI into a focused executor rather than a guessing decision-maker.

Senior engineers adopt SDD not because they want to slow down, but because they’ve felt the cost of ambiguity in production. A 20-minute spec investment saves hours of debugging and hotfixes.

Start now with the simplest habit: before any AI prompt, write 1 user story + 3 ACs + 1 EC. No special tools, no workflow overhaul — just a small habit with a big impact.

In the next article, we’ll deepen the concept of specification as source of truth — what it means operationally, why it matters in fast-moving teams, and how to build a spec governance system that doesn’t slow you down.

Related Articles

💬 Comments