That more than anything is what kills readability, because context switching imposes a huge cognitive load. Isolating layers of abstraction almost always means small, isolated, single-purpose functions.
That more than anything is what kills readability, because context switching imposes a huge cognitive load. Isolating layers of abstraction almost always means small, isolated, single-purpose functions.
I agree up to a point, but I find this kind of separation a little… idealistic? I prefer the principle that any abstraction should hide significantly more complexity than it introduces.
At the level of system design, there probably are some clearly defined layers of abstraction. I’d agree that mixing those is rarely a good idea.
But at the level of individual functions, I have too often seen numerous small functions broken out for dogmatic reasons, even though they hid relatively little complexity. That coding style tends to result in low cohesion, and I think the cost of low cohesion in large programs is often underestimated and can easily outweigh the benefit of making any individual function marginally simpler. If you’re not careful, you end up trading a little reduction in complexity locally for a big increase in complexity globally.
// v1, mixing layers of abstraction
x = a if exists, else first()
y = b if exists, else second()
result = third(x,y)
// v2, abstraction
result = getResult(a,b)
In v1, we have the semantics of x and y, so we understand that a “result” is obtained through the acquisition of x and y. Whether we need to understand this is a judgement call. But v2 opens a different “failure to understand” modality: “getResult” is so blackboxed that the only thing it really accomplished is indirection, without improving readability.I love Clean Code, but I think it sometimes prematurely favors naming a new function and the resultant indirection.