Programming with AI
ENRU
13.1

How to break an idea into parts

three questions and one rule about direction

What it is

Architecture sounds grand, but in practice it is the answer to one question: what depends on what. Not "which folders to create" — the folders will appear later, by themselves — but who is allowed to touch whom.

While the project is small this feels unnecessary: one file, everything works. The problem isn't size but that without an answer to that question every new AI answer puts code wherever it was convenient at that moment. A month later the project has three places that talk to the database, two different ways of reading settings, and a two-hundred-line function nobody wants to touch.

What it looks like in code

Three questions for any idea

Before asking for code, answer three questions. Not the AI — yourself.

1. What is stored? List the things: a city, a user, a note, an order, a subscription. Those are nouns, and tables and data models come out of them.

2. What happens? List the actions: add a city, refresh the weather, send a notification. Those are verbs, and functions come out of them.

3. What is visible from outside? A screen, a page, a bot command, an API address. That is what people talk to the program through.

Three half-page lists, and the project already has a skeleton. The technique works for a two-hundred-line bot and for a twenty-screen application alike.

The rule about direction

Those three lists are then laid out in layers, and between the layers there is a single rule worth remembering:

Diagram from the book — to be redrawn for the web
The arrows only go down. The data layer doesn't know there is a screen above it — which is why it can be checked on its own.

The upper layer knows about the lower one. The lower one doesn't know about the upper. The screen talks to the logic, the logic to the data. Never the other way.

That isn't theory or a matter of taste: breaking the rule is visible at once, because the program stops starting. Here are two files that know about each other:

# app/models.py
from app.services import fetch_weather

class City:
    def refresh(self):
        self.temp = fetch_weather(self.name)
# app/services.py
from app.models import City

def all_cities():
    return [City("Oslo"), City("Bergen")]

Running it:

measured
ImportError: cannot import name 'fetch_weather' from partially initialized
module 'app.services' (most likely due to a circular import)

A circular import is a message saying the layers have been mixed up. The data climbed upwards, into the logic. The cure isn't a clever import inside a function (that is what the internet advises, and what an AI often proposes) but reversing the dependency: the model knows nothing, the service knows about the model.

# app/models.py — imports nothing
class City:
    def __init__(self, name, temp=None):
        self.name = name
        self.temp = temp
# app/services.py — knows about the model
from app.models import City

def all_cities():
    return [City(n, fetch_weather(n)) for n in ("Oslo", "Bergen")]
measured
[City(Oslo, 4), City(Bergen, 4)]

The smaller the better — up to a point

It makes sense to split down to the level where each piece can be explained in one phrase. "Takes a city, returns a temperature." "Holds the user's list of cities." As soon as the explanation needs the word "and" three times, it is time to split further.

There is a limit in the other direction too: twenty files of ten lines isn't architecture, it is scatter. The sign that you overdid it: understanding one action means opening five files in a row.

What to tell the AI

The main mistake is asking for the whole application. Instead, get the breakdown as text, before any code:

Prompt
The idea: <describe it in two or three sentences>.
Don't write code. Break it into parts:
1. What is stored — a list of entities with their fields.
2. What happens — a list of actions.
3. What's visible from outside — screens or addresses.
For each action, say which entities it touches.

The answer to such a request reads in a minute and is corrected in two. Code that would have to be rewritten takes an evening.

Prompt
Here is my breakdown: <the three lists>.
Propose a layer structure: what depends on what.
Draw with arrows who imports whom.
If an arrow comes out pointing both ways, say so.
Prompt
I got an ImportError mentioning a circular import.
Don't propose moving the import inside a function.
Explain which of the two modules shouldn't know about the
other, and how to reverse the dependency.

What to look for

The application in one piece. You asked for "make an app" and got one four-hundred-line file with everything mixed together.

Circular imports. Reverse the dependency rather than hiding the import.

A database query right in a screen's handler. The interface reached over the logic's head straight into the data. It works — until the first change in the database.

The same action in three places. Three places where the temperature is formatted, and all three have to be remembered.

A file called utils that keeps growing. Everything that found no home gets put there. That isn't a layer, it is a cupboard to sweep things into.

The AI did the breakdown and you didn't read it. The project's structure is worth understanding whole — it is the one part that can't be delegated (article 1.1).

Cheat sheet

  • Architecture is the answer to "what depends on what".

  • Three lists before any code: what is stored, what happens, what's visible from outside.

  • Three layers: interface → logic → data. The arrows only go down.

  • A circular import is a signal of mixed-up layers, not a Python error.

  • A piece of the right size is explained in one phrase without the word "and".

  • Ask the AI for the breakdown as text before code — text is cheaper to fix.

  • A utils folder is where you sweep what you didn't sort out.