Why I Don't Use AI-Assisted Programming (Vibe Coding)
Why I Don’t Use AI-Assisted Programming (Vibe Coding)
TL;DR:
Author Jacob Harris explains several reasons why he, as an experienced developer, doesn’t use AI-assisted programming (vibe coding):
- I’m cheap — unwilling to pay a permanent subscription for “thinking,” uninstalled after using up free credits
- I’m old — referencing The Mythical Man-Month’s “no silver bullet” concept; LLMs address accidental complexity, but essential complexity (designing elegant systems) won’t disappear
- I like chaos — LLMs can’t understand the limits of their own perspective, can’t do metacognition; using DOGE’s misinterpretation of Social Security data as an example
- Resistance is a gift — coding difficulty signals architectural problems; AI removes resistance, causing people to skip deep thinking; and software development is collaborative — AI replaces human connection
- I care deeply — I love programming itself and don’t want to hand over the joy of creation to machines; LLMs don’t truly “care” about errors and consequences
- And a bunch of other reasons — dislike of AI’s sycophantic tone, enjoyment of unfinished hobby projects, and the ethical issues of the LLM industry
The article ends with a reflection: if the AI bubble bursts, he hopes we can rebuild software development as “a human practice of building a better world.”
Author: jacobharr.is (March 5, 2026)
A collection of various personal reasons why, as a developer, I’ve never taken to AI-assisted programming.
There’s been a lot of discussion online lately about AI-assisted programming (vibe coding) — all sorts of claims about how large language models (LLMs) will fundamentally transform software development. Each new model brings us closer to pure productivity, letting us ship software at the speed of thought, eliminating all friction and overhead from product development. Or so the story goes.
Maybe. I can only take your word for it. Because I don’t use AI-assisted programming.
If it works for you, great! I’m not here to debate the pros and cons of LLMs. It’s just never really clicked for me personally. This article is a summary of all the reasons I don’t use AI-assisted programming.
I’m Cheap
I’m not a purist. I tried those LLMs integrated into the IDE. They were useful for tasks that were simple enough to describe but annoying enough that I didn’t want to do them myself—like resizing a grid of square images. I could look up ImageMagick command-line arguments, but that was a perfect scenario for asking an AI. Later I tried an AI tool to analyze the code in my project, and a few other small tasks, and then everything suddenly ground to an awkward halt—the system told me my credits were used up, and if I wanted to continue I’d need to provide a credit card to buy more tokens.
You need to understand: Both sides of my family, going back several generations, are famously tight-fisted. For centuries, on both sides of the Atlantic, we’ve pinched every penny and hunted for bargains. For example, one of my distant ancestors died during King Philip’s War after risking his life to dash out of a fortified safe house to retrieve a chunk of cheese he’d left behind when they evacuated. So trust me: the idea of paying forever for a service that helps me “think” is so ridiculously absurd and terrifyingly insane that I wouldn’t even bother handing over my card. I closed my laptop, uninstalled the IDE, and even went back to Emacs. Then I realized I never missed it in the first place.
I’m Old
Being older has definitely helped me. I’ve been writing code for a long time, especially in an industry where someone with five years of experience can already be called a “senior engineer.” Sometimes, experience is the antidote to anxiety (except when the anxiety is about ageism—again, in an industry where five years qualifies as senior). The AI hype reminds me of the low-code/no-code breakthroughs from earlier years. I don’t doubt that AI is a useful tool for developers. I know it can help with certain tasks as a better tool. But these arguments always bring me back to the distinction between accidental complexity and essential complexity.
Fred Brooks was already old when I was a young programmer. As the project manager for the IBM System/360 mainframe family (and its operating system), he witnessed firsthand all the ways software projects can go wrong, back when those ways were still novel. He collected those observations into The Mythical Man-Month, a book that should still be required reading in software engineering courses. My copy is a later reprint that includes his subsequent paper “No Silver Bullet.” In that paper, Brooks examines the impact of new tools on developer productivity.
To think like a programmer, you have to understand that the real world is complex. Programming is best understood as imposing simplified representations—what we call abstractions—onto the messy reality, making it manageable by reducing complexity. This lets us generalize specific situations into layers that can be stacked. For example, the concrete act of spreading peanut butter on bread can be generalized into a spread(substance) method that accepts peanut butter or cream cheese as a parameter. Then we can use those spread methods to build higher-level functions like create_pbj() and so on.
Writing code in a modern high-level programming language is like standing on top of a pyramid of abstractions: one line of code can trigger millions of operations across multiple systems. Pretty exciting!
So what if we go further and abstract away the act of programming itself? That’s the dream of agentic AI: swarms of agents assigned tasks, implementing themselves without supervision. Sounds great! But this only addresses what Brooks called accidental complexity—the complexity inherent in the act of writing code itself. Since that paper was published, software development has made huge progress on this kind of complexity. We don’t write low-level machine code anymore; we use modern dynamic interpreted languages that get compiled down to assembly. Instead of writing quicksort from scratch, I can just call the sort method in the standard library. Instead of building an entire web app from zero, I can use an off-the-shelf framework. Want to rename or refactor code? The editor can do it. AI seems like a continuation of this trend—some editors have even replaced those predictable rename/refactor tools with unpredictable AI agents. Sure, it feels like rolling dice, but how often can things really go wrong?
Yet even as better tools have whittled away accidental complexity, essential complexity remains. The work of designing elegant, clear, maintainable abstractions and systems is still complex. And that complexity isn’t going away. This kind of work requires skill, experience, and hard-won wisdom from past system failures. And I’m not sure that the LLM’s fancy autocomplete approach really handles this kind of complexity well—it’s usually not something you can just autocomplete away. Maybe with careful prompting you can steer it toward a preferred solution, but at that point, the person doing the steering might as well design the solution themselves, because the LLM can’t explain why it chose a particular path. Essential complexity is often weird, rare, and messy. Maybe I’m wrong, and models are getting better at these messy cases too, but I find it usually requires a certain mindset and approach. Lucky for me, I enjoy the messy stuff.
I like chaos
So far I’ve been talking about how software abstracts processes, but we also use the reductionism of abstraction as a tool to understand the world. In the classic work Seeing Like a State, James Scott describes a core project of the post-Enlightenment era: making populations and property legible through abstraction and classification. To measure is to modify.
For example, a state might start viewing a forest not as a complex ecosystem, but simply as a percentage of timber usable for shipbuilding. This perspective allows the state to act on it—for instance, by replacing those forests with monoculture plantations of a single tree species. A forest gets abstracted into a system for growing ship masts.
This practice gave rise to bureaucracy and paper forms, which later evolved into web forms and databases. As programmers, we need to simplify the messy data of the world in order to act upon it. We expect dates to be precise. We expect names to be relatively simple. We expect data to be complete when entered and consistent over time. Every programmer, every system design, is a series of Procrustean choices about which aspects of reality to reflect in our systems and which to discard. I’m not criticizing; this approach is the only way to build systems that aren’t bogged down by endless exceptions—what we call “edge cases” because they’re supposed to be rare, peripheral paths.
But this process is so instinctive that we sometimes forget it’s also artificial, especially when it describes people. Forcing a gender field to only accept “Male” or “Female” doesn’t make gender binary. Our definitions of race are social constructs, constantly shifting. Our simplified models might give us insights (Autism diagnoses increased 300% in the last 20 years!), but they can’t capture the deeper factors behind those insights (It might just be due to changes in diagnostic criteria and increased screening). It’s important to step back and examine how any model is built and what types of knowledge it doesn’t capture. Every abstraction is also a form of concealment.
As a data journalist, I learned how to “interview” data and to maintain a high degree of rigor about all the ways the answers can be misleading. Paranoia is a data journalist’s best friend if you want to avoid the embarrassment of publishing a correction. You need to think not just about what the data says, but also everything it doesn’t include.
Unfortunately, this kind of metacognition is something LLMs can never do. The model is their reality. As Robin Sloan pointed out sharply in his powerful article “Are Language Models in Hell?”, AI models are built from simplified data and view the world in a simplified way. When you and I see a piece of text, we can perceive its context (e.g., text formatting, headlines, author bios, the website where the article is cited), while an LLM operates only in a pure world of letters (technically they receive subword tokens, which is why early models couldn’t count the number of ‘r’s in ‘strawberry’). Asking an LLM to recognize the limitations of its perspective on reality is like asking a goldfish how the water is.
While writing this section, I kept thinking about DOGE’s botched attempt to find fraud in the government’s Social Security Administration. In one case, DOGE looked at the SSA database and found over 9 million records with birth dates more than 120 years ago but no recorded death date. Elon Musk claimed the only explanation was that millions of people were fraudulently collecting benefits. He was wrong about both the cause and the severity of the issue. DOGE could have questioned the data quality, could have checked actual payments made, could have asked any expert at the SSA. But they didn’t; they took the data as gospel and jumped to the wrong conclusion—a pattern they repeated over and over (as in another example about payment fraud):
“These payments are valid,” wrote Acting Deputy Commissioner Sean Brune in a memo investigating one of the issues. (A Treasury spokesperson declined to comment.)
But according to people familiar with Mr. Russo’s remarks, he didn’t trust career civil servants. (Russo did not respond to requests for comment.) Instead, he insisted that Akash Bobba—a 21-year-old former Palantir intern who became one of DOGE’s lead programmers—conduct the analysis himself.
In their own crazy way, the DOGE team replicated for themselves the same working conditions that lead LLMs astray. They refused to consider explanations beyond the data. They didn’t communicate with anyone outside their bubble. They latched onto a simplistic explanation because it perfectly fit their worldview of incompetent government employees and widespread fraud.
This isn’t a rare situation. I myself am terrified of looking like an idiot, so I don’t want to outsource data analysis to an LLM. But of course many people will. I worry this problem will only get worse.
Resistance is a Gift
The appeal of LLM-driven development is that it’s supposed to eliminate resistance. Advocates paint a picture of development teams shipping dozens of features a day, using multiple teams of agents operating autonomously in increasingly bizarre topologies. I get it—software development can be tedious and frustrating. Being able to churn out code at a relatively insane pace and play with polished products instead of prototypes must be exciting.
But I need resistance.
When I first learn a new language or framework, I struggle with the resistance of even the most basic tasks. It’s uncomfortable! When faced with a new, unfamiliar codebase or data source, I need to set aside a few hours to carefully study it. I often find myself doing close reading, going file by file line by line until I understand their context and the choices the developer made. I know I could save time by having an LLM summarize the project for me, but I find that I need this process to really immerse myself in the code. I need it to understand not just what choices the developer made, but why they made them, and how those choices reflect the constraints and idioms of the language they were using. I learn from failure, and if an LLM takes that work away from me, I won’t truly understand what I’m doing.
Even when working in familiar languages and my own code, I still rely heavily on resistance as a signal. When writing code becomes difficult, it tells me that the current architecture is headed in the wrong direction—I should seriously consider redesigning to make future extensions easier. When that happens, I usually go for a long walk (or just go offline), giving my brain space to examine the problem from a new angle. It really works. I find these pauses so effective that I force myself to do them even when the path ahead seems clear. On large software projects, I wait until I’ve written an Architecture Decision Record (ADR) before I start coding a new feature, describing what I intend to do. These documents force me to capture my current thinking, my assumptions about the problem, and the consequences of my approach. Sometimes, they even make me realize I was too enamored with my initial intuition to see how it might go astray, and they’re always a great way to record “What were they thinking?” for anyone who inherits my work later.
The LLM-driven approach to resistance is to just code right through it without any rethinking. And the LLM will do it. It might produce working code. Performance metrics will be fine, tests will pass (especially if the tests were also written by an LLM). But it won’t know why that path was chosen. It doesn’t feel the resistance, and it can’t explain whether one architectural approach is cleaner than another. If the engineer writing the prompts lacks the insight to judge good designs from bad ones, they fall into a dynamic: repeatedly having the AI code through the resistance. This can lead to a tangle of strange abstractions, and the only design documentation the future team has is a markdown file of prompts used for the AI model years ago. Good luck reconstructing architectural decisions from that file!
It’s worth noting that most of the AI-assisted programming success stories I see come either from developers who are already experts in what they’re asking the LLM to build (and thus can guide its work), or from situations where the cost of failure is low. For everything else, we still need to figure out whether the rest of that owl is any good and safe to use.
I’d be remiss if I didn’t mention another thing that bothers me. When LLM advocates treat resistance as a problem, most of the LLM marketing I see in ads, live demos, and LinkedIn posts depicts a lone engineer (or maybe a small team) heroically using AI-assisted programming to crank out some app or website and ship it quickly—our velocity and KPIs are off the charts! But what the industry actually wants developers to do with LLMs is work, and the resistance there is usually established processes and practices that prevent defects or even conceptually unclear features from reaching production. Inevitably, the demand to prioritize LLM-driven speed is used against people themselves—other engineers, product or project managers, testers, compliance, or design team members. Because those roles are also seen as resistance. Who needs user research when we can use AI personas? Who needs design when we have AI tools to generate layouts? Who needs a project manager when we are the managers of our agent armies? What if we don’t need to wait for another developer to review our PR, and can just auto-merge code that passes tests and scans? What if we don’t need to spend any time talking to anyone during work hours, living purely in the realm of coding?
But software development is a collaborative process; every member of the team helps make the product better. Removing those roles or replacing them with the specter of LLM influence will certainly make the team faster, but that doesn’t mean the product they deliver will be better. And the process will definitely be lonelier.
I Care Deeply
Perhaps the simplest reason I don’t use LLMs is this: I love programming too much to hand it over to a machine. It’s like if I were an artist or musician—I wouldn’t turn to AI. Programming is how I express my creativity, and I’m not giving up that joy. Sure, it can be frustrating, but there’s a profound satisfaction in shaping a vague idea into a real system, especially when the implementation is elegant or the problem is interesting. Some nights I close my work laptop and open my personal one just to dive into some new fun thing I want to build. And when I’m building software professionally as part of a team? Even better. I love the collaboration, the shared process of shaping software—especially the way people step up and take ownership of problems. When a team is only responsible for the prompt and the LLM assistant does the actual work, I don’t think that dynamic is the same. Or when the LLM assistant replaces parts of the team.
Ownership matters. Over the past few decades, the roles I’ve worked in have cultivated a strong personal sense of responsibility. As a data journalist, a bug in code could mean an embarrassing correction or a devastating lawsuit. In civic tech, a mistake could mean a catastrophic failure in delivering services or benefits—whether to an entire vulnerable population or to a single person. I’m not saying I’ve never made mistakes, but I care deeply about getting things right because I care deeply about the mission of the work. I’ve been fortunate to work with people who also care, who want to do right by others. An LLM doesn’t care. Sure, it can convincingly fake caring, but it’s still just a simulacrum of a mind, stringing together words that are statistically likely to appear together. It won’t be haunted by its mistakes. It won’t try to do better because it has no inner awareness, let alone a conscience. It can never be held accountable, which means I can never shift my moral responsibility onto it.
When the LLM performs well, it’s a genius that’s going to replace all programmers. When the LLM deletes all your infrastructure or “lies” on tests, that’s your fault. After all, you just needed to craft your prompt and workflow the right way to coax the right output out of it. Oops, try again. And again. So much of the LLM advice I’ve read emphasizes that you must give all the necessary instructions, amendments, and sub-clauses upfront, or the system will do the wrong thing. This mindset is a major departure from agile programming, which emphasizes frequent course correction, feedback, and trusting your team to do the right thing. Instead, we seem to be regressing to a usage pattern resembling early 1950s computer time-sharing. Except here, the solitary programmer isn’t submitting a deck of punch cards—they’re submitting legal documents to be translated into programs.
I’m kidding; there’s no legal responsibility here. Given the similar demographics, it might not be surprising, but LLM vendors are repeating the same dynamic as Tesla. New features are pushed to users without safety testing, and oddly enough, LLM boosters—just like Tesla superfans—often blame themselves and others, saying users should have done better at writing prompts. I really don’t know how to think about this, but what bothers me is that tech is normalizing a kind of capitalism where more risk is shifted onto consumers because businesses and governments have abandoned their responsibility. We banned lawn darts after they killed a child, but chatbots pushing users toward death and psychosis are accepted as the price of AI innovation. Will things change when AI-assisted programming itself causes someone’s death due to a system failure—not an embarrassing death, but a real one—instead of just making someone look bad?
Coding has also been my solace during hard times. There’s research suggesting that playing Tetris is an effective way to prevent PTSD. The theory is that it works because activating the part of the brain responsible for arranging and rotating shapes blocks the formation of traumatic memories. I’m fortunate not to suffer from PTSD (and I don’t mean to make light of those who do), but I do resonate with the concept. Programming is like a complex puzzle, and sometimes that’s the comfort I need in dark times. As the example above hints, I’ve learned a lot about DOGE over the past year because I’ve been building and maintaining a system to track their rampage. Unlike a project with a deadline, this is a data assembly effort meant to provide clarity for an organization that prefers to stay discreet. It’s a meaningful exercise and a way for me to channel my despair into something I hope will be useful. It’s not the first time I’ve used code to soothe sadness, and it works because this is the work—if I only focused on the product, the process would lose its impact.
A Few More Dumb Reasons
This post is already way longer than I intended—especially since it started as just a handful of short posts on Bluesky. Before wrapping up, here are a few more quick reasons.
First, I absolutely hate the sycophantic tone that AI chatbots default to. As someone who grew up in an East Coast city, when a stranger is excessively friendly to me, I get deeply suspicious—it usually means they’re either about to scam me or preach at me. Reading LLM chat logs gives me the creeps. Yes, I know I can ask the LLM to use a completely different tone, but somehow that makes the whole idea feel even worse.
Like many developers, I have a folder full of unfinished hobby projects. Like that Spelling Bee clone I’ve been meaning to write—but in Clojurescript, so I can use the Blabrecs code to generate non-words and make it super frustrating. Well, I guess that’s only funny to me. You had to be there. From an LLM’s perspective, these are failure folders—I could definitely use an LLM to ship an app a day or complete any challenge I set my mind to. Yet the process matters far more than the product (there I go again!). Not every whim needs to become reality. Often I get more out of enjoying the brainstorming and learning enough to know I don’t need to follow through. It’s easy to forget that sometimes.
This post wasn’t originally going to wade into the ethics of using LLMs at work. Not because I don’t care, but because so many others have already written more powerfully than I can about the thorny impacts of this technology. And right now, at a moment when LLMs are being used to drop bombs on schools or generate child pornography on demand, I really don’t feel comfortable using them. And I can’t just leave that unmentioned. While there may be no ethical consumption under capitalism, I’m at least going to try. We can’t build a better world with tools that cause so many people suffering.
Strangely, no one seems more miserable than the LLM boosters themselves. If developers were taking their newfound productivity gains and finally living that four-hour work week the geeks pretended to worship a decade ago, I might be convinced. But perversely, many people in Silicon Valley seem to be outsourcing their work to AI agents and then using their new free time to do more work. Instead of spending that time relaxing, making art, or finding joy, they’re embracing the 996 grind and a hyper-quantified workplace that would make even Frederick Taylor turn pale. The LLM revolution might eventually come for me and take my job—but I’d rather not burn myself out first.
So What Now?
I don’t pretend to know the future. Maybe technology will advance to the point where I regret my lack of experience and familiarity. Or maybe it will stagnate, and the whole financial house of cards will come tumbling down. If the latter happens, I hope we can rebuild software development as a human-centered practice for building a better world, one line of code at a time.