Chapter 2: Meaningful names

I generally agree with the theme of this chapter: names are important. Naming is the main component of building abstractions and abstraction boundaries. In smaller languages like C or Go-Lang, naming is the primary mechanism for abstraction.

Naming brings the problem domain into the computational domain:

static int TEMPERATURE_LIMIT_F = 1000;

Named code constructs — such as functions and variables — are the building blocks of composition in virtually every programming paradigm.

Names are a form of abstraction: they provide a simplified way of thinking about a more complex underlying entity. Like other forms of abstraction, the best names are those that focus attention on what is most important about the underlying entity while omitting details that are less important.

Fom "Philosophy of software design"

However, I disagree with Clean Code's specific approach to naming and its examples of "good" names. Quite often the book declares a good rule but then shows horrible application.

Use intention-revealing names

The advocated principle is the title - the name should reveal intent. But the application:

The name of a variable, function, or class, should answer all the big questions. It should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent.

This sounds like an impossible task. First, the name that reveals all of those details fails to be an abstraction boundary. Second, what you notice in many examples in this book that this approach to naming leads to using "description as a name".

Martin presents 3 versions of the same code:


public List<int[]> getThem() {
  List<int[]> list1 
       = new ArrayList<int[]>();
  for (int[] x : theList)
    if (x[0] == 4)
      list1.add(x);
  return list1;
} 
(Might be) Obfuscated and unrealistic

public List<int[]> getFlaggedCells() {
  List<int[]> flaggedCells 
     = new ArrayList<int[]>();
  for (int[] cell : gameBoard)
    if (cell[STATUS_VALUE] == FLAGGED)
      flaggedCells.add(cell);
  return flaggedCells;
}
(Migh be) Good enough

public List<Cell> getFlaggedCells() {
  List<Cell&ht; flaggedCells 
      = new ArrayList<Cell>();
  for (Cell cell : gameBoard)
    if (cell.isFlagged())
        flaggedCells.add(cell);
  return flaggedCells;
}
(Might be) Premature abstraction

My main disagreement is this: not all code chunks need a name. In modern languages, this method can be a one-liner:

gameBoard.filter(cell => cell(STATUS_VALUE) == FLAGGED)

That's it. This code can be inlined and used as is.

While getFlaggedCells looks like an improvement over obfuscated getThem, it's not really a name, it's a description of what the method does. If the description is as long as the code, it's often redundant.

Martin writes about it in passing: "if you can extract another function from it with a name that is not merely a restatement of its implementation".
But he violates this principle quite often.

If for readability alone, I'd argue that the second version is as clear as the third and introducing Cell abstraction is an overkill.

To be fair there are good reasons to introduce Cell abstraction, but also there are reasons not to
List<int[]> is a generic type - devoid of meaning without a context.
List<Cell> - has more semantic meaning and is harder to misuse.

List<int[]> list = getFlaggedCells();
list.get(0)[0] = list.get(0)[1] - list.get(0)[0];
        
This compiles and runs, but the transformation is non-sensical and will corrupt data (the first element is a status field).
List<Cell> has a better affordance than List<int[]>, and makes such mistakes less likely. But improved affordance comes from the specialized type, not just the name.

But there is a the downside - specialized types needs specialized processing code.   Serialization libraries, for example, would have support for List<int[]> out of the box, but would need custom ser-de for the Cell class.

Since Clean Code, Robert Martin has embraced Clojure and functional programming. One of the tenets of Clojure philosophy: use generic types to represent the data and you'll have enormous library of processing functions that can be reused and combined.
I'm curious if he would ever finish the second edition and if he had changed his mind about types.

Avoid disinformation ... Use Problem Domain Name

Martin pretty much advocates for informative style of writing in code: be clear, avoid quirks and puns.

These are examples of "good" names from his perspective:

  • bunchOfAccounts
  • XYZControllerForEfficientStorageOfStrings
Do not refer to a grouping of accounts as an accountList unless it's actually a List. The word list means something specific to programmers. If the container holding the accounts is not actually a List, it may lead to false conclusions. So accountGroup or bunchOfAccounts or just plain accounts would be better.

This might be stylistic preferences, but accountsList is easier to read and write than bunchOfAccounts: it's shorter and has fewer words - it is more concise. If by looking at word List the first thing you're thinking is java.util.List, then you might need some time away from Java. Touch some grass, write some Haskell.

XYZControllerForEfficientStorageOfStrings - this is not a name - it's an essay. It tells the whole story of (web) application working with strings and storing them efficiently.

Martin states that acronyms and word shortenings are bad, but doesn't see the problem in a name that has 7 words and 40 characters in it.

There is a simple solution to this - comments that expand acronyms and explain shortenings. But because Martin believes that comments are a failure, he has to insist on using essays as a name.

By the end of the chapter he finally mentions:

Shorter names are generally better than longer ones, so long as they are clear.

...The resulting names are more precise, which is the point of all naming.

These are really good points! But most of the examples in the chapter are not aligned with them. And I don't think "short and concise naming" is a takeaway people are getting from reading this chapter.

If anything, in most examples he proposed to replace short (albeit cryptic) names with 3-4 word long slugs.

Opinion: Code Conventions for the Java Programming Language
Pascal and Camel case in Java was proposed in Sun's Java Style as a way to enforce "names should be short yet meaningful".

The idea being that ItIsReallyUncomfortableToReadLongSentencesWrittenInThisStyle. Hence people will be soft forced to limit the size of names.

The assumption was wrong.

Add meaningful context

I believe this was my first big WTF moment in the book:

Original Code:
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);Instead of printing, the method should just return guessMessage String result
     
     
}
Proposed rewrite:
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";
    }
}

In no way the second option is better than the first one. The original has only one problem: side-effects. Instead of printing to the console it should have just return String.

And that is it:

  • 1 method, 20 lines of code, can be read top to bottom,
  • 3 mutable local variables to capture local mutable state that can not escape.
  • It is thread-safe. I's impossible to misuse this API.

Second option:

  • 5 methods, 40 lines of code, +1 new class with mutable state.
  • Because it's a class, the state can escape and be observed from the outside.
  • Which makes it not thread safe.

The second option introduced more code, more concepts and more entities, introduced thread safety concerns.. while getting the exactly same results.

It also violates one of the rules laid out in the chapter about method names: "Methods should have verb or verb phrase names". thereIsOneLetter() is not really a verb or a verb phrase.

If I'll try to be charitable here: Martin is creating internal Domain Specific Language(DSL) for a problem.

A good indicator of an internal DSL: there are parts of API that doesn't make sense outside specific context/grammar.

For example, new GuessStatisticsMessage().thereAreNoLetters() looks weird and doesn't make sense.

On the other hand, language consturcts are presented in a somewhat declarative style:

private void thereAreNoLetters() {
    number = "no";
    verb = "are";
    pluralModifier = "s";
}

private void thereIsOneLetter() {
    number = "1";
    verb = "is";
    pluralModifier = "";
}

I hope you agree that methods like this is not a typical Java. If anything, this looks closer to typical ruby.

But ultimately, object-oriented programming and domain-specific languages are unnessary for the simple task. In a powerful-enough language, keeping this code procedural gives the result that is short and easy to understand:

def formatGuessStatistics(candidate: Char, count: Int): String = {   
   count match {
       case i if i < 0 => throw new IllegalArgumentException(s"count=$count: negative counts are not supported")
       case 0 => s"There are no ${candidate}s"
       case 1 => s"There is 1 ${candidate}"
       case x => s"There are $x ${candidate}s"
   }
}