Table of Contents #
Somewhere between mindlessly giving in to the hype created by big tech and entirely boycotting generative AI there’s a middle ground that lets developers make good use of the new tools without sacrificing software quality or giving up on their craft.
Fundamentals # #
We believe that the principles and fundamentals established by generations of software engineers are not only still relevant, but in the age of cheap code generation they are actually more important than ever.
Users and customers deserve to have confidence that the software they use has been reviewed and understood by humans who can take informed accountability for it. It’s a crucial element of any sustainable product – humans taking actual responsibility for the work, something that is simply not possible from pure vibe-coding.
Workers deserve to be allowed to create code and products that they’re proud of; to work on codebases that are understandable and maintainable – not on sloppy spaghetti code.
It’s important to be transparent about the extent to which LLMs were used in the creation process – how much human power was involved in creating the output?
Less code is generally better – as every line of code is a liability that will have to be maintained over time – while the true value of any product lies in its functionality that matches the users’ needs and in a shared mental model of the codebase within the team that allows for agile and sustainable development.
Rather than producing features as quickly as possible we should focus on quality, delivering value to users, and on risk reduction – especially when working with non-deterministic output that characterises LLMs.
LLMs are a tool and should be treated as such – don’t turn them into a dependency, obligation or disruption.
It’s important to minimise the negative impact of LLMs on the environment, communities and individuals.
Quality of production code # #
Every line of production code must have been read and understood by a human. Engineers must be able to explain the code, debug it and modify it – even when Claude is down.
Generative AI can be used as a tool for fast prototyping, proofs-of-concept, internal tools, for validating ideas without having to invest much effort into an implementation that you might throw away next week, maybe even for covering production code with tests – but the production code itself being thoroughly understood by humans is critical.
There’s a massive difference between “I took this cheaply and quickly generated code and worked on understanding it and shaping it into something close to what i would’ve written myself” and “the computer spewed it out, i didn’t even bother trying to understand it”.
Short iteration cycles # #
As tempting as it is to just define a large task for an agent to finish overnight, and then consider green tests and passing CI as a good enough reason to deploy the changes to production – that goes directly against all the fundamentals.
Keeping iteration cycles short – reviewing the LLM output often and course-correcting early – helps keep the mental scope small, reduce cognitive debt of the team, reduce re-work, catch bugs more effectively, and also optimise token costs.
Same applies to code reviews: nobody likes being asked to review a massive thousands-lines pull request. It’s a good practice to split larger features into multiple PRs in order to reduce the mental overhead. If that’s what works for code written by humans, there are even more reasons to apply that same policy to code generated by machines.
Keep it simple # #
Generative AI tends to create verbose and overcomplicated code, often inconsistent with the rest of the codebase; it loves to add lengthy comments that nobody reads and that are very prone to becoming outdated and therefore confusing rather than useful. Be critical of their output and look for ways to simplify it.
LLMs are pattern-matching machines – therefore excelling at tasks like refactoring and tackling technical debt. If you give them one or two good examples and ask them to repeat the work on dozens of other files, they’re likely to do a good job. Use AI to reduce technical debt – not mindlessly increase it. Automate boring tasks – enjoy the fun ones.
Instead of treating AI as some kind of magical, infallible programmer that can do anything (it can’t), treat it a non-deterministic machine that it is; the closest analogy to a human would be either a junior developer with a narrow focus, but a huge talent to complete boring, repetitive tasks very fast, or a senior developer that you can brainstorm ideas with, but ultimately make decisions based on own judgment and experience.
Code reviews # #
The purpose of a code review is not just to validate the correctness of the change. It’s also a vital tool for sharing knowledge among the team members and for staying on the same page on architectural decisions. Delegating code reviews to machines comes at an invisible cost to long-term sustainability of the project.
Agents may pre-review a change to get the nitty-gritty out of the way, and the author should address those recommendations before bothering a teammate for a review – but ultimately there’s still just as much value in having two pairs of (human) eyes on every code change as ever.
Transparency # #
Your users deserve to know whether they’re buying a quality product or being fed slop nobody has even bothered to read. They deserve to know whether their personal data and trade secrets are ever leaving your servers as if ‘agentic workflows’ magically made that okay.
Your workers or teammates deserve clarity and proper training on what tools, behaviours, patterns and workflows are acceptable and expected of them; they deserve the right to refuse excessive AI mandates.
You should have an AI policy.
Sensitive data # #
LLMs must never be granted access to PII (personally identifiable information), trade secrets or credentials. Use dummy data for local development. Sandbox environments properly. Don’t give agents access to production systems.
User support and decision-making # #
The best way to support humans is by letting them talk to humans. Automating user support might seem like a good cost-cutting measure, but has proved countless times to decrease customer satisfaction and retention. If the user’s issue was straightforward and fixable by quoting FAQs, most users would rather just read the FAQ themselves than have a bot misquote it for them. If they decided to take time to chat or to call, they likely have a problem that’s more messy and personal than a bot can handle. You might delegate the initial triaging to a bot, if you really have to – but keep that interaction minimal, make it clear that responses are machine generated rather than trying to pass it off as a human, and give the user an easy way to get connected to a human.
Moderating content and making decisions that impact people’s lives (like hiring or renting an apartment) should always be made by humans who can take responsibility for the decisions. Use a bot for initial triaging, if you really have to, but keep the actual decision-making to humans.
Mindful choice of models and use cases # #
Various models have different costs – not just monetary, but also ecological, social and even geopolitical. Rather than giving in to the latest hype, make informed decisions which model to use and – more importantly – whether to use any at all. Treat AI as a tool – don’t turn it into a dependency, obligation or disruption.