Don’t Refactor Like Uncle Bob. Please

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.

27 thoughts on “Don’t Refactor Like Uncle Bob. Please

  1. 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.

    Liked by 1 person

  2. 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!

    Liked by 4 people

  3. 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";
    }

    Liked by 2 people

  4. 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 šŸ˜‰

    Liked by 1 person

  5. 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.

    Like

  6. 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.

    There are a few names which are meaningful in and of themselvesā€”most are not. Instead, you need to place names in context for your reader by enclosing them in well-named classes, functions, or namespaces. When all else fails, then prefixing the name may be necessary as a last resort.

    Imagine that you have variables named firstName, lastName, street, houseNumber, city, state, and zipcode. Taken together itā€™s pretty clear that they form an address. But what if you just saw the state variable being used alone in a method? Would you automatically infer that it was part of an address?

    You can add context by using prefixes: addrFirstName, addrLastName, addrState, and so on. At least readers will understand that these variables are part of a larger structure. Of course, a better solution is to create a class named Address. Then, even the compiler knows that the variables belong to a bigger concept.

    Consider the method in Listing 2-1. Do the variables need a more meaningful context? The function name provides only part of the context; the algorithm provides the rest. Once you read through the function, you see that the three variables, number, verb, and pluralModifier, are part of the ā€œguess statisticsā€ message. Unfortunately, the context must be inferred. When you first look at the method, the meanings of the variables are opaque.

    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.

    Like

    1. 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.

      Like

  7. 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.

    Like

  8. 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.

    Like

  9. 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.

    Like

    1. 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?

      Like

    2. 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:

      • Leaving the method-level and entering the component level
      • Using method names to give context about what the following code is good for, instead of using comments to explain what is done in this part of the spaghetti code

      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!

      Like

  10. 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.

    Like

    1. 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.

      Like

  11. 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.

    Like

      1. 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

        Like

  12. 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.

    Like

Leave a comment