I experiment relentlessly with using AI for computer programming. I have achieved great success and am seeing my use of AI, already pervasive in all of my programming activity, increasing steadily.
There are two patterns I see emerging. I use both, but note that they are largely incompatible:
Dialog programming with AI assistants: I do the programming, but rely heavily on AI assistants for help. I chat with AI to ask for advice, I use the AI for code completion in the editor, I sometimes highlight a line or short segment and ask the AI to suggest modifications or, very occasionally, use an agentic editor like Copilot Edits to prepare a larger change, which I then review carefully and more often than not often make changes to.
Commanding an AI programmer: I don’t do any programming at all, though I do casually review the code produced by the AI. I work with an end-to-end agentic AI programmer like v0, Copilot Workspace, or ChatGPT Canvas, instructing it to create the code I want. I was initially rather skeptical of this approach, but I now see it working well for me in some scenarios and I find it valuable. Beyond the fact that it allows me to produce code that I could not have written without considerable additional effort (for example with programming languages I don’t master or in domains I don’t understand well) it also pushes me to approach the craft of software construction as a manager, focusing on the bigger picture of how different pieces work together to create a useful product, rather than how to implement each piece.
I’m comfortable with both approaches, find both applicable, and don’t have an ideological commitment to one approach or the other. What is becoming ever clearer to me, though, is that there’s nothing in between. It is not effective, and in many cases quite dangerous, to mix writing code with AI assistance together with letting AI do the coding. The reason is that understanding the code written by someone (or something) other than yourself is effortful and error prone. Not understanding what the AI did, or the AI not understanding what I did - that happens more often than you’d expect - renders the entire experience unpleasant and risky.
I’m considering different ways of mixing the two approaches:
Per-project separation: the entire project is constructed using only one approach. That’s my current default, but it means that I’m under-utilising end-to-end AI programmers, since they are not really good enough to work on more complex projects.
Per-unit separation: modularisation is an approach to software construction that I generally tend to avoid - as a human writing code I just find it confusing and high overhead - but it can be useful here. What’s a unit? It can be a class, or a module focusing on well-defined functionality and interface, a microservice, a function, … I’ve used all of these with some success. For example, I have farmed the porting of microservices I’ve written in Python to Rust and got great results from Copilot. I also have some functions in my code that have been written by AI and which I clearly tag as such in the comments with the intention that I never edit them myself, instead handing them back to the AI for improvements where necessary.
I love dialog programming, and it feels most natural to me, but I suspect that with time we will be doing less of that and more the management of end-to-end AI programmers. One reason is that AI keeps improving, and what isn’t yet possible today, may become possible in a few months. The other is that once you get an AI agent to do work on your behalf, it becomes hard to convince yourself to do the same work manually, unless you’re doing it as an exercise for learning or fun. It’s like washing dishes without a dishwasher. The arrow is pointing in one clear direction.
I’m curious to hear your thoughts on managing a larger project using the latter approach. When you have many modules that need to work together, how can you have the AI write all of them if it doesn’t comfortably fit in its context window?