Bad LLM Code, 4,000 Lines or Less

I didn’t get bad code because an LLM can’t write software. I got bad code because I gave it too much freedom.

I was working on a new feature for TrueMath this past week. I’d already written it before, but it lived behind an admin wall, and I wanted to elevate it for customers. The change came with some structural overhead, but at its core this was mostly a lift-and-shift exercise.

So I started a session with my trusty LLM, fed it a bunch of code, and said, “Here’s the basic structure, here’s what I did before—write this in the new style.”

The original code was about 2,000 lines. The LLM wrote 4,000 lines and still wasn’t done. And the code was spaghetti. There is no way I could have supported that long-term.

After a few days, I stopped writing code, started a new chat with the same LLM, and changed my instructions. This time I explicitly told it to lift and shift within the new structure and to write as little new code as possible.

It nailed the core in a single response. The rest took me a couple of days to finish up. The result? Clean, easy to update—and about 2,200 lines of code. Perfect.

One of the beautiful things about using an LLM to write software is that there’s a clear way to check whether what it did was correct. We run code through a compiler. We inspect outputs. We write automated tests and run them again and again.

Most things we ask LLMs to do don’t work like that.

We ask it to do research, and unless we double-check everything, we have no idea whether it made parts of it up. We ask it to draft legal documents without knowing whether it missed important laws in our jurisdiction.

We ask it to do math.

Did it use the right formulas?
Did it use the right data?
Did it make assumptions it never told us about?

Software gives us compilers, tests, and repeatability. Other domains—research, law, and especially math—don’t. Until they do, we’re still guessing. Just faster.

The New Software Development Stack: Humans + LLMs

How Software Development Really Works

I’ve been developing software for 40 years. I’m mostly self-taught and because of that (and my personality), I’ve spent a lot of time thinking about the process of developing software, not just the code.

While most of my software development has been in small companies I founded or personal side projects, my day job is as an engineering manager at a large multinational. This has given me a different vantage point, observing two completely different engineers: the senior engineer and the junior engineer.

The fundamental difference isn’t years of experience. It’s which parts of the process the engineer is expected to own. A junior engineer is expected to implement a solution and test that their solution works. A senior engineer is expected to understand the problem, break it down into possible solutions, choose the best one, implement and verify:

  1. What is the problem we are trying to solve?
  2. What are the options for solving the problem?
  3. Which of these options is the best choice for this situation?
  4. Implement
  5. Did I fix the problem without breaking other stuff?

Senior engineers are responsible for all five stages; junior engineers only steps 4 and 5.

How We Used to Learn

When I started writing software, there were really only two ways to learn how to write code: read a book (magazine, etc.) or ask someone who knows (including take a class). With the advent of the Internet both of these options expanded exponentially. “Read a book” became “search the Internet.” Stack Overflow became the de facto method for figuring out how to do something you haven’t done before, or reminding you how to do something you’ve done in the past.

Asking someone you know also expanded exponentially. I could read blog posts by other developers, ask on social media, take an online course, and of course our ability to connect with other developers expanded as well. At big companies, “ask someone who knows” is known as pair programming. Two (or more) developers sit in front of a screen and talk about the code in front of them.

Both of these are inefficient. While searching the Internet is easy to do, it is rare to find the exact solution to your exact problem. Most of the time we have to transform someone else’s solution to solve our problem.

Asking someone who knows is also inefficient. Finding someone who knows and with enough experience with your problem and willing to give you time is one thing. Getting that help at the exact time you need it is a whole other problem. In addition, “asking stupid questions” is something most people don’t want to do with another human for fear of being judged.

What LLMs Change

LLMs resolve many of these inefficiencies. They can answer questions about your specific code, and they can act like a pair programmer with deep knowledge of your language, framework, and tools. LLMs have the patience of Job, answering every question, discussing endlessly, and, yes, even being polite for the stupid queries. No shame necessary!

LLMs don’t just change how we write code. They change how we think about solving problems.

Hello World!

A lot of engineers recognize the potential power but don’t know how to start. Some try things but have a hard time fitting it into their development processes. This is really hard to do, actually. We each build certain processes, certain ways of working, and the act of inserting an LLM isn’t so much inserting as rototilling.

So we need practical ways of putting a toe in the water. I’m going to propose five.

Help Me Do This

This is the simplest. It requires only the most modest change in process. Instead of asking the Internet how to do something, ask the LLM. To do this effectively you need to take the time to fully describe what exactly you are trying to do. This is a different muscle. Instead of reading something on Stack Overflow and attempting to determine how that turns into a solution to your problem, you instead need to fully describe your problem. The reward, though, is it gives you a tailor-made solution. This targets stage 4: implement.

Write Tests

The reward for writing tests is incredible. I can change code with impunity and know that my tests will make sure I don’t screw up other code, assuming I wrote the tests well.

LLMs have gotten exceptionally good at writing tests. Even if you are someone who enjoys writing tests, use the LLM to make sure you have fully covered your code. Feed it your code and your test suite and ask it to tell you what tests you need for complete coverage. This targets stage 5: making sure you don’t break other code as you make changes.

Review My Code

LLMs have been trained on the world’s public (and sometimes non-public) sources. Every line of open source code, every line of documentation, every example ever created for every programming language. Furthermore, an LLM is always available and, as I pointed out above, never judgmental. What a perfect pair programmer! Next time you write some code, when you think you are done, feed it to the LLM and ask it what it thinks, what it would improve or change, ask it if you missed anything obvious (even if you know you didn’t). This targets stages 4 and 5: implementation and did I fix the problem.

Investigate the Best Solution

Senior engineers often perform the first three stages almost subconsciously. After a while the process is just baked into what they do. One of the issues, though, is sometimes senior engineers don’t stop long enough to listen and ask questions, ensuring they fully understand the problem before deciding on a solution. Junior engineers don’t generally perform these stages, but there is no faster way to become a senior engineer than to start. 

In this stage, instead of proclaiming your detailed solution to the problem, start with the problem itself. Explain to the LLM the problem you are trying to solve. Tell it not to write code; instead, ask it to discuss the best solution. Don’t share with it what you think the solution should be, but instead use your knowledge of the space to push at the edges of what it thinks is correct, redirecting it, expanding the scope, testing the edges of your own thinking in the process. For juniors, ask it what potential approaches we should take and why it picked the one it did. If the problem isn’t clear, ask it what else we might need to know in order to even decide on a path ahead. This targets stages 1, 2 and 3.

The Whole Enchilada

The real unlock happens when we start using LLMs for all five stages. While I wouldn’t start here — get comfortable with LLMs at your own pace and the level to which you are willing to let it participate — allowing an LLM to direct all five stages unlocks a speed and leverage that wasn’t possible before.

Start with Investigation as defined above. Align on a solution together, then align on an implementation plan. Work on each step of the plan together. As it changes code review what it is doing. I personally like to have it write in its own chat, leaving me to move it into my code base. That allows me to align it with my expectations.

I often go back and forth with it a few times, getting it to refine its approach. I test as I go manually. At each step I also work with it to write unit tests. And as I complete all implementation steps, I have it write integration tests, many of which I specifically define and direct it to write.

I’m still responsible for all the code. It’s my job to make sure that what outcome I wanted is what I actually got, and to ensure my test suite is complete. Same as it always was!

Metamorphosis

I’ve now built complex projects I never would have attempted on my own. I’ve been able to develop complex SQL queries when before I could really only accomplish simple ones. I’ve learned a ton about SOC compliance and row-level security for securing Postgres databases. I’ve built groundbreaking mathematical engines that drive a new way of doing math in the AI age. And I’ve done all this by myself. In the old days there were plenty of ideas I had and discarded because I did not have the skillset to build them myself.

Now? It’s incredibly liberating to realize that the only thing holding me back is my imagination.

How Apple’s App Store policies have contributed to the surveillance advertising industry

Image from The Verge

Apple’s goal is to sell more hardware, in particular sell more iPhones. That’s where the bulk of Apple’s revenue comes from even today. In order to sell more hardware, Apple wants to commoditize its complements and the biggest complement is apps. The irony is that this decision to destroy the value of software has led directly to their need to crack down on surveillance advertising. The law of unintended consequences at its finest.

How did we get here? This story starts all the way back in the mid-90s when Microsoft launched Windows 95 and Adobe, Pagemaker and Quark decided to support Windows.¹ For years before, Apple had paid huge co-marketing fees to keep these three mammoths of the software publishing business on Mac OS. But Apple’s market share was tiny and the digital publishing business, along with education, were the only two markets still using Macintosh computers. Apple was dying and everyone knew it.

Microsoft came along with Windows 95 and offered a lifeboat to these businesses. Port to Windows and you will be saved. They jumped at the chance.

Apple was the jilted lover and seems to have decided that if it ever survived that mess it would never put itself in that situation again, beholden to software companies. Instead, it would ensure that no one software company could ever get big enough and powerful enough to hurt Apple again.

25 years later and Apple of course is the largest company in the world, and has leveraged its dominant position in the technology industry to ensure no software company could ever challenge them again.

So Apple has been more than happy to see the impact of the App Store, and has been more than a willing participant in devaluing mobile software. Prices in the Palm OS and Windows Mobile days? Shareware titles were $20-30, commercial titles were $50 and more. My own products ranged in price from $10-$160 with our best products at $60. Average price was $37 net with most sales going through our website where we collected contact information and promoted upgrades.

In the iOS days? Prices started at $10 and quickly dropped from there. Within a year average prices were in the $1 range (before Apple took 30%) and most apps were free. The same apps I sold for $60+ on Palm and Windows Mobile were selling for $3.50 net on iOS with all sales going through Apple, no customer data, and no upgrades.

This helped sell lots of iPhones. All this cheap software made Apple’s hardware all the more valuable (commoditizing its complements). And Apple did little to help raise the price. No upgrade pricing, no trial versions, lax App Store policies. If your product failed, oh well for you as there were 100s of others willing to take your place.

The problem is all this cheap software required a business plan to make real money. If developers couldn’t make a living actually selling software, then they would make money selling you. They tracked your location, sharing information with the highest bidder. Because the income to stay in business had to come from somewhere and it wasn’t coming from selling software. Welcome to the surveillance advertising business.

(This is where I want to point out that I never did this and thus work full-time for someone else. Not every company did this and I’m in awe of those who made it through, survived this insanity, and still were able to build indie businesses.)

Even if these developers charged an up-front or subscription price, there was no mechanism to advertise and find out if someone purchased. While Apple could easily provide this capability, they don’t. So developers tracked consumers using the same mechanisms utilized in surveillance advertising. How will developers know if their advertising works now that tracking has been eliminated?

Now Apple cracks down on the surveillance advertising industry, blaming Facebook and others for the intrusion. Is Facebook to blame? Of course. But Apple is no innocent bystander in this insanity to track everything you do and everywhere you go in the name of selling you more crap.

Is Apple wrong to crack down on all this tracking? Of course not. It’s an abhorrent practice that should go away. But let’s not pretend that Apple’s App Store approach and policies hasn’t led us to this exact spot. The two go hand in hand.


¹ I know this story because a former Apple executive told it to me many many years ago.

Math doesn’t change

“Math doesn’t change,” the customer tells me as to why he doesn’t want to pay a subscription.

Yes, he is absolutely right. Math does not change. But iOS does. Constantly. And in order to keep that math working the app that that math works within does have to change. Otherwise it stops working.

We developed PowerOne Finance and PowerOne Scientific at the dawn of the iOS App Store. (Actually PowerOne pre-dates the App Store by more than a decade but that’s a different story.) At one point versions of PowerOne occupied 3 spots in the top 1000 apps with one of them occupying a slot in the top 100. As the App Store gold rush cooled, we couldn’t justify the investment in maintaining this old code and a one-time price so we started working on a new version we hoped would replace it and hoped we had a substantial number of customers who would move to the new version, propelling us up the charts, and driving enough subscribers to keep working full-time.

A few years ago we released PowerOne 6 and gave a great deal to existing customers to move over and, well, they didn’t.

At this point, the writing was on the wall. After 23 years of working on Infinity Softworks full-time, it was time to work for someone else. PowerOne was making enough money to keep it going, barely, but not enough to work even part-time.

Now I do a handful of releases per year, maintain the web servers, and am happy to work with a small group of customers who really enjoy the app. PowerOne Finance and PowerOne Scientific have continued to work all these years for anyone that already had a copy.

This week, however, Apple released iOS 14.5. For me, PowerOne Finance and PowerOne Scientific still worked. For some customers, though, it does not. A few customers reached out to me and asked what to do. I suggested they download PowerOne 6, explaining it was free with advanced features available for a subscription price of $19.99 per year.

Very politely, the customer responded that they don’t see a reason to pay a subscription because the “math doesn’t change.”

And he’s right. The math doesn’t change. But the platform on which the software runs is constantly changing. The server to sync templates across devices costs time and money. The earth in which that unchanging math lies is constantly shifting.

I’m really not upset. I’m sad but not upset.

I’m sad because the mobile landscape became hostile to running a small company with a niche product. I’m sad that I couldn’t figure out how to keep Infinity Softworks going as a full-time job after 23 years. I’m sad because I thought I had more customers that cared deeply for the product and would stick with me. And even that sadness isn’t the same as it was two years ago when I decided it was time to get a job. Then I was devastated. Now it is more of a lament.

While I’m not upset with this customer, it does strike me as ironic that the same person complaining that PowerOne Finance and PowerOne Scientific no longer works did so at the same time while lamenting a subscription price for software.

Because “the math doesn’t change.”

Texas Isn’t a Failure of Capitalism

The stories out of Texas are horrific. Rolling blackouts. Burst water pipes. Icicles dripping from ceiling fans. Electricity bills as high as $17,000. And to make it all worse, idiots all over Twitter claiming this is a failure of capitalism.

Capitalism is nothing more than an economic mechanism that allows markets to match supply with demand. In most countries around the world, the government tries to manage this. But governments are horrible at matching supply with demand. So here in the United States, we let the market figure that out.

A $17,000 electricity bill is actually unfettered capitalism working. It means that when supply is at its lowest and demand is at its highest, prices go up. But capitalism also assumes that consumers have choice and knowledge, and in many cases they do not, including this one.

The failure in Texas isn’t capitalism. The failure here is government.

Electricity, like lots of markets, is one where the company has way more knowledge and power than the average consumer. It is also a market where consumers have no choice.

The government of Texas, meanwhile, sells deregulation to unassuming consumers as a guise for “get the government off our back.” But all deregulation is doing in this instance is giving electric companies unfettered access to screw consumers.

The act of regulation is a critical role in capitalism. The act of regulation is the act of leveling the playing field between consumers and companies. But in Texas, and many other places across the country, government has failed to do its sworn duty to protect consumers. Instead, it has abdicated its role, throwing in with the companies, and screwing over those who voted.

You want a better country? Don’t throw out capitalism. Throw out the elected officials who aren’t working for you.