How XP Made A Better AI Coder

Early in my career as a professional programmer, I stumbled into the Ruby community. In particular, I found myself in a subset of the Ruby community that was passionate about Extreme Programming (XP) and Agile. As a result, I spent my formative years as a developer in an environment where pair programming, test-driven development, small commits, and programming happiness were core values of the culture. They weren’t fringe practices. Pairing 100% of the time wasn’t unusual. It was completely normal.

Those practices shaped how I approach software engineering. They shaped how I approach management. I still default to TDD with small commits 20 years later if I get a choice. But most interesting to me is that all those XP practices have made me a successful user of AI in software development as well.

Always Be Pairing

One of my dev jobs was on a team that was 100% pair programming. We started the day with a standup where we split into pairs, picked a story card from the board, and then spent our day working at one computer with two keyboards and two mice. At first, it was hard. It felt awkward and slow. I found being shoulder to shoulder with another person all day exhausting. But with practice, I learned. I learned to communicate with precision, not phrases like “you know” and “stuff”. I learned to be a useful navigator, pointing out our errors and planning our next step when I wasn’t the one typing. I learned to enjoy pairing as a default practice, rather than as a tool to use only when you have a tricky bug.

When I started using AI for coding, I quickly fell back into my old pairing patterns. I’ve customized my AI tools to use pair-programming principles. We discuss changes bit by bit before writing any code. I discuss design decisions with the AI. I ask it for feedback and ask it to point out errors not only in my code but also in my design. I find that working with AI as a pair-programming partner, rather than fully delegating tasks to it, improves the quality of the results. It also ensures that I understand the code’s design and the decisions that went into choosing that design.

Small Stories, Small PRs

A habit my early mentors taught me was to work in very small chunks. The first time I submitted a 1,500-line pull request that I’d spent 2 weeks on, I was politely but forcefully instructed never to do that again. I was taught to break down tasks into chunks that could be completed and merged in 1-2 days. If I objected and said something couldn’t be broken down into pieces that small, I was told, less politely, that I was incorrect. And when I combined feature work and a refactor into a single PR, I was told that no one would code-review it until I separated them. All this feedback turned me into someone who is uncomfortable keeping a branch alive for more than a few hours.

When I started writing code with AI, it was natural to work in small stories and small PRs. I break features into pieces I can finish in a few hours to a day. Each task is a new conversation and a new branch. I have some customizations in my setup that direct the AI to work only on the current feature. If it identifies additional work along the way, I’ve told it to put those ideas into a to-do list. Once the feature we’re focused on is working, I submit the PR. I also sometimes start a new conversation to critique the code and identify issues, such as incorrect comments, before I do a human review.

Test Driven Development

One of the most important things I took from my time in the Ruby community was the culture of test-driven development and testing as a critical component of software development. I was taught a particularly strict form of TDD that only tested behavior, forgoing mocks and assertions on what methods were called. I still do a tiny involuntary shudder when I see tests that assert new was called.

When I work with an AI, I find I’m most successful when I use a modified TDD approach. The biggest difference is that instead of starting each step with a unit test describing the end state, I start each step with a prompt describing the end state. The prompts are usually closer to functional tests than unit tests, but they aren’t at the level of an entire spec. And just like the tests I write, my prompts don’t describe implementation details; instead, they describe the user-facing behavior.

Self Documenting Code

One of the things I found hardest to adopt in XP was the approach of self documenting code. When my mentors said self-documenting code,, they didn’t mean header blocks on methods picked up by a documentation generation tool. They meant code that was written so clearly that those blocks were largely unnecessary. It took me years to habitually use plural variable names for collections and the singular version of that name for individuals in that collection. And I remember some debates during code review about the order of clauses in conditionals. Do you start with the positive condition or the more common condition? Are postfix conditionals too confusing?

Now that I’m writing with AI, I’m grateful I developed early habits of constantly reviewing code with an eye for readability. Readable code is easier for the AI to understand, and it’s easier for me to review the diffs for the AI-generated code. I have made one adjustment to better document my code for AI. I have a generated file in each directory labeled something like context.md that my AI assistant can use for context.

Conclusion

If you were an XPer back in the early 2000s, what practices have you found most helpful as we adopt AI as part of our coding toolbox?

AI Usage Note: Google's image generation models were used to create the social card image for this post. AI was used to correct spelling and grammar and for minor improvements to readability.