Naïve programming with Aider: Implementing Zustand for local storage
Watch me stumble through the implementation of local storage in my recipe-sharing app project, with the help of Aider.
Note: I am not affiliated with Aider in any way. Just a big fan :)
I am what I’ll call a “naïve programmer”. I can read some code, write some code, but I’m by no means a software engineer.
Despite this lowly station in life, I’ve found that, with the help of AI coding tools, I can finally produce functional software, which is more than I could have claimed before these tools came along.
In this post, I’m going to (clumsily) walk through my workflow with my favorite AI coding tool, Aider. This is the 5th app that I’ve built with Aider, so I’ll point out some lessons I’ve learned along the way.
Part 1: Intro & app walkthrough
In today’s session, I work on a recipe-sharing app that I’m building for my family.
These are the simple features I start with:
The user can type/paste a rough recipe and have it transformed into a standard recipe format.
The user can paste a URL to a recipe and have it scraped and formatted in the same way.
These are the features I add during the session:
The user can create and save multiple recipes locally.
The user can view and switch between these recipes.
In order to implement these features, I have to:
Set up Zustand for local storage
Add some interface elements (e.g. a menu panel and recipe list) and work out some UX kinks to make it all barely usable 🤷🏻♀️
The total time I spent doing these things (while orating) was 48 min 18 sec, though I cut out most of my humming and hawing in these videos. Might not seem impressive to real bonafide SWEs, but keep in mind that I have no idea how to work with Zustand yet am able to get it to work convincingly in this time.
Part 2: Setting up Zustand for local storage
Key points
Building a mental map of your app: The art of reading generated code is subtle, but your goal in doing so should be to build a “mental map” of your app so that you can understand and locate different concepts in the code if, heaven forbid, something goes wrong. You’ll start to build an intuition for how deeply you need to read different chunks of code in order to do this. (To be honest, I didn’t do a great job of this in this session, so you can see what it’s like when you decide to cut corners here…)
As an example, if I make a visual change within a single interface component, I probably don’t need to read that code—it doesn’t change much about the concepts at play. However, if I add a completely new endpoint to my server, I should probably read the code just enough to understand the high-level logic used by that endpoint and where/how/when it gets invoked by the frontend.
Don’t make the mistake of thinking that just because you didn’t write the code, you don’t have to know what it does. But also don’t make the mistake of thinking that understanding your code requires you to read it “cover-to-cover”. Find the balance, and you’ll both save time and be able to keep working with your codebase as it becomes more complex.Dealing with errors: When you ask Aider (or any coding agent) to implement something, especially something a bit meatier, you will almost always get some errors. The first thing I do is take the naïve approach and just copy-paste them back to Aider. More often than not, Aider will fix them right away. I usually give it 3 attempts to fix the error. (Tip: it’s helpful to explicitly say “try a different approach” each attempt.)
Dealing with bugs: When you face a more meaningful bug, my first-line tactic is to describe to Aider what I am seeing and why it is wrong. Pay attention to Aider’s strategy for addressing the bug—what areas of the code is it focusing on, what logic is it adjusting? If it can’t seem to fix the problem (again, I usually give it 3 attempts), that’s when I roll up my sleeves and go investigate the areas of the code where I think the issue lies. This is where the mental map of my app becomes instrumental.
Part 3: Adding interface elements and working out UX kinks
Key points
Describing changes in a user-centric way: An effective strategy, especially when making changes to your interface, is to describe things in a user-centric way, i.e. by listing a sequence of user actions and resulting behaviors that you (or your user) would like to see in the app. You may have a sense for how these features should be implemented in the code, but resist the temptation to be pedantic, as Aider will often over-anchor to your best guess and stick to it even if you’re wrong.
Describing changes in a programmatic way: Alternatively, you can describe things in a programmatic way, i.e. describe exactly what you want to happen under the hood. This is a good strategy if you are confident that you both know how things currently work and how you want things to work. It is also sometimes necessary when Aider can’t seem to figure things out itself. The danger of this strategy, as mentioned above, is that Aider tends to over-index on your specifications and isn’t good at adjusting it if you are wrong about something.
Manually validating generated code: When a Aider implements something, there are two obvious ways that you can validate that it did what you wanted it to do—by reading the code to verify its correctness or by testing that the app behaves correctly. I’ll point out that both strategies are flawed, both in the sense of being annoying and being prone to error. I see the issue of validating generated code as an open question and one of the primary blockers to human collaboration with coding agents like Aider.
Closing remarks
The biggest lesson I want to emphasize from today’s naïve-programming-with-Aider session is this: just because you’re using an AI coding agent to help you build your piece of software doesn’t mean that you are free from the burden of understanding the thing you’re building. You can get away with not understanding how your app works for a little while at the start of a fresh project, but as the complexity of your codebase grows, a lack of understanding will hinder you to the point where you can’t even make further progress or diagnose breaking issues that pop up.
The amount of attention you pay to generated code will vary between both individuals and the type of code they are generating, but think about it as if you’re trying to build out a “Spark Notes” guide to your app in your head. You’ll use this guide to help you diagnose issues when things inevitably go wrong in the future.
Anwyay, I hope this rough walkthrough helps other naïve programmers out there :) I’ll be posting more walkthroughs like this one, so feedback on what would be helpful to see if very welcome!
Full video walkthrough:"