It's probably time to stop recommending Clean Code
qntm.org
qntm.org
But discussed with 800+ comments five months ago
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.
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.
4* footnotes
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.
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.
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.
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".
When you have a long pure function that has several local variables and changes them in several places - you can't trivially split it into smaller pure functions (because you would need to return several values from each of them).
You could return Pair<X,Y> or similar, but that's clumsy in Java. Sometimes these values form a single consistent type - then it's simple - make a class out of them and return that. But often they don't, they are just conceptually independent parts of the algorithm. For me that just means the function is cohesive, either change the algorithm or keep away.
Uncle Bob's advise is to make a new class out of that function, change the local variables into private fields and split the code into methods of that class with side effects modifying these variables all over the place.
This results in exactly the kind of code this article complains about, and I agree it's awful. We managed to change code that was explicit about what it does, with no side-effects and no easy way to break it accidentally - into a mess of small interlocking surprises. Whole classes of programming errors that were basically impossible previously are now easy to make.
Can this state even happen when I'm in this function? Should I check for it? Can I call this function after I called that other function? Can I change this variable in this function after requirements changed? The answer after the refactoring is "Dunno - solve this intricate puzzle to find out."
Where is the data or evidence for his ideas?
Nowhere.
Even a few that call themselves 'scientists' still don't offer any evidence. I don't know why we tolerate it. Why doesn't someone shout out 'where's your data' at their talks?
Also, even when it's possible to research something, external validity (whether it applies in another situation) is often a matter of opinion anyway.
We still need ways to share what we've learned from experience. But I think case studies, telling stories about what we learned in particular situations, are about the best we can do.
If not, then that's clearly a astronomically enormous red flag. If his ideas are that good he should be shipping successful stuff left, right, and centre. I don't think he is.
I'd listen to someone who's shipped great things. I don't think he's shipped anything substantial at all, let alone anything great.
I've wondered what experience and successes "Uncle Bob" has on his resume that makes him an authority. Whenever I read his articles I get the feeling he's a pompous fraud.
We need to go back to writing code like we are on resource constrained systems, which forced you to be explicit in what you did and think about why you were doing it.
I'm often test-driving stuff with unit level tests and the only times I have to mock collaborators are when the collaborators are
- third party services,
- badly designed, or
- not built yet.
This has nothing to do with unit/integration level testing.
With integration tests my expectation is that we're using real dependencies just fake data which is often easier to set up.
perhaps the pertinent take-away is "be sure you test thoroughly" rather than "do things my way and you will mess your code up egregiously, likely increasing the cognitive load to work with it beyond sensibility, and maybe test thoroughly as a side-effect thereof".
Not all unit tests are bad of course, but many in my context, imo, are unhelpful.
Then there is the code quality issue. The stuff that was written 20 years ago was somewhat hard to refactor because noone dared because who knows what would break. So basically every code base would degrade into a mess and become harder and harder to maintain over time. A code base that is maintained with automated tests can be improved as time goes on with relatively low risk.
That said, I think TDD is a cargo cult. I think a better approach is to determine on a case-by-case basis if a test is useful for whatever you're working on and developing a sense for that is part of becoming a better software developer. Things like coverage metrics completely obliterate this nuance and lead to some of the most obvious, ridiculous tests I've ever seen.
Do you have any hard evidence that this is actually a skill that some people have and others don't? How do you even measure it to verify?
I'm genuinely curious -- I've been thinking in these terms myself recently, too, but my literature search came up empty.
But I also think it's worth asking "what language are we in"? When I'm writing in C, I'm going to be maxing out that 24 lines, because it's sufficiently low level that seems appropriate. With powerful languages like Python and Ruby, you can accomplish so much with 3 lines of code, maxing at 5 doesn't seem terrible. With Java (yuck, not a fan), one of the problems is there's so much boilerplate for every function that it just seems like the screen is filled with non-informative blah-blah-blah.
So... imagine that someone enters a kitchen, because they want to show you how to make a cup of coffee. As you watch carefully, they flick a switch on the wall. The switch looks like a light switch, but none of the lights in the kitchen turn on or off. Next, they open a cabinet and take down a mug, set it on the worktop, and then tap it twice with a teaspoon. They wait for thirty seconds, and finally they reach behind the refrigerator, where you can't see, and pull out a different mug, this one full of fresh coffee.
Worth a read for the instructive value of the critique - whether you agree with either point of view, much is to be learned in the "space" between different viewpoints.
And herein lies the problem. If I define a function that calls two other functions, does the caller do one thing, or is it two? The main() function ultimately does everything. Does that mean that main() is the worst function in an application?
I studied programming in the 80's, and I seem to recall that evidence suggested that large functions were not necessarily more unreadable nor unmaintainable than small functions.
So in the end, are we are forced back into a circular argument: good programs are written by good programmers, and bad programs are written by bad programmers. How do we know that they're good programmers? Because they write good code!
Parenthetically, I've always been suspicious of his "Uncle" moniker.
"Functions should have no side effects." So, Haskell it is, then. No printf for you, monads is where it's at.
"output arguments are to be avoided in favour of return values." So what about functions that affect multiple values? Are we supposed to use structs everywhere? What about error conditions? Are we supposed to use hybrid variables, which are often regarded as a source of problems?
No silver bullet.
I wouldn't say the mission of trying to create "clean" code is a bad one. Certainly there is terrible code to read and maintain, which implies there are good ways to write code.
This is one step closer to understanding a Bash coder. Just get things done, because you don't have the time to worry about how it was done. Maybe a bit of optimization but really optimizing some string or numeric loops that are split second over optimizing functions or operations that take longer periods of time are much more important. Readability and the ability to debug is also important.
Stop worrying about the Purity Of Essence of your code, you'll end up turning on your own kind while muttering something about the fluoridation of the code supply.
Instead of:
function foo() {
// this does abc
big block of code
// this does def
big block of code
...
etc
}
and having a function I have to scroll pages through, I now do this:
function foo() {
DoABC()
DoDEF()
}
If yes, great, your code is fine. Clean code is a meme foisted upon us by bloggers and the equivalent of dev influencers.
Edit: Can we have one thread without the political topics overtaking the discussion? It's Friday, come on.
This type of attitude to what's "fine" is immature and in my opinion represents a clear and present danger to businesses. I have seen several companies brought to their knees because their codebases were so difficult to change.
And that's not even touching on issues like security, performance, reliability, etc.
Code quality matters.
It's like our little mini version of the moscow show trials.
(Oh yes, as I mentioned to someone else earlier today I'm so utterly fed up about how we treat certain topics. I actually respect actual climate scientists but I don't to same degree respect people who are out to punish climate heretics.)
As far as I can see Bob was everyones uncle, generally popular, not infallible but widely respected until he did the grave sin of not paying enough respect to climate scientists or something to that effect. In fact there is a chance he just asked questions, but that is a sin so grave it got its own derogatory term, JAQ-ing. As everyone should know by know asking questions isn't good scientific behaviors and everyone who ever does it only ever do it to it to derail constructive discussions.
After that he became pretty clueless and his books became dumb and useless. All he knew about programming is wrong and it turns out he never helped any project.
I hadn't heard of Uncle Bob being cancelled before this thread, but I have voiced my reservations about Clean Code to colleagues multiple times before.
You're not doing anyone a favor by painting all criticism of his work like that. It's meaningless polarizing nonsense.
But maybe I should give the benefit of the doubt.
It was not related to the article but to the comment.
Sorry for the confusion.