Into
When I first started using large language models (LLMs) in my everyday programming work, I primarily saw them as tools that could help me work faster. Generating code, explaining unfamiliar APIs, writing tests, and exploring possible solutions all seemed like natural use cases.
Over time, I began delegating more complex tasks to these models. I started asking them to analyse existing code, explain implementation details, identify potential problems, and suggest architectural changes. This gradually changed how I approached development, but it also revealed a problem that I had initially underestimated:
LLMs can produce answers that are convincing, technically plausible, and sometimes simply wrong. More importantly, they can be wrong in ways that are not immediately obvious.
This made me reconsider not only how I use LLMs, but also how much responsibility I should allow them to assume in the software development process.
The Problem of Understanding code and Systems
When an LLM generates code that does not compile, the problem is usually easy to identify and it usually fixed by the model.
I have encountered situations where a model made a statement about the system or identified a potential issue, but closer examination showed that the statement was incorrect. What I found particularly interesting was** how the model reacted when challenged**. Asking about specific implementation details was often enough for it to recognise that its original interpretation was wrong.
I think of this as a form of false-positive analysis: the model reports behaviour, risks, or problems that are not actually present in the system. Such findings can be particularly misleading because they often sound reasonable and may lead developers to investigate or change code unnecessarily.
So, I ended up double-checking its statements, which was frustrating.
For this reason, I have become more careful about treating an LLM's analysis as a set of hypotheses rather than established findings.
- When a model claims that something happens in the code, I want to see exactly where and how it happens.
- When it identifies a problem, I want to understand the conditions under which that problem can occur.
The Problem is Not limited to Generating code
Another problem that gradually became apparent concerns the quality of a solution within the context of the entire system.
A model can generate code that works correctly on its own but does not fit well into the existing application.
It may introduce an unnecessary abstraction, add another layer of indirection, duplicate existing functionality, or suggest an architectural pattern that is inconsistent with the rest of the system.
For example, when asked to implement a particular feature, a model conclude that an additional abstraction is necessary. Such a suggestion might be entirely justified in another system, but not in current.
So, I ended up having to provide detailed guidance on how the functionality should fit into the existing architecture and integrate with the wider system. At times, felt like it would have been faster to write the code myself.
A similar problem occurs with refactoring. A model may suggest changes that simplify one part of the code while introducing inconsistencies or making other parts of the system more difficult to maintain.
What Does It Actually Mean to Verify a Result?
Initially, I associated verification of AI-generated code primarily with familiar programming practices: reviewing the implementation, running tests, checking compilation, and making sure the functionality worked.
However, this is not always sufficient.
So, I ended up double-checking the model's understanding of what needed to be done, before allowing it to proceed with the implementation.
This is especially important because a model can produce a solution that satisfies the criteria it has implicitly established for itself, even though those criteria may not be appropriate for the actual system.
The Shifting Responsibility
Over time, I noticed that my role in software development was changing. I spent less time writing code and more time defining tasks, providing context, reviewing suggestions, and verifying results.
At first, this seemed like a natural progression. If a model can write code, developers can spend more time on other aspects of development.
However, I think it is important to distinguish between delegating implementation and delegating technical decisions.
We can delegate writing code, exploring alternatives, creating tests, and identifying potential problems. But deciding what should be implemented, how the architecture should look, and which trade-offs to accept is a different matter.
These decisions require an understanding of the wider system and its constraints. A model can help evaluate different options, but the final decisions should remain with the developer.
Over time, I have come to see an LLM as an executor of clearly defined tasks and an advisor when exploring possible solutions, rather than an autonomous decision-maker.
Developing a Personal Approach
First, the task needs to be clearly defined in the context of the system. The model should understand how the requested functionality fits into the existing system.
Second, analysis and implementation should be separated.
Third, suggestions should not automatically become decisions. A model can propose several solutions and explain their advantages and disadvantages, but the choice must remain with the programmer.
Finally, verification must be independent.
Conclusion
LLMs have undoubtedly changed the way I work and allowed me to perform certain tasks more quickly and efficiently.
At the same time, they have reinforced the importance of
- understanding the broader system
- questioning proposed solutions
- taking responsibility for technical decisions.
Ultimately, we can delegate a considerable amount of work to a model, but we should not confuse its ability to produce convincing solutions with its ability to make good decisions.
A Note on Vibe Coding and Cheap Code
One thing worth mentioning is the growing discussion around vibe coding and the idea that code is becoming cheap.
I largely agree with this idea. Generating code is becoming easier and faster, and the cost of producing an implementation is decreasing.
However, code may be cheap, but technical decisions are not.
Writing a feature is only one part of software development. Deciding what needs to be built, how it should fit into the existing system, which trade-offs make sense, and how to maintain it over time are much harder problems.
The fact that a model can generate code quickly does not necessarily mean that the resulting solution is useful, maintainable, or appropriate for the system.
Vibe coding can make it easier to turn ideas into working implementations, but it does not eliminate the need to
- understand the system
- evaluate technical decisions, understand their consequences
- take responsibility for the solution
Top comments (0)