What i commonly notice is if a developer starts solving the problem at its core, and then builds the interfaces around it, or he starts writing "utility" code, like writing code in hope that the problem will solve itself if enough helper code (or framework code) has been written for it. When i see a developer doing the latter, i know he will not be too productive, because he has no clear vision of the solution.
Perhaps if you have a poor definition of utility code. Sure if you spend all your time writing random string functions or a new web framework or whatever. But that's sorta tautological: writing useless code is useless and bad.
I often find bottom up coding to be fantastic. I'll start with a general idea of what I want to do, then start writing low-level "helper" functions. Like "save image to blob storage", "perform search against remote API", "normalize search result into our document type", etc.
When I get around to tying it all together, if I've done it well, I find that hey, my "main" function is only 5 lines long as I've built up all the primitives I need.
And sometimes the reverse works, too, where I start off writing main and end up with a 1000 line function with lots of nested bits. Then go back over it and start extracting parts/structuring.
Like many things in software creation, this is one of those "artistic" or "creative" or "intuitive" parts where the developers internal heuristics and experience hopefully guide them to the right approach. Not to say it should be that way, but that seems to be a big difference between good and bad programmers, and I've not seen really hard, structured, learning approaches to teach the difference.
I have heard the expression that wiring bottom-up is "building a DLS for your problem".
That would explain why we don't see a lot of it in the Java world and therefore why "traditional developers" think it's useless - first, creating DSL may need stronger language than Java to be practical, and second, most of the DSL is already done as parts of Spring or JEE.
Ha! All this time I thought DSLs were more, well, languages. More flexible syntax, or at least differently behaving functions or operators. Like some of those fluent APIs (usually not a fan), or test frameworks love to do. I'd not thought of just regular domain functions to be a DSL.
I'm curious though, what do you mean that Spring is the DSL? Certainly that's the opposite, being so generic?
You're dismissing bottom up programing. That is actually a wide used technique, especially useful when the problem is not completely understood at the beginning.
It is also useful as a refactoring technique. Pulling common structures and patterns out to helper code often reveals the true structure of the existing code, which makes the end result easier to see.
.. That is, provided you refactor again afterward to put things into the correct abstractions.
I call this pulling out into helper code function "blowing up" the code, because it usually results in an order of magnitude larger amount of lines of code.
This refactoring technique certainly helps make the core logic of the (usually) over complicated program visibly easier to see / grok from a distance.
From there its just a matter of reducing / refactoring / simplifying things down to more correct (at least I hope) abstractions that don't overcomplicate the solution.
This is most useful when you're given something like a 5k line script that does far too many things for far too many different reasons, all with very little if any documentation. (ie, bottom up refactoring, don't really know what the solution is but you know the current solution isn't the way to go)
>when the problem is not completely understood at the beginning
That was my point too, you just formulated it in a more positive way. If the developer does not understand the problem completely, then he will do what you describe as bottom up programming.
Experimenting, in addition to taking more time than just spilling out the solution, also produces code that is not used in the final solution. So yes, a library for manipulating a certain kind of data is nice. But when it has functions that are never used, that is lost productivity. And even if the library is there, when a function becomes necessary, it might require different parameters or different application over data. Then making a conversion routine around the library function to fit the new usage also takes time, and potentially leads to inefficiency.
So my principle is that understanding the problem well is still better. That does not exclude experimenting, but its easier to experiment without spending time actually coding up a false solutions, just in ones head, when the problem is well understood.