As the first stage of my project, I had to ask myself, how do we typically reason about objects in the physical world that's different from how we reason about open source projects?
IMO, there's no example that showcases humans incredible abstract reasoning skills better than a computer. A computer is unfathomably complex; every mouse movement you perform gets processed by the device's driver, smoothened, stamped with information from the window server, which enters an application runloop queue, etc... until you actually see it happen on your screen. But for the vast majority of computer users, none of these needs to be understood by them; it's abstracted away to the simple action "move your finger on the trackpad, and the cursor on the display moves in the same direction". Ultimately, it's this idea of abstraction that lets humans build and reason about extremely complex things simply.
But this isn't a new idea in computer science. It can't be, obviously, since a computer employs the principle flawlessly. In fact, many open source projects have well thought-out schemes to split apart the codebase using folders:
An abstract codebase tree
An arbitrary app's tree on Github
This is the framework I'll be using to reason about large codebases: the dashed lines indicate the submodules that a codebase is split into. The example on the right is a real example of the folders that a typical app might be split into.
It's not perfectly clear what each folder does in the tree split on the right, but it's good enough. So where's the problem?
The problem arises with dependencies. Those sub modules that a codebase splits into depend on each other:
To make the point more concrete, let's take the example of an open source "Todo" app. A todo app is a common example project in javascript codebases that showcases a todo list where a user can add todos and mark them as complete. Here I've omitted the "mark complete" action.
Let's visualize how a user might explore this codebase's tree:
As I wrote on the image, the typical model of a user's exploration is DFS: depth first search. They explore each sub module all the way down first before "backtracking" and exploring other sub modules. This poses a lot of problems: at edge 4, they visit a completely separate branch of the tree that they don't understand yet. At edge 8, they visit a submodule that they saw a long time ago.
Luckily for humans, this simple todo app is easy enough to understand (at least for people familiar with codebases). We're smart enough that at edge 4, we can either infer the purpose of the submodule we're visiting or work our way back up from there to the main module to understand it. For edge 8, we probably have enough memory that we can just remember the sub module we're visiting.
Our brains are capable and powerful so we're able to make up for the deficiencies of the DFS exploration. But this doesn't scale. A todo app is easy and maybe will contain a hundred or so lines of code (LOC), but how will we handle hundreds of thousands of LOC?
If you're wondering about using BFS; it's not any better:
The problems from the previous exploration persist, and it has the added problem that its unnatural. After traversing edge 2, we jump to edge 3 on a completely different submodule which we now have keep in memory.
So how does our reasoning and intuition about computers work then? Isolating the "mouse move" example and removing unnecessary components, we as humans imagine the relationship as: