Correction: Throughout this article, I attribute Chapter 2 of Clean Code to Robert Martin, however I was recently informed that this particular chapter was actually authored by Tim Ottinger. That said, Martin is listed as the sole author of this book, so it’s a given that he stands by the advice from this chapter.
“Clean Code” has garnered a bit of notoriety despite coining an endearing term we use in coding conversations. This book from 2008 is a compilation of principles and studies that “Uncle Bob” (a.k.a Robert Martin) had developed in his decades of programming.
Many have incorporated his practices into their work, some swearing by it, and others feeling that they’re to be taken more as loose guidelines rather than rules. Regardless of what you think, though, Uncle Bob doesn’t see them as guidelines. He ascribes to them wholeheartedly and very rarely allows exceptions.
So you’d think that his examples of refactoring in this book would at least be better than the average code you see in your day-to-day work. Or at least consistent with his other advices.
You’d think.
Keep in mind that Martin didn’t make up these examples out of the blue. These are ACTUAL changes he’s made in his work, and he’s using them as teachable moments.
I’ll be discussing the very first example of refactoring in his book from “Chapter 2: Meaningful Names”. Here’s the before:
private void printGuessStatistics(char candidate, int count) {
String number;
String verb;
String pluralModifier;
if (count == 0) {
number = "no";
verb = "are";
pluralModifier = "s";
} else if (count == 1) {
number = "1";
verb = "is";
pluralModifier = "";
} else {
number = Integer.toString(count);
verb = "are";
pluralModifier = "s";
}
String guessMessage = String.format(
"There %s %s %s%s", verb, number, candidate, pluralModifier
);
print(guessMessage);
}
and here’s after:
public class GuessStatisticsMessage {
private String number;
private String verb;
private String pluralModifier;
public String make(char candidate, int count) {
createPluralDependentMessageParts(count);
return String.format(
"There %s %s %s%s",
verb, number, candidate, pluralModifier );
}
private void createPluralDependentMessageParts(int count) {
if (count == 0) {
thereAreNoLetters();
} else if (count == 1) {
thereIsOneLetter();
} else {
thereAreManyLetters(count);
}
}
private void thereAreManyLetters(int count) {
number = Integer.toString(count);
verb = "are";
pluralModifier = "s";
}
private void thereIsOneLetter() {
number = "1";
verb = "is";
pluralModifier = "";
}
private void thereAreNoLetters() {
number = "no";
verb = "are";
pluralModifier = "s";
}
}
Oh boy.
Before I get into what’s wrong with this, let me say something first.
There’s a whole other chapter about function separation, but it somehow leaked into this one. Martin does this a lot in his examples. His strict adherence to using real code changes from his work confounds the lessons he teaches in each chapter. He could have had these functions laid out to begin with and simply changed names.
And in many of his examples, he’ll show “before” code that’s bad for significant reasons than just the ones he teaches in that chapter, but he’ll fix those too, which makes it harder for a reader to judge just how effective that chapter’s advice really is on its own.
I tell you all this because I’ll evaluate this code holistically, just like he refactors holistically in his examples. I think that’s fair.
The first thing you’ll notice is that Martin has taken a single, mostly PURE function (shout out to all the functional bros), and made a class out of it. And not a static utility class or anything like that. An INSTANCE, with its attributes being mutated to serve the result.
Why does he do this? Well, it’s because Martin’s relentless insistence on splitting out functions and having zero arguments means that class attributes are the only way to assign variables inside a function to be used outside that function. Java doesn’t have pass-by-references, except with classes, but Martin doesn’t even want to pass a class as an argument and mutate that, so the end result is littered with side effects (i.e functions modifying outside their scopes).
He also seems to pick and choose when he uses instance variables and when he passes arguments. Like why is count
an argument and not also assigned to an instance variable? He never explains his reasoning for this. I can only guess it’s because count
is passed in from outside the class, but why does that matter? Having it as an instance variable means one less argument to pass around, which is what Martin seems to be going for.
Am I supposed to interpret make
as a constructor? That can’t be right, because it returns a String
, not a GuessStatisticsMessage
type. If he’d made a case for these instance variables needing to exist for something other than this specific return string, I might’ve been on board with this. But he never does.
The second thing you’ll notice is that even though this chapter is about naming, the names are, ironically, not that good.
What does make
mean?
What do number
, verb
, and pluralModifier
mean in the context of the problem?
Why do functions that modify state start with the word “there”?
How is a reader supposed to know what GuessStatisticsMessage
means without the calling context? And why is the name a weird verb-noun hybrid?
I could go on.
If you’re like me, you had to read all the code anyway to understand what make
does. It generates a grammatically correct sentence describing the count of a given character (i.e “There are 4 Cs”, “There are no Ds”, “There is 1 B”, etc). That’s all. I could’ve told you that more easily just by looking at the original code.
The last thing you’ll notice is that Martin hasn’t actually made the code easier to understand. Because even if his naming was on point, there is now more code to parse.
The spaces between functions make the logic no longer fit on a screen. This adds an overhead of visual memory to piece the functionality together.
The functions themselves add an overhead of finding where they’re called, along with having to track the arguments passed to them. Or worse, track the mutations of instance variables.
Just reading the body of createPluralDependentMessageParts
(what a mouthful) doesn’t actually tell you what’s going on under the hood. That’s because Martin considers the if
blocks to be in a lower level of abstraction than the enclosing function, so he believes they should be encapsulated (i.e hidden) in their own functions.
Now, imagine you’re trying to understand this code for the first time. You reach the if
blocks and see that they delegate to other methods. Are you gonna stop there? Probably not.
Anyone who’s read the code up to this point would love to know what’s inside thereAreNoLetters()
. In fact, you cannot understand the make
method without understanding how verb
, number
, and pluralModifier
are mutated. You might be able to guess by looking at the return string command and the order in which the fields are used. But there’s no way for you to know that “no” replaces “0” unless you go deeper. This is an essential detail to understanding make
, and it’s encapsulated 2 levels deep.
This unintuitive separation results from Martin treating levels of abstraction so granularly, that the most microscopic of dips rings alarm bells. But that’s the thing; it’s just him. Most programmers would rather deviate a little in abstraction, than to fragment a simple function into a stateful, side-effect-ridden tree of methods.
The original method was actually better. By far. But Martin’s right in that it could be improved, he just went about it the wrong way. Here’s my “perfect” version:
private String generateGuessSentence(char candidate, int count) {
String verbIs = count == 1 ? "is" : "are";
String countStr = count == 0 ? "no" : Integer.toString(count);
String sIfPlural = count == 1 ? "" : "s";
return String.format(
"There %s %s %s%s",
verbIs, countStr, candidate, sIfPlural
);
}
You may have noticed the subtle redundancies in the original code, namely that each String value is the same in 2 of the 3 cases. One of Martin’s rules is that you should reduce duplication wherever possible, and he breaks it here for no reason.
You may have also noticed that these if statements don’t do anything except assign values to variables. Martin uses ternary operators in other examples, so why not here? I can only assume it’s because he found the 3 count
cases to be clearer than assigning each value with individual conditions like I have.
Strange, isn’t it? Martin extracted the logic into functions before even trying to optimize it first. I can’t read his mind, but in the book, he almost never mentions alternative approaches to refactoring. He simply shows his improved version, and explains why it’s the best with surface-level explanations. I feel like this should’ve been a major lesson.
But if Martin really wants to keep the 3 cases, the naming, and the other functions, I can play along with that. I still think his refactoring could have been cleaner, even by his own standards. Here’s another version I made:
public class GuessStatisticsMessage {
private char candidate;
private int count;
public GuessStatisticsMessage(char candidate, int count) {
this.candidate = candidate;
this.count = count;
}
public String make() {
if (count == 0) {
return thereAreNoLetters();
} else if (count == 1) {
return thereIsOneLetter();
} else {
return thereAreManyLetters();
}
}
private String thereAreManyLetters() {
return String.format(
"There are %s %ss", count, candidate
);
}
private String thereIsOneLetter() {
return String.format(
"There is 1 %s", candidate
);
}
private String thereAreNoLetters() {
return String.format(
"There are no %ss", candidate
);
}
}
Would you look at that. A proper constructor, no arguments, and all the string operations are in the lowest-level methods. Now that’s clean codeš! Just ignore that there’s no reason for this to be a class.
So in conclusion, if you’re gonna read this book, ignore the refactorings and come up with your own. And take his principles with a grain of salt.
P.S
I can’t believe this is my first post!
My inspiration for writing this article came from noticing how often “Clean Code” was being uncritically recommended to beginners. Fortunately, I’ve never worked with anyone who coded like this. But the thought of aspiring devs learning from this book scares me for the future of the field.
Credibility of opinion about software development practices is to be viewed with skepticism. Software as a field is in its infancy and is constantly changing. By the time you become an expert, the landscape has already shifted.
I was also inspired by Martin’s recent interview with a Youtuber named “ThePrimeTime”. They talked about a lot of stuff, and it was mostly just a casual conversation, though Primagen tried to challenge some of Martin’s principles at some points throughout.
So I was expecting that Martin, 16 years after his book, would relax some of his moreā¦debatable principles. But he didn’t. He doubled down, and that was the last straw for me. Look, I don’t have anything personal against him. I just think there aren’t enough people calling out his weaker advice.
Anyway, thanks for reading and have a nice day.
Great post, it captures what’s so wrong about Uncle Bob’s book so well. I would go a little further with the conclusion though: for those looking to read this book: don’t. Either you don’t need it, and it’s a waste of time, or you do, and you’ll pick up very bad advice. Go read A Philosophy of Software Design by John Ousterhout instead.
LikeLiked by 1 person
I’m currently reading it, and so far I’m super impressed.
LikeLike
Great first post!
I wholeheartedly agree that taking the Clean Code advice on refactoring dogmatically results in code that unnecessarily complicates simple things.
Notice that the examples of “cleaning” code are always applied to simple problems where it doesn’t make much of a difference to begin with. Both the “unclean” and the “clean” versions are usually understandable enough. But there are no examples of big and complex software projects (say, a browser, an OS, a complex app) following Clean Code strictly. I suspect this is simply because if a project tries to, its codebase becomes an unmaintainable mess well before the project gets big or complex enough.
Anyway, i like your refactored version of `generateGuessSentence()`, and that’s probably would i would’ve done too. But i wanted to mention that another possibility is to still keep the 3-case `if` and simply return the formatted strings directly:
“`
private String generateGuessSentence(char candidate, int count) {
if (count == 0) {
return String.format(“There are no %ss”, candidate);
} else if (count == 1) {
return String.format(“There is 1 %s”, candidate);
} else {
return String.format(“There are %s %ss”, count, candidate);
}
}
“`
Less conditionals. Less stuff to keep track of. Clear separation of the 3 different cases we care about. And i’d argue much “cleaner” that both the original and the class-based refactored one.
Simple >> Clean
Keep up the great writing. Cheers!
LikeLiked by 4 people
The main problem I see with Uncle’s Bob code for that problem, is he tries to figure out a pattern for something really simple, like a simple English text, when he shouldn’t, and by doing so he adds a lot of unnecessary complexity. The ‘pluralModifier’ is unnecessary.
Also, I’m not a fan of the void returning methods which mutate the state. It’s hard to follow the data which is mutated across the different methods. This increases the complexity, the maintenance and you can’t figure out easily how it all works together.
Just because you have a class with some private state doesn’t mean internally it’s OK for it to work chaotically. The same good clean code principles must also apply internally, so it’s easy to understand the code and maintain it.
Here’s my C# version:
public static class GuessStatisticsMessage
{
public static string Make(char candidate, int count) => count switch
{
0 => MakeNoLetters(candidate),
1 => MakeOneLetter(candidate),
_ => MakeManyLetters(candidate, count)
};
private static string MakeNoLetters(char candidate) => $"There are no {candidate}s";
private static string MakeOneLetter(char candidate) => $"There is 1 {candidate}";
private static string MakeManyLetters(char candidate, int count) => $"There are {count} {candidate}s";
}
LikeLiked by 2 people
Someone said similar things in another blog post: It’s probably time to stop recommending Clean Code.
LikeLiked by 1 person
Oh I remember this one! Come to think of it, seeing this post was also part of my inspiration to read Clean Code for myself and see what’s it all about.
LikeLike
Love it!
Clean Code has always left a bad taste in my mouth, but I couldn’t quite put my finger on it the way you have. One thing that’s upset me multiple times is all the mindless job ads that go “are you also sleeping with a copy of Clean Code under your pillow?”. Hell no! A book that size will definitely mess with my REM sleep š
Anyway, good going. Please do take apart Agile whenever you have the time š
LikeLiked by 1 person
Really good article. Never read the book, even thought i have it. But Not sure i want to read it anymore.Naming is also terrible. `describeLetterCount` or similar would be better. There is no guess involved. We can confidently count the letters, not just guess.
LikeLike
Taking a re-read of that section, I feel that the context in which it lies is the key to understanding it. The example is bad, and the result is garbage because of that, the actual message though is sound – use your language to add context intentionally instead of accidentally.
My guess is that this was a fairly synthetic example meant to portray that when you create variables with names that don’t inherently reveal the reason for their existence this often indicates a missing concept. `number`, `verb` and `pluralModifier` in the context of `
printGuessStatistics
` are bad names. They only reveal their reason for existing once you actually read the entire method, which highlights that they’re used to build a “guess statistics message” which is then printed. This is definitely a problem I see more junior devs doing regularly (though the cure presented here is worse than the illness)A much more meaningful (but significantly more complex) example that leads to similar ideas is the URL class – https://github.com/openjdk/jdk11/blob/master/src/java.base/share/classes/java/net/URL.java
On LinkedIn, the author of that section (Tim Ottinger) notes the examples were written in Python and converted to Java, so perhaps there’s a translation of idioms there that’s lead to something that looks just plain wrong. I wonder if the example domain was changed / or simplified drastically. If you wanted to properly do that conversion to idiomatic java, replace the code with constructors + final fields (or the post- Java 1.8 shenanigans) and a toString method.
LikeLike
I honestly didn’t know these were initially written in Python. But surely this refactoring is strange for Python too, since Python is even more functional-oriented than Java.
But yeah, there definitely could have been better examples of this concept. Kind of a waste of a chapter conclusion if you ask me.
LikeLike
For another view similar to yours watch this lightning talk by Christin Gorman on vimeo: https://vimeo.com/49484333
LikeLiked by 1 person
Very refreshing to see others pushing back against this book with ACTUAL examples. Beat me to it. Thanks for linking me this.
LikeLike
Good post, thanks !
In fact, there is even a good reason for your version to be great, and a good reason to have an object.
I would add a “toString” calling make (or rename make). Then you can instantiate this message whenever you want, and pass it to as logger. And the string manipulations will only cost when the message is actually logged, instead of being made just to be thrown away if the logger configuration doe not print it.
LikeLike
Great post, and especially your point about instance variable. This is the thing that irks me the most. Refactoring code by obscuring what happens is terrible and makes it strictly worse. You’ll note that great programmers like John Carmack do the exact opposite and say to use pure functions.
LikeLike
Yet another dunker. Oh well.
LikeLiked by 1 person
I have read “Clean Code” several times and have watched several talks from Robert Martin and also watched the interview with “The Primegan” and think you are wildly off-base.
I doubt you have read the book or even paid close attention to that interview or have worked to understand the nuances of Robert Martin’s positions, and instead are cargo-culting on this idea that somehow his opinions are harmful.
Chapter 2 is called “Meaningful names” and that code snippet is from a section called “Add meaningful context”. It was meant to demonstrate how naming is important for adding context. I will say it’s not a great example, but I understood the intent without getting overly literal about it. It’s a great point to be made that naming helps build context.
LikeLike
I can see where you’re coming from, but please don’t throw out accusations of bandwagoning and not having read the book. It’s not a good look.
When an example is presented as a good refactoring, but it’s not, then regardless of context, it needs to be criticized.
I’m glad you understood the “intent”, but the intent wasn’t what I was criticizing. You seem like an experienced developer, but do you think a beginner knows exactly when to take things literally, and when to just listen to the intent?
LikeLike
second you jrenton87
Additional:This chapter 2 (~15 pages) is about naming and not about refactoring.The example Martin made (2 pages) is one of many in the context of naming.
The original code is bad, and the refactoring is worse, but the code is perfect for demonstrating two things:
Martin gave two additional solutions to handle problems, that’s all.
As a developer, you need a lot of different possibilities.
What you choose is your responsibility.
btw:
Your solution is too technical. You need to make it easier to read and understand, without forcing the reader to think too much.
the imho “best” solution for refactoring this code would be simple returning the text depending if it’s nothing/one/more, zero variables and if you use if/else, switch, pattern-matching in this case, has nothing to do with clean (use pattern-matchingā¦).
If you want/have to change the texts, invest the damn 30sec to rewrite the 3 lines!
LikeLike
Wow this is so petty. There are no side effect caused by this code, every mutable varaible is reassigned when make is called. Its just “i dont like OO so Martin is bad”.Weak, very weak criticism.
LikeLike
I mean, obviously it’s not atrocious or anything because it’s a small function. Just because the side effects were properly managed this time, doesn’t make this not a bad change.
Also I love OOP.
LikeLike
that just bad example. The principle is actually good
LikeLiked by 1 person
Meaningful names…and then…
Which is not really much worse than
LikeLike
After all that fuss, try it with candidate = “dress”:
There is 1 dress.
There are 2 dresss.
Doesn’t really work anyway. English ain’t that simple.
LikeLike
candidate is of type char, so it’s only a letter. But you’re right, coding grammar is insanely tough, and that’s just English.
LikeLike
Then I guess I was thrown off by candidate being formatted with %s rather than %c. (I work in C++, not C#.) If candidate really is a single char, then the printed messages are often going to be gibberish. Maybe I’m missing some context for what kind of char candidate might be:
There is 1 i
There are 2 is
LikeLike
This is very bad example. If we want to show a message to a user, then we, most probably, would like to have localized message. And different languages have different rules for numbers. It is only English language differentiates between 0, 1, and >1. So, the most appropriate name should be ‘StatisticsMessageInEnglish’.
Taking into account possible localizations we, most probably, simplify message to something like “Number of letters: <..>” to be translation-friendly to another languages and avoid unnecessary complexity.
Both author and Robert are discussing here minor issues while there is the major issue up there that depreciates the whole discussion.
LikeLike
Not all software needs localization.
LikeLiked by 1 person