One thing I find really hard is where to draw the line to an uncleanable ball of mud.
One thing I find really hard is where to draw the line to an uncleanable ball of mud.
Then, I can isolate and fix a function by itself. It may have been written to be 200 lines long, filled with hackery and half measures, but my complexity is contained within it, and the functions it calls out to (so nothing calling it should need to be substantively changed). Those called functions, in turn, may also need improvements, but my focus every step of the way was keeping the interfaces clean, so I can always get down to some base set of functionality that has no dependencies, fix that up, make the necessary fixes to keep things working in the functions directly calling them, test, and then move up to those calling functions and repeat, until I get back up to the big ugly 200 line monstrosity. Every step of the way I can make sure things are still working, and I don't have to substantively touch anything above the 200 line monstrosity until I've cleaned it up.
So by keeping my interfaces clean, I can figure out how, inductively, I can make progress.
The lack of clear interfaces is the real problem; if you don't put in the effort when writing your code to keep those clean, you end up with circular dependencies, implicit workflows and state between functions ("to call X you first have to call Y to get a foo, pass that into X, then call Z to reset the foo value"), and other nightmares, that move you more to needing a full rewrite.
Of course sometimes stateful (procedural) interfaces are a must, but it's surprising how many painful OOP classes can be replaced with a "const struct foo" interface with a clear meaning to the data in it.
Of course you can declare everything is data. You don't need higher order functions for that. But real data is simple and introspectable. Computation is not.
Let me try to rephrase with an example. Eg you might have an API for a file that allows you to open a file, manipulate it, and then close it. That's a very procedural interface.
As an alternative, think of an interface like the following:
withOpenFile(filename, manipulator)
that opens a the file, calls the manipulator function on the contents, and automatically closes it.Or compare map, filter and reduce vs manually iterating over a collection of items.
I did really like your grandfather comment (https://news.ycombinator.com/item?id=11099224). I hope I've cleared up my point?