Software Design in a World of AI
How we build and design software is obviously changing with LLMs. For the most part AI is creating the same software previously done by hand, same general principles and structure, just faster and more efficient. However, should it be creating software using those same principles?
If we know an LLM is going to be the primary tool to create, edit, and maintain the software then this should lead to different design choices. As Larry Wall famously put it, a good engineer is lazy, so previous software was designed in a way to make it easy for an engineer to make changes.
For example, to add a footer to each page, we use a common component or template. So when making an update it only has to be made in a single spot.
In early websites, running Apache, we would use server-side includes to handle this. You would register .shtml as a file that Apache should parse for an include. Then each html file you’d include a snippet like:
<!-- #include file="footer.html" -->
Now with LLMs, that wouldn’t really be needed. You could include the footer as plain old HTML in every file, and then when making an update, does it really matter if the LLM has to update 400 pages to say tweak the copyright date.
In this example, it would require a bit more token burn on the edit side, but on every page load, Apache wouldn’t have to do any extra work to serve.
CMS
At work, we are using a similar idea to replace parts of our CMS with Claude. We use a CMS to make it easier for the marketing team to update portions of the site.
However, onboarding marketing to the CMS system is an extra step, or three. They need a login, then learn a new system which is organized and tagged in different ways, and then update content and make sure using proper environment, etc. This is all unfamiliar.
However Claude is quite familiar. So we can create skills and a project in Claude to set them up, using GitHub/Vercel enables them to update the repo directly and preview their results easily with Claude as their editor. This is the same basic idea of adding the content they need to the files directly and serve.
This has the possibility to remove a whole piece from our infrastructure and make it easier for them to publish to boot.
Generators and Factories
This same idea probably can be applied to many parts of software design. Do we need more complicated factory design patterns, abstract classes, and inheritance?
The factory pattern is mainly used to prevent code redundancy, should that be a goal? How many times does the next implementation not quite fit and requires extending and complicating the factory making the code harder to understand as you trace through file after file of inheritance.
For example, take something like a PaymentProcessor factory that made sense for two payment types, then a third comes in that doesn’t quite fit, so someone adds a flag, then a subclass override, then a config object threaded through three layers — and six months later, understanding what happens when a payment runs means opening five files to trace the actual logic.
DRY
DRY, Don’t Repeat Yourself, is a long standing software design philosophy, so ingrained we rarely ask what it is optimizing for and does it still make sense.
React is DRY on steroids. React takes an HTML page and turns the simple file into a tree of header, nav, footer, all their own components (and files) with these components broken into smaller components, with styles pulled into separate files. All this so that each piece exists in exactly one place. Understanding a single page can mean walking through a dozen files, keeping the shape of the tree in your head as you go.
Ugh! What a headache.
The design makes sense if humans are going in and editing a small piece, you can see the one component is in a single file making it easier to review and maybe see the impact, if you also trace all the spots that might be including that component.
Claude, v0, and Replit are already eschewing this behavior and many of the “apps” they are making are a single HTML file with HTML, CSS, and JS all in one file. Not because it’s a better architecture, but because it’s simpler for a machine. No modules, no build step, no tree to walk. The LLM can read the file top to bottom and have all the context needed to make any changes.
Summary
This isn’t a call to rip out every abstraction. Duplication still means every copy has to actually get updated, and a missed one is a bug nobody sees until it’s found the hard way. But it is worth reconsidering software design, a lot of the structure in how we built was around human limitations. Now might be a good time to check which parts of that scaffolding are still doing real work, and which parts we’re maintaining out of habit.