This is the kind of assertion that's going to need a very compelling, realistic example in order to avoid being seen as anything other than some sort of purely theoretical fantasy.
This is the kind of assertion that's going to need a very compelling, realistic example in order to avoid being seen as anything other than some sort of purely theoretical fantasy.
Abstractions have their place, but the evolution of my 15 year career has led me to use them as a last resort when I find myself needing to copy code more than twice.
It's a pity that so few of our programming languages distinguish between these two quite different concepts.
The only language I use that differentiates between functions and procedures is PostgreSQL, and to be honest I find the distinction pretty annoying in practice. One can return values, the other can control transactions. So when refactoring code you sometimes need to make a function a procedure or vice versa and it's a pain. (As a consequence, I try to always use functions to make things easier)
This is the sort of advice that can quickly become a thought terminator because the writer has so much authority.
10 or 20 small functions with no nesting inside still has all the complexity, but none of the structure to help programmer understand it.
Functions can be called in any order, any number of times, and you have to trace the full call-stack in your mind and keep it there to get the same understanding of what happens that you had just by looking at the linear code.
A sentence should be as long as required to concisely get across one idea.
“A function should be as long as required to concisely do one thing” might be a better sentiment. But even then it will have exceptions.
http://number-none.com/blow/john_carmack_on_inlined_code.htm...
Setting aside the lesson, I find Carmack does a wonderful job articulating his argument, whereas the Uncle Bob's put a lot of weight on an appeal to authority.
It's been a while since I read Clean Code, but I seem to recall the author giving numerous examples of bad code where it was obvious the developer attempted to condense their code into as few lines as possible at the expense of clarity. This immediately proceeded the advice to keep functions short. The author does this as a warning to not follow this advice blindly. Commenters on this thread are cherry-picking advice from the book and ridiculing it by presenting edge cases where the advice would be bad. To which I imagine the author facepalming and saying "yes, that was the entire point".
I was certainly guilty of it at times.
The clean code movement has drastically improved the quality of most projects I’ve seen.
I see plenty of garbage but nothing like 20 years ago.
The sweet spot is somewhere in the middle.
I've definitely refactored code which was done by someone who was clearly a clean code enthusiast where they couldn't generate enough single line methods.
I took a lot of those single line methods, that were often used only in one place or where the name of the method was nearly as complicated as the expression that it was replacing and by in-lining them did not produce spaghetti code and which eliminated the cognitive load of that extra method.
You have to account for the cost of extracting a method, which is always additional cognitive load and the possible cost of a reader scanning through the source code to look for the definition of the method (even if its still in the same file).
When you are cleaning up clear spaghetti code the cost of the cognitive load is generally going to be much less than the benefit of removing the impenetrable spaghetti.
But at some limit you no longer have spaghetti code yet you can still continue to pull out methods which are mostly useless abstractions which give the reader more names and more overhead. At that point you're just inventing a complicated domain specific language composed of your methods which are adding pure cognitive overhead to the reader and offering nothing in terms of actual code cleanup.
Also not every way of breaking up spaghetti code is equivalent. If you use complicated abstractions in an attempt to avoid code duplication you can wind up with code that is deduplicated and abstracted and not spaghetti but completely impenetrable. I've had success with removing abstractions completely producing simple, clean but duplicated code, and then approaching the de-duplication problem by producing simple instead of complicated abstractions.
And when you extract a method you've invented it so you're instantly familiar with what it does and your rationale for naming that way, and it seems perfect and good. When someone else approaches your source code, your subjective rationale for why something was abstracted into a method and what it was named and what it does may not be anywhere near as intuitively obvious. You can generate an affinity for your own abstractions due to the familiarity of inventing them that blinds you to how difficult it is to read your own code for someone with no experience with it.
The best code isn't measured by line count of methods or anything like that, but lowest cognitive overhead on the part of the reader. All the objective measures that are offered are really trying to get at that actual subjective measurement. And every single objective rule about writing clean code have edge conditions that you need to violate from time to time to produce actual easy to read code. All rules about clean coding are going to be terrible if they're followed 100% of the time. Some rules are better and should be followed almost all the time, other rules are closer to 80/20 and should be fairly commonly rejected. When you reject a rule you should understand why the rule is there and what it costs when you reject it, and understand what benefit you're getting by rejecting the rule.
IME, most reading of code has a specific focussed goal for which logical splitting allows not reading lots of irrelevant code you'd have to read if it was a non-abstracted mass.
Modularizing code into focused units is a big part of how you end up with sustainable software (another important part is understanding the requirements).
One of the easiest ways to obfuscate code is to have control flow obscured by hiding it in functions scattered around the codebase.
Getting the former without the latter can actually be difficult, particularly with large teams.
They are readable because you can look at any one part and understand what is going on. Referential transparency is important here, as you can be assured each combinator has no strange side effects. Composability is also important, as it gives you an idea as to what each function call does without having to look it up.
If your functions have side effects and 100 different interfaces, then yes, that's a mess and hard to understand. If your functions have predictable interfaces and are referentially transparent, then that's a completely different scenario.
I mean, pretty much every Haskell program ever is split between a number of clean small functions. But for someone who understands Haskell it's hardly unreadable. Quite the opposite.
4* footnotes