Learn, Plan, Design before Code

By Chad Kluck on

Before we begin, I want to put this out there:

This post is NOT about AI.

It's about getting back to the fundamentals we learned in our programming, software engineering, and project management classes. (Or, if you didn’t take those classes, I encourage you to still read on; we all developed an interest in coding even before we set foot in class anyway.)

As you read this post, I implore you to identify the same practices we were all taught as developers and software engineers but may have set aside because we needed to get things working, the project done with less time, fewer resources, and growing backlogs. Many of us on small teams or working solo vibe-coded our entire careers, but instead of re-prompting AI, we were stuck in the code-deploy-run-research-error-message-fix-deploy-run-research-next-error-message loop on our own. Yes, for many of us, we were vibe-coding all along, well before AI!

I’m here to break that. We can’t continue down that path. If you’re not bringing in the fundamental principles of engineering, planning, patterns, methods, security, and efficiency from the start, what you build won’t be maintainable or sustainable in the long run. And going down that rugged path with AI will only make it worse.

Garbage in. Garbage out. Amplified.

Even if you aren’t using AI, especially if you’re just starting out, put AI aside and focus on the message of this post. Replace the term “AI” with “Human Mentor” (copy, paste, replace all) and view it from that angle. Read this article twice. One time with “AI,” the second with “Human Mentor,” in any order.

In real life, AI does NOT replace a Human Mentor. FIND—don’t REPLACE!—a human mentor!

A screenshot of a Find/Replace dialog box where the user is finding text 'human mentor' and replacing with 'AI'
There is no Ctrl+Z or Ctrl+H in real life.
A screenshot of a Find/Replace dialog box where the user is finding text 'human mentor' and replacing with 'AI'

This is not about AI. It has never been. It is about becoming a better developer or engineer building the next great product.


Let’s begin.

When it comes to starting a development project, if you’re like me, it’s hard to not jump right in. That’s where the fun begins! Coding!

That’s great for personal weekend projects, side projects, even when you’re learning, but if you are getting paid to do the development and it will end up in production, that’s a whole other story.

I’ll admit, especially when you are a solo developer on a team, which until recently is where I spent most of my career, following everything you learned in computer science class was overkill.

UML diagram showing the properties and relationships between the parent chair and child objects: stool, arm chair, and swivel.
UML diagram, anyone?
UML diagram showing the properties and relationships between the parent chair and child objects: stool, arm chair, and swivel.

User stories and specs?

I’d be the only one reading them.

Documentation?

By the time I wrote it, I was handed another feature request. Plus, I always wrote documentation for the “next” person. After a decade, I was always that “next” person. Yes, helpful, but again, only for me. There was enough there to jog my memory, but could a “new” person really fill in the gaps if I were abducted by aliens (or replaced by one)? Stolen identity or not, that’s my legacy and reputation we’re talking about!

Tests?

Who has time? Just manually test. And keeping test suites up to date with the new specs or package updates? Nope. Forget it. I spent more time fixing and updating tests than writing code.

Three years into AI (and really, only the last year was jaw-dropping for software engineering), and I don’t need to tell you how AI changed things.

We still don’t worry about user stories and specs, documentation, or tests. After all, anyone can now—say it with me!—Vibe Code!

Just kidding!

I’m saying it right now:

Do. Not. Vibe. Code.

I’ll say it again:

Do not vibe code.

I’ll say it now as a rant:

Do not vibe code. Do not vibe code. Don’t. Just don’t. Do not vibe code. No. No. No. No vibes here. Vibe-free zone. Thinking about vibe coding? Just step away from the keyboard. Unplug it/turn off Bluetooth. All vibe and no plan makes Jack a dull boy. Do not vibe code. Plan or plan not, there is no vibe. Vibe-free Code. Vibe-free Code Zone. No vibes.

Now that that is out of the way, let’s figure out what we should do.

First, instead of vibing, let’s focus on “AI-Assisted Engineering.”

There is a great article (and book) by Addy Osmani from last year around this time titled “Vibe coding is not the same as AI-Assisted engineering.”

A great read, and I hope you’ll take the time to read it, but I’ll offer this one line from the article and move on:

“This isn’t engineering, it’s hoping”

So, how do we get away from “hoping” a project into existence?

By doing what we learned in computer science school:

  • Write specifications
  • Define requirements
  • Design
  • Code
  • Run tests
  • Document

In the past, no weekend project or small team would EVER dream of doing all this and keeping up to date while juggling daily tasks. However, now it is non-negotiable.

If you want to keep pace, if you want to scale and maintain your creation once you release it and it becomes critical, you need to do all these things. From the start. Not as an afterthought. From the start.

The world is full of disposable items. The world is full of garbage.

Don’t create more.

And the environmental impact of AI? Reduce your token use. Conserve and use them judiciously. Don’t waste tokens iterating over something that could have been done right the first time if you had just planned. The quicker AI produces a good result, the less time you sit in front of your computer and the more time you can venture out and enjoy light not generated by an LED screen (or CRT if old school).

Think of all the time you spent scrolling and searching for some obscure answer in the past. A day? A week? How many cycles of deployments before you found a solution? How many resources did that burn?

AI can use fewer resources than a developer churning through server time and midnight hours trying to find a solution. But AI can also become an addiction and bloat.

Slow your use. Breathe. Take a chance to walk away and use your own mind.

To assist in this, I developed a Developer Mindset page to help developers slow down and focus on the process. (Yes, I vibe-coded it while on vacation. We listen, and we don’t judge 😊)

If we slow down, catch our breath, stretch, and think about what we will build, we will be ready for the heavy lifting.

Let’s revisit the task list:

  • Generate specifications
  • Define requirements
  • Design
  • Code
  • Run tests
  • Document

Now we’re going to do what any good prompt engineer would do and iterate. Let’s add “Learn and Explore Options” to the top of that list, number it, and combine Code, Tests, and Document, each delimited with a plus sign (so we never leave it to last, because what’s last never gets done, and ensure they all add up to the complete product):

  1. Learn and explore options
  2. Generate specifications
  3. Define requirements
  4. Design
  5. Code + Run tests + Document

I’m sure we could include any number of steps, but let’s not over-engineer or overthink this. We haven’t even gotten to the main points of this post yet, and we’re a thousand words in!

1. Learn and explore options

The first thing I like to do is figure out how something would work even if I think I already have a good idea. I like to challenge myself, but after a while, thinking in your own head creates an echo chamber; you only hear your past thoughts. Nothing new emerges.

If you are an introvert like me, or someone who had to read through hours of philosophical waxing as a philosophy minor (also like me!), then being able to converse with AI is a welcome accessory for technical discussion and debate. Imagine if Descartes wasn’t alone in a cabin with his own thoughts but had AI! He might have concluded that we ARE in a simulation!

However, you need to be careful not to let AI assume your thoughts, fall into widely spread bad practices, or generate hallucinations (I cringe at some of the advice I see—or even saw before AI!). Therefore, I typically draw a hard line at the start to keep AI from congratulating me, agreeing with me, or accepting what I say as “the way.” If it has a reference, I follow up and read it. I often ask for references that support and oppose my ideas.

Establish guidelines:

“I want to implement Amazon Cognito for authentication on my web app and backend API (API Gateway with Lambda, CloudFront distribution backed by CloudFront). I’m using React for the front end. Users will have access to a profile where they can respond to a poll posted once per day by a moderator. They can respond to two polls per day if they use Buy Me A Kitten to pledge support. Play the role of a senior project manager, security analyst, cloud architect, operations engineer, and software engineer. What are some possible ways to design this? (I want serverless-first). Does Buy Me A Kitten even have an API to connect entitlements to a supporter’s external profile? What do I need to consider that is commonly missed?”

Now, that prompt was quite loaded. If you’re new to development, cloud, or even AWS, you might never have thought about 90% of what I just mentioned. Or even know 50% of what I was talking about.

Someone just learning may have just typed:

“I want to implement a user profile for members to respond to a poll question. I want to provide additional polls to supporters who use Buy Me A Kitten.”

Which is okay, but I would preface it with:

“I don’t want to implement this yet, I want to learn how such systems are designed and architected in the cloud.”

From there, learn and build upon your own mental model. Ask new questions. Dive deeper. We have no excuse not to ask new questions and dive deeper to understand what we don’t yet know. Be the child that asks, “Why?” AI won’t get annoyed and dismiss you by saying, “Because that’s the way it is. Now, let’s play a game of who can stay quiet the longest.”

Again, referring back to Addy Osmani, he recently wrote a post titled “Agentic Skill Decay” with the subtitle “Agents can finish the task without teaching you anything. Building expertise now has to be deliberate.”

Again, good read. I highly recommend it for developers at any level.

Your first prompt doesn’t need to be towards the end goal. In fact, it shouldn’t.

Think of this: If you've never swum before but want to swim the English Channel, start small. A local pool. A class. Learn the basics. After you are more confident, move towards a natural body of water. Go further every day. A mile. Two miles. Compete in larger and larger competitions. Then, once you have the skills and endurance, then, and only then, swim toward the goal.

Each workout didn’t close the gap from one shore to the next. Swimming 5 miles a day in training doesn’t get you 5, 10, or 20 miles closer to France. Five miles doesn’t count towards actually crossing the channel, yet you wouldn’t consider it wasted. You wouldn’t jump into the channel and say, “If I’m going to swim, I will make it count towards getting to France.” This isn’t a buy-five-subs, get-a-sixth-free reward. Those repeated 5-mile swims prepared you to swim the 23-30 miles from England to France (or vice versa).

So, where does this mentality come from that code and time are wasted if we leave our tutorials and experiments behind in the sandbox? Training is never wasted (unless you never use it). Training is repetition. Training is encountering new obstacles and overcoming them.

Thinking you will go from nothing to product in a series of prompts is like setting out to swim the channel without preparation. Training while you head towards the goal will only exhaust you and is ill-advised. I’ll even say it is downright irresponsible.

No one ever built a prototype single-engine aircraft and then iterated until it became a Boeing 747.

Sketches showing progression building a single engine plane, then changing to a bigger single engine plane, followed by a jet plane, and finally a 747.
Yes, this image was AI-generated. Yes, you can obviously tell. When I gave GPT-Image the prompt, I didn’t set out to prove anything other than you don't build a plane and then iterate new features. But note how, at each iteration of development, new errors are introduced. I’m going to leave it as is, but caution against using it for your own aircraft design because of the blivet effect.
Sketches showing progression building a single engine plane, then changing to a bigger single engine plane, followed by a jet plane, and finally a 747.

All this to say, spend some time learning and training before you plan. Iterate and become confident with your plan before you set out.

If you vibe-coded something in the past or even planned something out but had AI execute the tasks, go back to that project and have AI explain some of the methods used. Ask whether there are any gaps or considerations around security and maintainability. Explicitly ask it to teach you so you can learn the concept.

Also, take some time to learn some of the methodologies and patterns used in computer science. They are truer today than ever before. They are more accessible and easier to implement even for one-person teams. I recommend browsing Semantic Anchors for some daily vocab lessons.

Get out there and learn. Hand code. Vibe code gasp! Dive into the fundamentals. Use a sandbox and leave the sand behind.

Learn, get background, ask questions.

2. Generate specifications

Often, at this point, I've iterated with AI, and the conversation has drifted into design, architecture, code snippets, and the “it seems like we have a defined set of requirements; would you like me to code this for you?”

No. Stop. Reframe.

I don’t mix my AI chat musings with my coding agent.

But we did have a good conversation, and I do want to bring some of it forward. We—I mean, I—have a better understanding of exactly what I am trying to produce. It may look drastically different than when I set out and gave my first prompt inquiring about user tier management, but I now know more about capabilities, security, and cloud resources.

Instead of asking the AI to create a design, I have it summarize the findings and generate a requirements document focused on the specs. I refrain from generating a design or implementation document at this point.

Here’s an example of one I used recently (full conversation):

“I don't need any implementation architecture, but I do need a specs document that provides what we know and envision so far, as well as some of the basic specifications we have described here. I need it in user story format. I need this to be a separate stack from the current application; I don't want to integrate it into the same stack, as I wish to maintain separation of concerns. We will need a requirement that provides IAM policies for the Lambda to interact with the existing DynamoDB database and infrastructure. I want to be able to add multiple sources (Patreon, Buy Me A Coffee, GitHub Supporter, etc.) and add more in the future. This is also meant to be a framework for me and others to use, since it will be provided as open source. It should be separate enough to implement as long as the DynamoDB user model is set up to interface with it (or can be custom configured). Again, this is just a specification, not a design document.”

I can work with the output from this.

I can copy it out, make some tweaks, and bring it over to my coding agent to begin a formal requirements document.

Why the separation?

Because it is a boundary, a different headspace. A hard stop. A chance to pause and reflect. Since it is a running thread on mobile or in a browser, I can easily fire it up and resume when a new idea comes to mind, I do more research on my own, or I talk with a peer for ideas.

I like to iterate ideas with my chat agent and implement with my IDE agent. Boundaries. Plus, my IDE agent has my codebase, steering documents, etc. It can use those to apply the requirements specific to the codebase.

Also, it gives output to an otherwise long-running session that could drift or lose context if I carried it any further. I can take the document and start fresh within a new context window. If you still have a few iterations in mind with your chat agent, it wouldn’t hurt to still save a specification document and bring it into a fresh session every now and then. If you feel like you took a wrong turn, you can re-copy the good specification from the previous chat and start fresh again.

3. Define requirements

Specs and requirements are the heart of AI-Assisted programming, and I believe that it goes further than just writing out requirements in “As A, I Need, So That” format.

It actually begins before that, with proper steering documents and an opinionated framework.

Drawing on what you went over with your chat client, determine aspects of your project that have already been addressed either in your own previous work or out in the world.

For example, if your project has a user interface, then steering documents for accessibility requirements, dark mode and contrast picker design, and brand color specifications (which are generic enough to move from project to project) are essential.

Capture those guidelines in a steering document and create a personal or organizational library or repository you can update and pull from. Review already existing steering documents (I recommend many from the Atlantis DevOps Platform and the kiro-project-template repository from BinarySword on GitHub). Use these directly or as inspiration for your own.

Steering documents establish unspoken requirements that follow each project without having to be regurgitated in each requirements document. They bring uniformity to your projects (making them easier to maintain), use fewer tokens (by guiding you the right way the first time), and capture the small things that are frequently overlooked (security practices, CHANGELOG updates, documentation structure, etc.).

You should also choose an opinionated platform during planning, not too early, but you should have iterated toward discovering the best option before you write your first set of requirements.

By starting with a framework or platform that provides core functionality, you can skip many of the “already solved” tasks. Whether you use React for the front end or Atlantis DevOps Platform for backend serverless (a little self-promotion for my platform project), design and implementation choices are already made.

Developers should never start a production project from scratch with a blank repository. Instead, build on a strong, tested platform that provides industry standards and best practices. If you use an existing application and deployment platform, you don’t need to focus time and tokens on learning and applying that piece of infrastructure.

In its platform engineering guidance, DORA states, “The platform’s primary goal is to reduce the cognitive load on developers by abstracting away underlying complexity.” Developers should not have to become experts in CI/CD, cloud, and security. Platforms should “Proactively 'shift down' cognitive load.”

With a platform, you (assisted by AI) focus on the requirements and design for:

  • Business logic
  • Domain behavior
  • User experience
  • Data transformations
  • Novel requirements

And the platform (foundation) already has implemented:

  • Architecture patterns
  • Infrastructure
  • CI/CD
  • Security
  • IAM
  • Authentication
  • Logging
  • Observability
  • Testing
  • Documentation
  • Operational practices
  • AI guidance

What list would you rather tackle? The short list? The long list? Both?

The short list, of course! (However, if you answered, “long list” or “both,” then maybe you want to be a Platform Engineer!)

The question is: “How many of the engineering decisions have already been solved, reviewed, documented, automated, and made reusable?” Those should be the platform's concern. Building upon that, “What makes my project unique and creative?” That should be the developer’s concern.

Going back to the swimming analogy, most swimmers don’t design their suit or goggles; they don’t do the heavy research developing new exercises and training methods. They use tried-and-true methods. Yes, they may dive deep into how training techniques work and study health and human performance, but they don’t reinvent the domain.

AI is probabilistic → platforms create repeatable constraints → therefore, a platform can provide a bounded environment for AI agents.

Note that an opinionated platform is NOT a past project (or tutorial or aircraft) that you modify to meet a new need by removing its innards. It is an empty skeleton with a simple “Hello, World” to build upon, not restructure. It is a foundation, not a complete reno-job that requires gutting down to the frame.

Once you have your chat-generated plan, steering guides, and platform picked out, start writing specs with your IDE's help. It will have access to the steering guides, platform code, and documentation to understand what is already dictated and built versus what it has to build.

Be sure to read through the complete specs and iterate, ask questions, clarify, and do more research. You’ll spend fewer tokens iterating in the specification and requirements phase than the design and implementation phase.

That has always been true in software development. But now we have measurable tokens!

Figure it out now, because it will cost you later.

(I thought about inserting an image here of a pixelated 1990s video game character (Legend of Zelda style) within a shop deciding between 10 health potions (1,000 tokens) or a map for safe passage (600 tokens), but I ran out of GPT Image tokens for the day. So just use your imagination. (Please don’t AI-generate an image for yourself; your imagination is good enough.))

4. Generate design

These sections are getting shorter because we front-loaded all the work! And, by this point, as you read through the requirements and now the design, the full picture should be coming together, and the AI begins to go on autopilot.

You will see design patterns and solutions to problems you never thought of. You will understand how the various components fit together. Maybe you did research and caught an issue on your own.

You’re learning!

You’re actually understanding what the AI is generating!

If you’re still unsure about something, then go back and review and ask questions, not necessarily to change, but to learn.

The important thing is to review. Review the requirements. Review the design. Make changes if necessary.

Then, and only then, move on.

5. Code + Run Tests + Document

I’m grouping these together because, with AI, they happen in parallel within the implementation or task phase. The AI can handle these tasks with the right requirements and design, and they should always be included in the requirements.

Don’t forget the change log! I usually have a steering document dedicated to managing the changelog based upon the specs so it gets picked up and included in every implementation.

If you review some of my projects, such as the serverless-image-resizer, you’ll see that I include a docs directory, as well as architecture, deployment, and README documents.

I keep these up to date and include them as AI implementation tasks through (you guessed it!) steering documents, so I don’t have to manage them.

I try to review them, keep them up to date, and consolidate or hand-edit them after several feature updates, as needed. Can there be mistakes? Yes, but what is better? A fully documented project (with only 99% accuracy) or a project with little, no, or severely outdated documentation?

I like to divide my documentation into three audiences:

  1. Admin/Operations/Installer/Initial Config: The individual with engineering, operations, and/or technical knowledge in charge of configuring and deploying the initial environment. They may also support the application’s architecture and infrastructure as it interacts with other applications and services. They ensure the application is efficient, cost-optimized, and observable. They maintain the underlying platform if necessary (VPCs, OS, Firewalls).
  2. Developer/Maintainer: The individual with engineering, development, and/or technical knowledge that adds features, fixes bugs, maintains packages, and monitors the application or service performance.
  3. End-User/Consumer: The individual, technical or non-technical, who is consuming the content provided by the application or service. Examples include APIs, HTML pages, documents, reports, and downloadables. Typically retrieved from the output of a script or accessed from the endpoint provided by the application or service.

Again, I have a steering document dedicated to ensuring AI provides the proper documentation to the right audience.

If I come to a legacy project without documentation (or tests), the first thing I do is make it AI-ready by bringing in documentation and steering documents related to testing, instructing the AI to examine the codebase, understand its architecture and purpose, and write tests and documentation. This may be a single request or divided into multiple requests depending on the project.

Usually, after all is said and done, once I have spent time across several projects honing my skills in prompts, learning, spec and requirements generation, design review, and tweaking steering documents, complex projects will deploy on the first try with little to no re-work. I may add a few features I hadn’t thought of before the first release, but there are rarely bugs (just designed as intended and approved, but let’s make a change!)

Summary

I’ve come a long way since I really started letting AI agents write code to the specifications I developed ten months ago. AI lets you dive deeper into what is possible. While it is a coding tool, don’t forget to use it as a learning tool, too.

What I went through in this post isn’t new. Instead, it refocuses on tried-and-true methods developed over decades for project management, software development, and engineering.

Since we can develop solutions faster, now is the time to reincorporate everything we set aside while chasing deadlines with constrained time and resources. Plan, design, test, and document. Build in security, observability, and deployment practices right from the start.

We can’t continue the bad tradition of the code-deploy-run-research-error-message loop into the age of AI. (Again, we’ve been vibe coding towards production for decades already!)

AI is an amplifier. The question is, what are you going to amplify? Are you going to add to the noise, or provide clarity?

Comments

View comments, reply, like, or share this post on:

Tags