Stop vibe coding and start creating maintainable software with LLMs
Since large language models (LLMs) hit the mainstream, I’ve watched engineers transform their workflows. With tools like ChatGPT, Claude, and GitHub Copilot at their disposal, developers are chatting their software into existence – but they’re doing it wrong.
I’m going to show you why this common approach leads to broken, unmaintainable software that becomes a nightmare to evolve – even with AI assistance. More importantly, I’ll demonstrate a method that produces clean, precise, bug-resistant, high-quality code that remains easy to maintain by both humans and LLMs.
The Problem with Vibe Coding
When most developers use LLMs, they follow what I call the “vibe coding” workflow:
- Come up with a vague idea
- Ask an LLM to implement it
- Get a code blob in response
- Test it, find issues
- Send the code back to the LLM with fix requests
- Repeat steps 4-5 endlessly
This approach initially feels magical. You can materialize complex applications in minutes instead of days. But just like asking a sculptor to “make something cool” without specifications, you end up with something that works momentarily but becomes impossible to replicate, extend, or maintain.

The fundamental flaw? Developers are iterating on code instead of requirements.
LLMs aren’t designed to understand and modify complex codebases. They excel at following descriptions and generating fresh implementations. When you force them to recursively modify code, you’re fighting against their strengths while amplifying their weaknesses.
The Solution: Iterate on Specs, Not Code
The revelation that transformed my development workflow came while working around the context limitations of early LLM sessions. Instead of trying to jam an entire codebase into the context window, I started iterating on specifications.
Here’s the key insight: LLMs are far better at writing new code than editing existing code.
So don’t hand them code to fix – hand them specifications to implement.
I call this approach Spec-First Development:
- Start with a clear written specification
- Ask an LLM to implement it
- Test the implementation
- Instead of modifying the code, update the specification
- Ask the LLM to implement the updated specification from scratch
- Repeat as needed

This method yields several immediate benefits:
- Inherent modularity – complex applications naturally break into component specs
- Drastically lower token usage – specs are compact compared to code
- No context overflow – even complex applications can be described succinctly
- Platform agnostic – the same spec can target different platforms or languages
- Framework flexibility – switch frameworks mid-development without rewrites
- Built-in documentation – your specs document exactly what your software does
How to Write Effective Specifications
The core of this approach is writing good specifications. Here’s my process:
1. Start with a One-Sentence Description
Begin with the absolute minimum description of your application:
An app to automate app store screenshot creation.
This cornerstone sentence helps you stay focused on the core purpose.
2. Expand to a Basic Functional Description
Elaborate slightly on form and function, without technical details:
An iOS and Android app that presents a simple UI to allow users to create, edit and output screenshot layouts. The tool would have basic functionality such as import/export and a visual layout editor with options to adjust placement, sizing and rotation of the imported elements.
3. Detail the Complete Specification
Now create a detailed specification covering UI, functionality, and behavior:
UI style: Minimalist flat style, buttons with rounded corners, blue, light grey and white color theme.
UI layout: Full-screen WYSIWYG editor, two floating buttons at the bottom: 'Import', 'Export'.
Default visual elements:
- A default screenshot title displayed as editable, floating text: 'title'.
- A default screenshot subtitle displayed below the title: 'subtitle'.
- An empty frame displayed below the subtitle, centered horizontally and vertically.
Interactions:
- Import button: Opens the OS-native photo browser.
- Export button: Opens the OS-native share panel with options: 'Export as PNG', 'Save Photo'.
- Element selection: Clicking an element displays a frame around it with handles for moving, sizing and rotating.
- Rotation: A rotation icon appears on the bottom-right of the selection frame. Dragging rotates the element with 90° snapping.
- Positioning: Elements snap to a thin semi-transparent grid centered on the screen.
When your specifications reach this level of detail, any LLM can produce a solid implementation. As you gather feedback and refine your application, you update this specification document rather than the generated code.
A Real Development Workflow
Let’s see this approach in action:
Initial Prompt to Parse and Formalize Your Idea
I have a basic description for an app I want to build. Could you:
1. Parse this into a formal specification
2. Identify any missing details
3. Ask clarifying questions about ambiguous areas
App description:
[your initial description]
Prompt to Generate Code from a Specification
Based on the following specification, generate a complete implementation using [language/framework]. The code should be clean, well-commented, and follow best practices. Don't assume any features not explicitly mentioned in the spec.
SPECIFICATION:
[your detailed specification]
Prompt to Update Specifications After Feedback
I've received feedback on the initial implementation. I'd like to update the specification to include these changes:
[list changes]
Please rewrite the complete specification to incorporate these changes while maintaining consistency with the original requirements.
Prompt to Generate Updated Implementation
Using this updated specification, generate a new implementation from scratch. The previous implementation is no longer relevant. Focus on implementing exactly what's described in the spec, no more and no less. UPDATED SPECIFICATION: [your updated specification]
Rescuing Existing “Vibe Coded” Projects
If you’ve already accumulated a mess of AI-generated code through the vibe coding approach, you can still recover:
Prompt to Extract Specifications from Existing Code
Analyze the following code and generate a comprehensive specification document that describes what it does, not how it does it. Include all features, behaviors, business rules, and UI elements you can identify. Format this as a structured specification that another engineer could implement from scratch.
CODE:
[your existing code]
Once you have these extracted specifications, you can clean them up, remove redundancies, and use them as the foundation for a fresh implementation.
Version-Controlling Your Specifications
Treat your specifications like the valuable assets they are:
- Store them in version control
- Use semantic versioning (v0.1, v0.2, etc.)
- For complex applications, split specs into modules (Core, UI, Data Layer, etc.)
- Include a “Lessons Learned” section documenting tricky implementation details
Your specification file should be the first file you create in a project and the last one you update in each development session.
Why This Approach Transforms AI Development
The spec-first approach fundamentally changes how you conceptualize software development with AI:
- Code becomes disposable – You’re no longer attached to specific implementations
- Multiple implementations become trivial – Generate iOS, Android, and web versions from the same spec
- LLMs become interchangeable – Your spec works with any model or tool
- You maintain control of architecture – The human focuses on what, the AI on how
- Your software becomes future-proof – As LLMs improve, regenerate better implementations from the same specs
Think of it as a return to the essence of software engineering: clearly defining the problem before solving it. The difference is that now, with LLMs, the path from specification to implementation has become nearly instantaneous.
The Meta-Layer: Code as a View
The most profound insight from this approach is seeing code as just one possible view of your specifications. Your specifications become the source of truth, and the code is merely one manifestation.
From a single specification, you can generate:
- Multiple platform implementations
- Test suites
- Documentation
- User guides
- API interfaces
Each is just a different projection of the same underlying specification.
Getting Started Today
To adopt spec-first development in your workflow:
- For your next project, write specifications before generating any code
- When you encounter bugs or need new features, update the spec before touching code
- Create a library of reusable specification patterns for common components
- Experiment with generating multiple implementations from the same spec
The more you practice thinking in specifications rather than implementations, the more powerful your development process becomes.
This approach has transformed how I build software with AI. My productivity has multiplied, but more importantly, the quality and maintainability of my software has improved dramatically. I’d love to hear how spec-first development works for you.