Good practice is good for LLMs too

ai llm development practices code

2025-06-08


LLMs are changing how we write software. In the face of this unheaval, it's tempting to think these tools somehow absolve us from following good engineering practice. We have fifty or so years of layered wisdom, and sticking close to this knowledge not only makes your codebase more robust, it enhances the LLM capabilities and developer experience.

One of the most important practices, now more than ever, is comprehensive test coverage. Ideally, your test suite should be granular enough to run relevant tests after each AI tool use. I've been using pytest for years, and recently added pytest-watch to my workflow. It's a small thing, but being able to see tests run automatically in the background as I'm prompting the LLM gives me immediate feedback and catches regressions early. You need automated, fast QA looking over the AI's shoulder.

Branch discipline is another essential practice. With LLMs, it's easier than ever to generate (and just as easily discard) large chunks of code. Keeping your LLM-assisted work on a dedicated, short-lived branch with a single, clearly defined purpose makes it trivial to revert changes if things go sideways. This isolates the impact of any AI-induced chaos to the specific feature or code area you're working on. Put up the safety net.

Another thing I've started doing recently is keeping a detailed work diary. This is invaluable for rebuilding LLM context over multiple sessions, especially when working on larger features or complex refactoring. While your commit history provides a record of what changed, the work diary captures the why – the reasoning behind decisions, alternative approaches considered, and future plans. We talk a lot about building context for the LLM, but remember your own personal context is an extremely lossy system.

DRY – Don't Repeat Yourself – is a fundamental principle of good software design. It's even more important when working with LLMs, as they have a tendency to reinvent wheels (sometimes quite badly). A well-defined application architecture, with clear separation of concerns, helps guide the LLM towards reusing existing code rather than generating duplicates. For example, in Django projects, I usually have a models.py, views.py, and a services.py for business logic. This isn't anything special, but I stick to the conventions, and keep everything in it's right place. The more you stick to a predictable structure, the easier it is for the LLM to understand the context and generate code that fits seamlessly into your existing codebase. The agents are easily distracted, but a clear set of instructions and a tidy workspace – they're much more likely to produce something useful.

Finally, good task ergonomics are essential. A root Makefile or justfile with clearly defined tasks (setup, deploy, lint, test) provides the LLM with a standardized way to interact with your project. This saves tokens and reduces the risk of the LLM trying to figure out novel (and probably incorrect) ways to perform common operations. Provide the AI with a well-labeled control panel instead of making it rummage through a box of wires.

LLMs are powerful tools, but they're not magic. The good practices we developed to work with humans still apply to working with a cloud AI.