Quicker, Easier, More Seductive
Album to listen to while reading: Deftones — Private Music. I wish I could maintain my mojo as long as these guys have. Damn.
It feels like only yesterday that we could still afford the time and vanity to argue about tabs and spaces. An elegant argument, for a more civilised age. This is my read of the LLM disruption, of how it's being sold to us, and a few tips on how to not lose your head in all of this. There will be Star Wars references throughout. Not out of love — I'm a casual fan at best — but because I assume its fandom overlaps almost perfectly with coding purists.

Nobody puts nuance in the corner
(Threw a bit of Dirty Dancing in there so that it's not just Star Wars.)The current AI discourse conspicuously lacks the appropriate nuance. Hype, a financially incentivised media ecosystem, and a target audience that cannot verify the claims have everything to do with it. Dealing in absolutes is only for the Sith, which is why I offer the following three nuances that are often missing from both sides' echo chambers.
First, AI is a blanket term that has all but lost its technical meaning. It captures a large area of Computer Science research of which (large) language models are just a slice. Making absolutist claims about AI (in either direction) does a disservice to countless people and companies who have been contributing to this field for far longer than LLMs have been around. Use the right terminology — Large Language Models — to describe most of what's marketed as AI these days. There are many branches of Artificial Intelligence that are every bit as useful but are getting squeezed out. Classical ML on tabular data is the obvious example: gradient-boosted trees still comfortably beat LLMs at structured prediction. I hit this recently on a customer churn model, where reaching for a language model would have been the wrong move.
Second, to bluntly declare that LLMs are useless is self-defeating: the technology has demonstrably found its niche in coding and mathematics. That's no accident: both fields are mostly textual and their entire corpus of knowledge is available on the internet. The question of whether it's a bubble is moot. The word itself has a negative connotation, probably because it's associated with economic depressions, but a bubble is just future infrastructure paid for by optimists: the dot-com era left us the internet and the fibre networks we still use today. What isn't moot: the fearmongering and mischaracterisation (by anthropomorphisation) of LLMs make for a strange way to sell the product. Imagine how well a lab that doesn't threaten its biggest user base would do. All you have to do is build a half-decent coding harness and be a little bit nicer to entry-level white-collar workers. Multi-billion-dollar idea right here.
Lastly, LLMs produce code that isn't perfect, but code was never perfect, nor does it have to be to do its job. In some areas the argument is already over: LLM-produced web security code is better than something produced by someone genuinely new to the field, or by a team that never cared and just left it to the security team's black-box testing. Hiring InfoSec professionals for every project never actually happened in practice, and security teams are always vastly outnumbered within large tech organisations. Poorly secured code made it to production all the time; it was just too tedious to find and/or exploit (until now). On the other end of the spectrum exist internal scripts and some frontend which don't have to be perfect to the semicolon to solve a business problem. The business graveyard is full of immaculate codebases.
Problem 1: Illusion of plausibility
Bad or actively dangerous code used to broadcast itself from a mile away.
public class utils {
public static String getdata(String s, String pilot, int n, boolean flag) {
String str1 = "";
if (s == "TIE") {
str1 = str1 + "<b>" + pilot + "</b> flies a TIE Fighter";
str1 = str1 + " and has " + n + " confirmed kills";
if (flag == true) { str1 = str1 + " (ACE)"; }
} else if (s == "XWING") {
str1 = str1 + "<b>" + pilot + "</b> flies an X-Wing";
str1 = str1 + " and has " + n + " confirmed kills";
if (flag == true) { str1 = str1 + " (ACE)"; }
} else {
// TODO: Falcon isn't really a fighter, ask Kenobi before release
return null;
}
return str1;
}
}Code like this used to be written by people who did not have to maintain or even acknowledge it afterwards. Coding harnesses almost never do this, simply because the models were trained on a corpus that largely avoided shameful code. That is open source, which we used to pride ourselves on contributing to. A pull request that appears to have had a lot of (human) thought put into it primes a reviewer to apply less scrutiny. It also masks the author's own proficiency and biases, making it hard for an experienced teammate to review with the right context.
The reluctance to review the code of someone deemed more experienced or senior long predates LLMs. I've spent a good chunk of time prodding people to do it anyway, because a fresh pair of eyes and simpler questions uncover quite a lot. That prodding is now a losing game: everyone's code looks senior.
I cannot in good conscience suggest that you start writing subpar code so that it's more recognisable, but I can suggest this: state plainly how much (or little) thought and LLM work went into a contribution.
Problem 2: Interchangeability with traditional coding
Have you ever asked a coding harness to make a small change such as a simple rename or extract refactor, something that an IDE would easily do for you two years ago? It's what I'd brand as LLM-paranoia. The vicious circle: you use LLMs. You pay in excessive code sprawl. You are forced to use LLMs to make changes due to the perception that they're less likely to miss a hidden nuance or gotcha that isn't properly tested for. LLMs have an irksome habit of inserting overly verbose code in general, and comments within it in particular.
/**
* Diverts auxiliary power to the deflector shields.
*
* IMPORTANT: Shield draw must never exceed available reactor output, or the
* reactor will trip and take life support with it. Callers are responsible for
* checking `reactor.availableOutput()` before invoking this function.
*
* NOTE: Assumes the shield generator has already been primed. Calling this on
* a cold generator is a safe no-op and will return early without side effects.
*
* @param amount - The amount of power to divert, in gigawatts
* @returns void
*/
export function divertPower(amount: number): void {
// Log the requested amount for observability purposes
console.log(`Diverting ${amount}GW to deflector shields`);
...
}This snippet should look instantly familiar. It should also be apparent why an IDE rename will make the code and its comments diverge faster than property values on Alderaan. Using LLMs for trivial refactors would, ironically, put our token bills in the same column as the PDF to slide conversions. Hunt down LLM code that introduces hidden assumptions and tribal knowledge via comments alone. Better yet, I instruct mine to leave out comments altogether unless absolutely necessary for a human reader.
Problem 3: Productivity bottleneck now shifted arbitrarily
Building software used to be the slowest link in the chain, which was predictable, stable, and known to everyone. Product owners and designers would understand what's needed and queue up the work. Engineers picked it up and, while providing their own input, built in line with the requirements. The perfect ratio of a few engineers to one or two product professionals wasn't accidental; it allowed building to continue while the feedback trickled down from the appropriate channels and user research was distilled.
When every role is encouraged to "build", ownership begins to dissolve and the "bottleneck" (which is what makes a bottle pourable) moves to an arbitrary function in the organisation. A product manager with a coding harness and the best of intentions can now cause a lot of thrashing in the engineering roadmap. Internal-facing platform or SRE teams, who arguably benefit less from advancements in LLM coding, now have to play catch-up with the feature delivery teams. The worst part is that there's no telling which team this pressure migrates to and what inefficiencies will get flushed out.
I keep in touch with many of the people I've worked with. When engineers tell me that someone picked up vibe coding who shouldn't have, it has never once been the prelude to a happy story.
Code care factor via a Star Wars analogy
(Yes, we're doing this.)I recommend partitioning your codebase into a few "care factor" categories and enforcing them at the CI/CD level (perhaps like GitHub's CODEOWNERS), not just by convention or discipline. Feel free to drop the Star Wars naming to land on a more neutral gradation.
Death Star's exhaust port
The foundations on which software stands or falls: auth, money, public APIs, or anything with a blast radius bigger than itself. No line goes unreviewed by a human with subject matter expertise.
@Transactional
public void reassignShield(long fromId, long toId) {
// Always lower id first, otherwise risk a deadlock
ports.lock(Math.min(fromId, toId));
ports.lock(Math.max(fromId, toId));
// These may throw unchecked exceptions - roll back, safer than proceeding
ports.getReferenceById(fromId).releaseShield();
ports.getReferenceById(toId).assignShield();
long exposed = ports.countByShieldedFalse();
if (exposed > 0) {
// A checked exception here would commit the exposure and return normally
throw new ExposedPortException(exposed + " exhaust port(s) unshielded");
}
}Nobody is dictating that these be written by hand, but slipping LLM-produced critical code directly into production without oversight is a process failure. While I realise that this statement is somewhat self-serving, this category alone should keep most vibe-coded apps that handle money or personal information out of app stores and the like.
Millennium Falcon
The vast majority of most codebases: business logic built on solid code-enforced invariants, non-critical frontend, internal tooling, infrastructure, etc. Review it in proportion to how much you'd mind being wrong.
const LABEL: Record<ShieldState, string> = {
up: "Deflectors holding",
charging: "Rerouting auxiliary power",
down: "Deflectors offline",
};
export function ShieldPanel({ state, onDivert }: Props) {
return (
<section>
<h3>Deflector shields — {LABEL[state]}</h3>
<button onClick={onDivert} disabled={state === "charging"}>
Divert power
</button>
</section>
);
}This is where you will spend the majority of your time. If instead you are finding yourself writing mostly critical plumbing (the first category), check whether you are reinventing the wheel and are worse off for it. For run-of-the-mill code, a codebase that has its invariants securely coded in (usually in the form of SQL constraints, backend checks, invariant tests, and language features such as exhaustive enums) can absorb mostly LLM-produced code. It's a big ask of most codebases, so a good amount of human scrutiny is still needed — it can range from a quick skim with an automated review to a careful read of the thorny parts by multiple engineers.
TIE fighter
Instantly verifiable, throwaway code. Review the results, not the code.
public class BuffWeapons {
public static void main(String[] args) throws IOException {
for (Path p : Files.newDirectoryStream(Path.of("."), "*.loadout")) {
byte[] b = Files.readAllBytes(p);
b[0x1C] = (byte) 0xFF; // Efficiency byte, was 0x40
Files.write(p, b);
System.out.println("Patched " + p.getFileName());
// Leaking some file handles? Ain't nobody got time for that.
}
}
}This is what LLMs are born for: automating repetitive tasks using throwaway code. Regardless of whether you are already a bash scripting guru, there's nothing more cathartic than seeing an LLM do in a few moments what used to take hours. The criteria for code that lives here should be very narrow: its results have to be verifiable by a human's eye and it shouldn't survive longer than it needs to fulfil its purpose.
Recent example: converting test data between layers of abstraction — from direct SQL inserts, to seeding via the REST API with a valid user cookie, to automating the UI interactions, each required to produce the exact same end state. About 10 minutes total, most of them spent checking the three end states matched.
Slower, harder, less seductive
None of the following advice is new. It was reasonable five years ago too, which should tell us something.
- No LLMs, no exceptions — Git, direct commands on a production database or server, access to the file system (outside of the codebase itself), etc. Please, spare yourself the indignity of clobbering data to fix an integration test.
- No offloading of architectural decisions to a probabilistic roulette — Why? "That's a great question, thanks for pushing back on this!" LLMs are post-trained to be agreeable and supportive. No amount of prompting can force a harness to correctly weigh architectural pros and cons and push back on an incorrect problem statement. As before, brainstorming or self-education via LLMs is a boon, but long-term decisions deserve more than a prompt. If in doubt, ask the LLM for prior art and dissect manually, preferably as a team. Darth Vader's officers never pushed back either. As a result, they shipped a company-ending vulnerability.
- Multitasking is an attention killer, resist it — LLMs make it easier than ever to set-and-forget. While it may be fine for throwaway tasks, avoid having more than one involved "thread of thought" running at the same time. I recommend no more than two Git worktrees: one for your main line of work, one for throwaway code or scripting (the third care factor category).
- Defensive programming is now table stakes — whereas before discipline and engineering culture could uphold correctness and consistency, it's a Wookiee's breakfast with LLMs. Rules and invariants have to be coded in as runtime checks (e.g. SQL constraints, explicit backend assertions) or as tests (e.g. database-state integration tests, unit tests checking SQL output verbatim) — never comments or tribal knowledge. Concise and actionable error messages (even when not user-facing) are a great way to cut down on the token crunching when an LLM agent inevitably debugs something.
- Bots reviewing bots is a dead end, stop it — either employ sensible human code review practices or commit to full end-to-end LLM delivery. Don't straddle both and let your engineers become apathetic and burnt-out meat proxies. Proper human-in-the-loop conventions have never changed: small pull requests, concise PR descriptions, expressive Git commit messages, and separation into logical domains are still necessary for a human to meaningfully review a contribution.
- Relinquish control of the less critical parts — you must concede that more code now flows through your line of sight. Human undivided attention can only stretch so far, so it's time to relax some of the pedantry and ritual around the parts that have a smaller blast radius. This isn't carelessness but rather the appropriate allocation of limited mindshare. Let the Wookiee win.
The future
(Hopefully we fare better than what's happened to Star Wars since the original trilogy.)Where all of this goes is hard to predict, not because the tech is moving fast but because so much of its marketing happens in bad faith. Threats of imminent job destruction or the extinction of the human race foreclose any chance of a rational conversation. So does lobbying governments by hyperinflating national security and economic risks. Encouragingly, the frontier labs are changing their tune. Engaging in good faith — and this is on the labs, not us — means being honest about the tech's limitations rather than overstating its potential, targeting fields that unequivocally improve society's welfare (e.g. alleviating the effects of ageing populations, health research), and growing adoption organically.
What's next for software specifically? No one knows, but despite the morose tone of the post, I'd like readers to leave with a sense of optimism.
Our industry has already shown it can get this right. After months of debate, the Linux kernel merged a policy on AI coding assistants instead of banning them outright. Contributions carry an Assisted-by: tag naming the model that helped, but an agent may never add Signed-off-by: — only a human can legally certify the Developer Certificate of Origin.
What I do know is that human ownership and accountability will have to stay because attributing them to LLMs is and always will be inconceivable. A human capable and willing to take those on will always have a place.