Moreover, it is counter intuitive, too. The instinct of most juniors is to build foundations first and it took me a long time to learn to do the opposite.
Each layer serves as a customer for the layer above. If you havent figured out what the layer above needs precisely by building it you're probably going to provide the wrong thing to it in the layer underneath if you write it first.
It's also more resilient to requirements mistakes and requirements changes - it's easier to change course on a feature after showing a UI to a stakeholder if you didnt build the lower layers.
E.g., requiring that a string be base64, have a certain fixed length, and be provided by the user.
E.g., requiring that a file have the correct MIME type, not be too large, and contain no EXIF metadata.
If you really always need all n of those things then life isn't terrible (you can parse your data into some type representing the composition of all of them), but you often only need 1, 2, or 3 and simultaneously don't want to duplicate too much code or runtime work, leading to a combinatorial explosion of intermediate types and parsing code.
As one possible solution, I put together a POC in Zig [0] with one idea, where you abuse comptime to add arbitrary tagging to types, treating a type as valid if it has the subset of tags you care about. I'm very curious what other people do to appropriately model that sort of thing though.
https://lean-lang.org/doc/reference/latest/Basic-Types/Subty...
I try to avoid bugs like this:
By accident, at first, I omitted the letter u in my list of letters that I was generating packages for, which caused extremely cryptic and long (500KB of uv painstakingly explaining to me why I was wrong) dependency resolution errors on specific guesses:
by doing this:
import string
LETTERS = string.ascii_lowercase
instead of this: LETTERS = "abcdefghijklmnopqrstuvwxyz"
It's a few more characters to type, but easier to examine for correctness.
https://arxiv.org/abs/1803.03635