{"slug": "the-jokes-about-vibe-coding-aged-poorly", "title": "The jokes about vibe coding aged poorly", "summary": "A developer who previously predicted AI's rapid takeover of software engineering says the jokes about \"vibe coding\" have aged poorly, arguing that AI agents now handle most implementation work while programmers shift toward architecture, requirements, and review. To keep AI-generated code maintainable, the developer enforces strict guardrails through pre-commit hooks and CI, combining linters with automatic fixes and static code-smell analysis, and codifies engineering habits into agent rules such as AGENTS.md and SKILLS. The developer reports that projects built this way achieve higher code quality than manually written ones, and that without structural feedback, problems accumulate quickly across large task backlogs.", "body_md": "Three years ago, I wrote a short article after I managed to build a terminal Spotify client using nothing but Ctrl+C and Ctrl+V. Back then, in [What To Focus On When Programming With Gpt4?](https://maciej-ciemborowicz.eu/articles/what_to_focus_on_when_programming_with_gpt), I predicted what would happen in the near future, and so far those predictions have turned out to be fairly accurate. At the time, I often encountered skepticism and opinions that AI would never be able to program well because it would always make mistakes and hallucinate. Meanwhile, the way we build software has changed very quickly. Less and less code is written entirely by hand, while an increasing share of the work is being done by AI tools and agents. Knowing a specific programming language is also becoming less important. What matters much more is the ability to read and evaluate code, understand architecture, and clearly define what actually needs to be built. In that sense, AI has already taken over a large part of the work that programmers used to do manually. The role of the programmer itself is gradually shifting toward that of an architect, a product developer, and, put simply, someone who coordinates and supervises the work of AI agents.\n\nLater came the jokes about vibe coding. And those jokes have aged very badly. I am using the term fairly loosely here. I do not mean blindly accepting whatever an agent produces without reading the code or understanding the project. I mean building software where most of the implementation is produced by AI and the programmer focuses mainly on requirements, architecture, constraints, review, and feedback.\n\nSure, you can create an application this way that scales terribly and is difficult to maintain. But this can be addressed to a large extent. Practices around using AGENTS.md appeared, followed later by SKILLS. In [13 Programming Books as AI Agents Rules](https://github.com/ciembor/agent-rules-books), I described one way of turning software-engineering habits into rules for coding agents. These rules do not solve the problem by themselves, but they can prevent a large class of recurring problems. You can use them to instruct the agent how it should organize the code, which patterns it should use, what layers the project should have, and how much emphasis we want to put on bounded contexts, which, in my opinion, are currently crucial in larger software projects. By following these rules, I can create projects with much higher code quality than the ones I used to write manually, when I constantly had to balance implementation speed against code quality.\n\nFor a vibe-coded project to have high code quality, however, it is worth focusing on a few things that are rarely discussed. You can put very strong guardrails around an agent by imposing strict rules on how it works and forcing it into a certain engineering discipline. I do this through a demanding quality check, not only in CI, but already in a pre-commit hook.\n\nThe quality check consists of several steps. First, I run a linter. At this stage alone, you can get rid of not only ugly code, but also many unnecessarily long blocks and methods. This is very cheap in terms of CPU usage and execution time. I also run the linter with automatic fixes enabled, which takes some work away from the agent and makes the process deterministic.\n\nThe next step is checking for code smells. Not everything can be measured, and a code smell reported by a static analyzer does not automatically mean that the code is bad. But many suspicious patterns can be detected mechanically. If one appears, the agent has to at least reconsider the code and either refactor it or make a conscious decision that the warning is acceptable. This happens already at commit time, before these problems have a chance to accumulate.\n\nWithout this step, they can grow very quickly. I measured this while building a project from a backlog of 80 tasks without any structural feedback for the agent. The most common problem was `DuplicateMethodCall`. For example, AI can produce something like this:\n\n``` python\ndef report_summary(report)\n  \"Average: #{report.calculate_statistics.average}, median: #{report.calculate_statistics.median}\"\nend\n```\n\nThis performs the same potentially expensive operation twice. Depending on what the expression does, repeated calls like this can be merely unnecessary or actually expensive. It is often better to make the intermediate result explicit:\n\n``` python\ndef report_summary(report)\n  statistics = report.calculate_statistics\n\n  \"Average: #{statistics.average}, median: #{statistics.median}\"\nend\n```\n\nThe point is not that every duplicate method call is a serious problem. The useful part is that the agent gets immediate feedback about something worth looking at instead of repeating the same pattern throughout the project.\n\nThe second most common problem is `TooManyStatements`. The model tends to keep adding more steps to an existing method instead of stopping for a moment and thinking about whether the responsibilities should be split. So it can produce something like this:\n\n``` python\ndef process_order(order)\n  validate(order)\n  calculate_total(order)\n  apply_discount(order)\n  save_order(order)\n  send_email(order)\n  log_order(order)\n  update_metrics(order)\nend\n```\n\nDepending on the context, it may be clearer to separate these responsibilities:\n\n``` python\ndef process_order(order)\n  prepare_order(order)\n  persist_order(order)\n  finalize_order(order)\nend\n\ndef prepare_order(order)\n  validate(order)\n  calculate_total(order)\n  apply_discount(order)\nend\n\ndef persist_order(order)\n  save_order(order)\nend\n\ndef finalize_order(order)\n  send_email(order)\n  log_order(order)\n  update_metrics(order)\nend\n```\n\nAnd in a larger application it may make sense to extract separate objects:\n\n``` python\ndef process_order(order)\n  OrderPreparer.new(order).call\n  OrderRepository.new.save(order)\n  OrderFinalizer.new(order).call\nend\n```\n\nOf course, splitting a method only to satisfy a static analyzer does not automatically make the code better. It can just as easily create unnecessary indirection. The value of this check is that it forces the agent to stop and reconsider the structure instead of endlessly extending whatever method already happens to exist.\n\nThe list of problems it produces is much longer, though, and it is worth seeing what it looks like on a chart:\n\nEnforcing these checks at commit time therefore does not guarantee good code, but it prevents many simple structural problems from silently accumulating.\n\nThe next stage is test coverage. A large number of tests obviously does not give us confidence that the tests themselves are correct. The same is true for 100% coverage. I do not treat it as a quality metric, because coverage only tells us that the code was executed, not that the right behavior was actually verified. I treat it as a mechanical constraint that prevents the agent from adding completely untested paths without noticing. It also forces the agent to look at the tests whenever a refactoring changes behavior or makes some branch unreachable. For that reason, requiring 100% coverage is still a useful safeguard, especially when agents are modifying the code repeatedly.\n\nAt the end of the pre-commit process, all tests are run. Later, the process is repeated in CI, which gives us confidence that the same checks also pass outside the local environment. Which, of course, is nothing new.\n\nWhat is new is the economics of this feedback loop. A strict pre-commit hook can be annoying for a human developer, because every failure means stopping, going back to the code, fixing it, and trying again. An agent does not really care. It gets an error, changes the code, runs the check again, and repeats the process until it passes. This makes checks that used to feel overly strict much more practical.\n\nOutside of the quality check, most of my supervision is necessary in the context of code organization at the application structure level: layers, directories, files, boundaries, and their organization. SKILLS are obviously very helpful here as well. This is especially important when starting a project and when we have some idea of how large it is going to become. Based on that, we choose an architecture appropriate for the project, and if the project grows more than we initially expected, we supervise the refactoring.\n\nThis is also where I think the programmer's role is changing the most. Writing the implementation is becoming the cheap part. The more important part is defining the constraints under which the implementation is created: the architecture, boundaries, tests, static analysis, conventions, and feedback loops. Instead of describing every line of code, we increasingly describe the environment in which the code is allowed to exist.\n\nThe jokes about vibe coding aged poorly. Not because blindly generated code suddenly became good, but because we learned how to put enough engineering around the agent that blindly trusting it is no longer necessary.", "url": "https://wpnews.pro/news/the-jokes-about-vibe-coding-aged-poorly", "canonical_source": "https://dev.to/ciembor/the-jokes-about-vibe-coding-aged-poorly-oei", "published_at": "2026-09-22 19:03:56+00:00", "updated_at": "2026-09-22 19:23:02.273261+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "generative-ai", "mlops"], "entities": ["SKILLS", "GitHub", "Maciej Ciemborowicz"], "alternates": {"html": "https://wpnews.pro/news/the-jokes-about-vibe-coding-aged-poorly", "markdown": "https://wpnews.pro/news/the-jokes-about-vibe-coding-aged-poorly.md", "text": "https://wpnews.pro/news/the-jokes-about-vibe-coding-aged-poorly.txt", "jsonld": "https://wpnews.pro/news/the-jokes-about-vibe-coding-aged-poorly.jsonld"}}