Comment by andai

8 hours ago

There's a metric I see omitted here which I call Rube Goldberg complexity.

I'm working on a multiplayer game, for which I haven't touched the code in a while. The other day I asked myself, "what happens when I press the shoot button?"

Well, it sends a message to the server, which instantiates a bullet, and broadcasts a bullet spawn message to all clients, which then simulate the bullet locally until they get a bullet died message. (And the simulation on both ends happens indirectly via global state and the bullet update function).

My actual analysis was like 3x longer than that because it focused on functions and variables, i.e. the chain of cause and effect: the Rube Goldberg machine.

I laughed when I realized that name was actually too charitable because at least in a Rube Goldberg machine, all the parts that interact are clearly visible and tend to be arranged in a logical sequence, whereas in my codebase it was kind of all over the place.

So that made me realize, a function is not really a sensible unit of analysis. They're too isolated. You want to try and understand a feature.

Also, I'm experimenting with organizing the code by feature, rather than by "responsibility." i.e. the netcode for the bullet should be in the bullet module, not the netcode module.

I forgot the punchline: Rube Goldberg is the right frame, but it's aspirational. You achieve Rube Goldberg when all the parts of a feature are clearly visible and arranged in a logical sequence...

> So that made me realize, a function is not really a sensible unit of analysis. They're too isolated. You want to try and understand a feature

The central element of code, IMO, is data. Where it comes from, where it goes to, and how it transforms. Whatever your paradigm (imperative, OOP, functional, relational,…), you need to design your data model first and its shapes through the stage of its lifecycle.

Once you isolate some part of the data, what I do next is to essentially create subsystems that will provide behavior. That’s the same idea as the entity component system. It’s also found in Clean Architecture (data is the core entities, uses cases are entities behavior, and everything else are subsystems) and DDD.

In your example, the netcode should be focused on state replication mechanisms, not the state itself. The state being replicated should be managed by the bullet entity, which is a core part of the game.

Such separation of mechanisms (subsytems) and policy (core logic) is a very good way of keeping cost of changes down.

I like this. And organising by feature has been something I've gravitated towards as a SWE, so I dig your thinking