Thursday, 16 October 2025

How I (vibe) code

 

1. Start with a clean codebase

Monkey see, monkey do.

I was initially dismissive of the "no broken windows" theory I came across in The Pragmatic Programmer, thinking it impractical to rigorously follow in a large codebase–but now I understand, and lean more towards the "How you do anything is how you do everything" philosophy.

In my experience, LLMs seem to be influenced much more by the surrounding code than anything you say in the prompt. They can output (at best) code at par on what they already see, and if you don't keep an eye on LLM contributions, overall code quality will only get worse over time.

If you find yourself prompting it to "write clean code, be DRY and reusable" or any other best practices–maybe you need to take a look at the rest of your codebase first. This applies to code layout, structure, documentation, type hints, and even tests. Going against the grain is inefficient–unless you're using Cursor, which manages to always output shite despite what it sees in the rest of the code (see below).

2. Fail fast

If the first attempt by the LLM is not ~80% there, I tend to discard it and start over.
Depending on how bad it did and how complicated the change is, it might be quicker to adjust your prompt or break down the problem than try and fix it iteratively.

3. Plan, then execute

For more complex changes or those that span a bigger surface area, have the LLM first plan out its approach in a markdown file–also useful for data-modeling activities.

I review and make changes (myself or using the LLM itself), then start a fresh session (with no memory of planning) asking it read the plan and start work.

4. Models gets worse, tools get better

Once upon a time, I swore by Aider, daily driving Gemini 2.5 pro before it went to shit in the 05-06 update, switched to Sonnet before making the jump to Claude Code (CC) altogether.

Tried OpenAI Codex again recently and the profanity in my chats is significantly lower than with CC. I have almost completely switched over to it, only ever using CC for when I need to have it fetch documentation from the web or for its IDE integration (in PyCharm).

It's amazing how bad CC is at not following simple instructions like "no local imports" and "no useless comments" unless you explicitly tell it to go read CLAUDE.md at the end of its run.

I've only ever used Cursor for code that I didn't care to read or run the next day, since combined with Sonnet it is especially bad at not following simple instructions (.e.g "no useless comments"). It is extremely infuriating to see code like this, despite having explicit instructions to prevent it from happening.

# Create account lookup dictionary and list of account IDs
account_lookup = {account.id: account for account in accounts}
account_ids = list(account_lookup.keys())

# Calculate date range for past 1 year ending with last day of previous month
today = timezone.now().date()

# Get the last day of the previous month
if today.month == 1:
end_date = date(today.year - 1, 12, 31)
else:
# Get last day of previous month
end_date = date(today.year, today.month, 1) - timedelta(days=1)

# Start date is one year before the end date (same day of month)
start_date = date(end_date.year - 1, end_date.month, 1)

I do not think the tool is meant for serious use, or if you have to directly deal with the code it generates.