DEV Community

Cover image for The Two Lists That Define Every Software Project
Tuntufye Mwakalasya
Tuntufye Mwakalasya

Posted on

The Two Lists That Define Every Software Project

If you’ve ever been near a software developer, you’ve probably heard a frustrated groan followed by the classic phrase: "But it worked on my machine!"

This, and a million other frustrations like File Not Found or Symbol Not Found, often boil down to one of the most misunderstood parts of software engineering. It’s not a bug in the code, but a problem with the lists.

The problem is that a computer is not a mind reader. It’s an incredibly fast, precise, and literal-minded robot. To get it to build your software, you have to give it two separate things: a Recipe and a Shopping List.

And the central conflict of all software development is that the robot never reads the Recipe to figure out the Shopping List.


👨‍🍳 The Metaphor: The Robot Chef

Imagine you have a robot chef. Its job is to bake a cake.

  • The Recipe: This is your source code. It's the "ground truth" of what needs to be done. It might say "Step 1: Mix flour, eggs, and sugar."
  • The Shopping List: This is your build file (a Makefile, BUILD.bazel, package.json, etc.). It's the list of ingredients you claim are needed.
  • The Robot Chef: This is your build tool (like make, Bazel, or npm).

The robot's process is simple and unforgiving:

  1. It reads only your Shopping List.
  2. It goes to the store and gathers every single item on that list.
  3. It returns to the kitchen and tries to follow the Recipe using only the items it just bought.

This simple process can fail in two major ways.


Scenario 1: The Broken Build (The Missing Ingredient)

This is the most common error.

  • The Shopping List: You write "flour, sugar."
  • The Robot's Action: The robot fetches flour and sugar.
  • The Recipe (Code): "Step 1: Mix flour, eggs, and sugar."
  • The Result: FAILURE.

The robot stops, drops the bowl, and reports FATAL ERROR: Ingredient 'eggs' not found. It doesn't matter that "eggs" are obviously needed. They weren't on the list.

This is a Missing Dependency. In technical terms, the Actual Dependency Graph (we'll call it Ga) included an edge from Cake to Eggs, but the Declared Dependency Graph (Gd) did not.

The build fails because your declared list was not a perfect representation of reality. You told the build tool a lie, and the compiler caught it.

[Your diagram/image for Scenario 1 here]


Scenario 2: The Slow Build (The Useless Ingredient)

This is a more subtle but equally important problem.

  • The Shopping List: You write "flour, sugar, eggs... and cabbage."
  • The Robot's Action: The robot fetches flour, sugar, eggs, and a head of cabbage.
  • The Recipe (Code): "Step 1: Mix flour, eggs, and sugar..." The cake bakes perfectly.
  • The Result: SUCCESS... but.

The build succeeded. The cake is delicious. But you now have a head of cabbage rotting on the counter, and the robot's shopping trip took twice as long.

This is Overapproximation. Your build is "correct" because the golden rule (Ga is a subset of Gd) is met. But it's inefficient. You've introduced build bloat. The build tool wasted time and resources compiling, linking, and processing a library (cabbage) that was never used. In a large project, this is the difference between a 2-minute build and a 40-minute build.


Scenario 3: The "It Works On My Machine" Nightmare

This is the most complex problem, and it’s where our metaphor gets really useful.

Let's say you're making two things: a Chocolate Cake and Brownies.

  1. Chocolate Cake: Your Shopping List correctly lists "Cake Mix." This "Cake Mix" happens to include a bag of "Cocoa Powder" inside.
  2. Brownies: Your Recipe for brownies actually needs "Cocoa Powder," but you forget to put it on the Brownie Shopping List.

You build the Chocolate Cake first. The robot buys the "Cake Mix" and leaves the "Cocoa Powder" on the counter.
Then you build the Brownies. The robot (which should fail) looks at the empty Brownie Shopping List, but then sees the leftover "Cocoa Powder" on the counter from the last build. It shrugs, uses it, and the build succeeds!

Then, the disaster: Your co-worker, trying to be efficient, switches the Chocolate Cake recipe to "Vanilla Cake Mix."

Suddenly, your Brownie build breaks. You're staring at your screen, shouting "But I didn't even touch the Brownie code!"

You were relying on a "ghost." This is a Transitive Dependency nightmare.

  • Your Brownie_App (Recipe) had an actual dependency on Cocoa_Library.
  • You never declared it.
  • It only worked because you declared a dependency on Cake_Mix_Library, which transitively depended on Cocoa_Library.

The moment Cake_Mix_Library no longer needed Cocoa_Library, your build failed. Modern build systems like Bazel are designed to prevent this. They enforce strict dependency checking, essentially "cleaning the counter" between every single step to ensure you're not using ingredients you didn't explicitly ask for.


The Takeaway

Writing software isn't just about the Recipe (code). It's about meticulously maintaining the Shopping List (build file).

  • If you miss an item, your build breaks.
  • If you add extra items, your build slows down.
  • If you "borrow" items from another recipe's list, your build becomes a fragile, ticking time bomb.

A good developer is a good chef. A great developer writes a perfect shopping list every single time.

Top comments (0)