Introduction
In today’s fast-paced software development landscape, the balance between speed and quality is increasingly difficult to achieve. A recent conversation with one of my team lead’s highlighted this challenge. His team relies heavily on AI-generated code, which raises questions about maintaining high standards in code quality.
The Current Workflow
The typical workflow in this team is straightforward:
- The product owner writes a requirement.
- A developer takes the requirement and prompts into an AI tool to generate code.
- A senior developer conducts quality assurance (QA).
While this process is efficient, it has led to some significant challenges.
The Challenge of Quality
As AI-generated code becomes faster and more prevalent, senior developers often find themselves flooded with QA requests. The rush of requests can lead to: increased workload for senior developers and inconsistent quality in the code being produced. Pressure to meet deadlines, resulting in decisions that compromise quality.
Ultimately, this situation can lead to code being deployed at an average quality, failing to meet the standards of technical excellence. In the concrete situation, my team lead told me, that a senior developer complained about the poor code quality of a mid-level dev, which just copy-pasted a poorly composed piece of code generated by Codex.
Maintaining Excellence in Code Quality
The question then arises: how can teams uphold excellence in code quality while accommodating the speed and volume of work produced by less experienced developers?
In my team lead’s situation, we discussed and decided, that the mid-level developer had to redo the work. We gave him some guidelines and asked that he should organize a short review meeting with the senior dev when done.
Across our company we don’t have a clear rule on how to handle this. For new projects containing web-services we have example implementations, which all devs can checkout to base their work on. Projects that go to the cloud and which will contain publicly consumable APIs have the strict rule to contain an endpoint with an OpenAPI specification. Services that are only consumed internally are strictly OData but don’t require such an endpoint. We have configs for several linter extensions in Visual Studio, but it’s open to the developer to use it or not. The interesting part is: most of the developers decide not use the linters, because it limits their freedom of how to implement stuff, but most of them are also not happy with the coding style. We’re trying to resolve this contradiction by giving the developers more responsibility, establishing more ownership of the code. This is still work in progress but each day more of the developers are taking the next step. Some teams have their own standards, but there’s no company-wide enforcement of linting.
Continuous Training and Mentorship
Investing in the development of less experienced developers is crucial. Ongoing training and mentorship from senior developers can help bridge the quality gap: pair programming sessions can enhance learning, regular code reviews can provide constructive feedback. Ten years ago I had one developer who freshly finished his apprenticeship. Nobody thought he was skilled. I started mentoring him here and there. Today he is the most wanted guy in the company when it comes to go for bug hunting in our legacy code.
Utilizing AI as a Tool, Not a Replacement
While AI can accelerate development, it should not replace the need for skilled professionals. Developers should use AI-generated code as a starting point rather than a final product.
One of our teams is going “AI-all-in”, which results in running several agents, each with a clearly defined goal, like one agent generating code from a specification and another agent controlling the code-style of the previously generated code, sending the result back, when it doesn’t match the required style guides. This already has the character of an AI factory. This team has a high-quality output. I looked at the code and projects: following a clean architecture, lots of inline documentation and the highest test coverage of the whole company. This is only possible, because they started their projects from scratch.
While other teams, mostly those dealing with legacy code, stick to vibe-coding and using Cursor as a sparring when hunting for bugs. We would not benefit from the factory approach here, because the legacy code, which holds the core business income of the company, is working reliably. The risk of breaking business values is too high.
Regular Code Audits
Conducting regular audits of the codebase can help identify areas for improvement and ensure that quality remains a priority. This is still an open issue in our teams. We are doing regular reviews of the produced software running on a demo system. We also had internal code audit sessions, where the most experienced devs pointed out problematic code. Unfortunately we experienced bad habits in such audit sessions. Some developers felt like being blamed for writing bad code, resulting in demotivated people. Noticing that, I decided to not continue these kind of sessions. Instead I focused more on the training of the junior and mid-level devs. This brought the rate of problematic code down to an acceptable level. Also seniors have to learn about error culture, not blaming others but instead provide constructive feedback.
Finally
Honestly, it felt a bit like a failure to break with our code audit sessions. Although I know that the training of the juniors is the correct place to catch the root cause, it feels like losing a valuable asset by dropping the code audits. How would you deal with this situation?