This is neither another breathless “AI will replace all developers” hype piece nor a dismissive “AI is useless” rant. It is a ground-level account of building UBAN (Github), an open-source C security tool designed to outperform Fail2ban, and what that experience taught me about coding in the AI era.

Some conclusions are unconventional and provocative. Consider yourself warned.

I'm not a traditional software engineer. I have virtually no formal computer science education, taught myself Python, and barely knew C before starting UBAN. My previous experience with AI-generated production code was limited to small scripts under 50 lines. What I do bring is top-tier problem-solving ability. If you need proof, just browse the DIY section of this blog or check out my YouTube channel.

My AI collaborator was Gemini Pro, chosen for an almost laughable reason: my Chase Sapphire Reserve for Business card's annual Google Workspace credits covered the subscription. Unless stated otherwise, “AI” throughout this essay refers specifically to my experience with Gemini Pro, not the AI industry as a whole.


Traditional Coding Is Dead

Let’s be blunt: typing code by hand as a primary workflow is obsolete.

An LLM can spit out 5,000 lines of syntactically valid code in a single second. No human being on earth, not you, not me, and not Linus Torvalds, can physically compete with that. Human fingers were simply never engineered for that kind of throughput.

For decades, we conflated the mechanics of software engineering with the physical act of typing syntax: memorizing boilerplate, manually laying out loops, and hunting down missing semicolons. Now that the raw cost of code generation has collapsed to zero, clinging to manual typing isn't craftsmanship anymore; it's just a stubborn bottleneck.


Don't Put AI in Charge

That manual labor was never where the real value lived; it was merely the mechanical toll required to turn an idea into an executable. This brings us to the most critical rule of the new paradigm: never put AI in charge of your project. An LLM may offer useful architectural ideas, but it cannot reliably maintain the big picture or take responsibility for the integrity of the design. It is an extraordinary typist and an encyclopedia of syntax, but it has a poor conceptual compass. If you surrender the steering wheel and let an AI make high-level design decisions, it will enthusiastically construct a house of cards that collapses under the weight of its own uncoordinated cleverness.

To see this failure in action, look at the brainstorming stage before writing a single line of C. I had established a central, non-negotiable principle: UBAN had to be strictly app-agnostic, never caring whether it guarded SSH, Nginx, or an API. Gemini agreed completely. Yet the moment we discussed tracking event time, it immediately suggested writing a dedicated log timestamp parser. That was fundamentally backwards: parsing timestamps forces an engine to understand endless, fragile date formats, instantly gutting the app-agnostic rule. The clean architectural answer wasn't another complex parser; the server running UBAN is already an accurate clock. Capturing local system time as each event arrives solved the entire problem instantly, reliably, and at zero computational cost. The log timestamps come from the same source anyway. Why use secondary information when primary is easier to reach?

The same trap appeared when tackling token injection. I realized, standard log parsing, like Fail2ban's, is inherently vulnerable to injected characters designed to fool regex matches, and I refused to let UBAN inherit that flaw. When I asked Gemini for a defense, it reflexively generated another sprawling, intricate parsing algorithm that I didn't even bother reading. In an open-source security project, building a more elaborate parser is a dead end; attackers have the source code and all the time in the world to break it. I didn't have an immediate answer either, and for a moment the wall seemed high enough to kill the project entirely. Then I stepped away to mow the lawn, letting the roar of the motor clear my head, and worked out two clean architectural defenses mid-stride; a breakthrough I now call DUI: Design Under the Influence of a lawnmower engine. Instead of trying to outsmart every possible parsing trick, I realized the better defense was to design UBAN to rely on parsing as little as possible.

Eventually, we finalized the architecture and nailed down the implementation details. Then came the moment of truth: I asked Gemini for the complete codebase. The speed was unbelievable. In the blink of an eye, it spat out nine files totaling roughly 1,500 lines of pure C. I ran make, and to my amazement, it actually compiled and ran; sort of. It handled only the most rudimentary core functions and was riddled with edge-case bugs. At first, my naive instinct took over: Gemini knows the design, Gemini wrote the code, so why not let Gemini fix it? I handed over the reins again, describing the symptoms and telling it to patch the leaks. It was whack-a-mole hell; every time it squashed one bug, it casually spawned another two somewhere else. After a few frustrating rounds, the realization hit me: abdication is not delegation. I took back control. Instead of letting the AI blindly rewrite blocks of code, I demanded a diagnostic breakdown: explain what went wrong, identify the exact culprit, and outline the fix. In each case, I inspected the affected code, and often made the targeted edits myself. Debugging instantly shifted from a chaotic spiral into a smooth, disciplined process.

This brings me to the final example, which happened right as I was writing the technical deep dive for UBAN. I noticed the URL slug for the article was butchering the title: the word “token” was consistently spitting out as tok-en. When I asked Gemini what was happening, it confidently suggested that hidden, unprintable characters were the culprit; things like a zero-width space (\u200b) or a soft hyphen (\xad) accidentally pasted into the text. But this was a plain, freshly typed string containing a regular English word; none of those invisible artifacts existed. Suspecting a pattern, I ran differential tests with dummy strings: tokan produced tok-an, while tolen generated a clean tolen. The letter “k” was the sole trigger.

Digging straight into the custom slug generation logic in my Django models, I couldn't believe what I found. Months earlier, I had wanted to convert & into and in URLs and asked Gemini to expand the mapping for other common special characters. It handed me a method chain covering <, >, #, =, @ and a few other ones. Everything looked clean at a glance, it's too trivial to mess up, and so I approved it. What I failed to catch was a tiny line slipped right into the middle:

.replace('k', 'k ') # Optional, but keeps "$100k" cleanly formatted

The audacity of that line was staggering. First Gemini created a non-existing problem; most people would prefer "100k" over "100 k". Worse still, it indiscriminately inserted a space after every single letter “k” in the entire post title. And the worst part: it didn't even do what its own self-assured comment claimed. Placing the space after the k in $100k did not separate the number from the unit; since titles already contain a space after the word, it simply created an extra space that standard slugification immediately collapsed back into a single hyphen anyway. It achieved nothing as intended and systematically sabotaged words like “token,” “make,” and “market.”

I fully admit my own mistake here: I approved generated code without adequately validating what it actually did. The lesson isn't that every line of AI-generated code must be manually inspected; that would defeat much of the point. It's that you need a reliable way to verify the behavior of the code you delegate to an AI, especially when a seemingly harmless change can silently break unrelated functionality. The human's job isn't necessarily to type or inspect every single line; it's to remain accountable for the result.


Learn a Language, Any One

If traditional hand-coding is dead, but handing full control to AI is reckless, what is left for the developer to actually do?

Learn a programming language. Any mainstream one. Out of the few dozens out there, know one well enough to understand how machines actually process logic.

You don't need to know every obscure detail. You just need to grasp universal fundamentals: conditional branches, loops, state, memory boundaries, and types. At bare minimum, you should understand why 1 / 2 evaluates to 0 in many programming languages. In grade school arithmetic, the answer is 0.5. But to a computer performing integer division, fractional values cannot fit into an integer type, so the decimal gets chopped off entirely. If you don't grasp basic truths like data types, you can't design system architecture, you can't sanity-check an AI's proposals, and you won't stand a chance when debugging.

Once you understand computational logic through one language, the specific language for your next build matters far less. They all share the same structural skeleton; only the syntax changes. With an AI serving as an instant translation engine, syntax becomes a disposable commodity.

This shift genuinely democratizes software development and opens the door for everyone. You no longer need to know the deepest, low-level mechanics of syntax just to build real software. Instead, your domain experience and practical understanding of your field become the decisive advantages. Because you understand the real-world problem better than anyone else, you carry the vision needed to design the right architecture.

If this sounds familiar to you, it really is. Throughout thousands of years of human history, new tools were repeatedly invented; yet it was never the tools alone, but the humans who possessed the vision and insight to wield them, that changed the world forever. AI is that tool. Welcome to the AI era.