Remind you of any library/cultural-heritage sector codebases you’ve worked with lately?
Some people seem so enamored with small functions that the idea of abstracting any and every piece of logic that might seem even nominally complex into a separate function is something that is enthusiastically cheered on.
I’ve worked on codebases inherited from folks who’d internalized this idea to such an unholy extent that the end result was pretty hellish and entirely antithetical to all the good intentions the road to it was paved with. In this post, I hope to explain why some of the oft-touted benefits don’t always pan out the way one hopes and the times when some of the ideas can actually prove to be counterproductive.
I think blindly following Rubocop’s dictatorial micro-advice without a human thinking about the macro-level and “does this make the code more readable/maintainable” (and “what are the use-cases for flexibility, what dimensions do we expect to change or be changed? And how do we provide for that?”) can contribute to this.
My main problem with DRY is that it forces one into abstractions — nested and premature ones at that. Inasmuch as it’s impossible to abstract perfectly, the best we can do abstract well enough insofar as we can. Defining “well enough” is hard and is contingent on a large number of factors, some of them being:
— the nature of the assumptions underpinning the abstraction and how likely (and for how long) they are likely to hold water
— the extent to which the layers of abstractions underlying the abstraction in question are prone to remain consistent (and correct) in their implementation and design
— the flexibility and extensibility of both the underlying abstractions as well as any abstraction built on top of the abstraction in question currently
— the requirements and expectations of any future abstractions that might be built on top of the abstraction in question
…DRYing up code to the fullest extent possible right now would mean depriving our future selves of the flexibility to accommodate any changes that might be required. It’s akin to trying to find the perfect fit, when what we really should be optimizing for is to allow ourselves enough leeway to make the inevitable changes that will be required sooner or later.
As a result, the cognitive overhead of processing the verbose function (and variable) names, mapping them into the mental model I’ve been building so far, deciding which functions to dig deeper into and which to skim, and piecing together the puzzle to uncover the “big picture” becomes rather difficult.
…This has already been stated before but it bears reiterating — an explosion of small functions, especially one line functions, makes the codebase inordinately harder to read. This especially hurts those for whom the code should’ve been optimized for in the first place — newcomers….
…Simple code isn’t necessarily the easiest code to write, and rarely is it ever the DRYest code. It takes an enormous amount of careful thought, attention to detail and care to arrive at the simplest solution that is both correct and easy to reason about. What is most striking about such hard-won simplicity is that it lends itself to being easily understood by both old and new programmers, for all possible definitions of “old” and “new”.
Actually one of the best essays I’ve seen on code architecture matching my own experiences I’ve seen. I recommend reading the whole thing.