One Thing: Extract till you Drop.

Posted by Uncle Bob on 09/11/2009

For years authors and consultants (like me) have been telling us that functions should do one thing. They should do it well. They should do it only.

The question is: What the hell does “one thing” mean?

After all, one man’s “one thing” might be someone else’s “two things”.

Consider this class:

 class SymbolReplacer {     protected String stringToReplace;     protected List<String> alreadyReplaced = new ArrayList<String>();      SymbolReplacer(String s) {       this.stringToReplace = s;     }      String replace() {       Pattern symbolPattern = Pattern.compile("\\$([a-zA-Z]\\w*)");       Matcher symbolMatcher = symbolPattern.matcher(stringToReplace);       while (symbolMatcher.find()) {         String symbolName = symbolMatcher.group(1);         if (getSymbol(symbolName) != null && !alreadyReplaced.contains(symbolName)) {           alreadyReplaced.add(symbolName);           stringToReplace = stringToReplace.replace("$" + symbolName, translate(symbolName));         }       }       return stringToReplace;     }      protected String translate(String symbolName) {       return getSymbol(symbolName);     }   }

It’s not too hard to understand. The replace() function searches through a string looking for $NAME and replaces each instance with the appropriate translation of NAME. It also makes sure that it doesn’t replace a name more than once. Simple.

Of course the words “It also…” pretty much proves that this function does more than one thing. So we can probably split the function up into two functions as follows:

   String replace() {       Pattern symbolPattern = Pattern.compile("\\$([a-zA-Z]\\w*)");       Matcher symbolMatcher = symbolPattern.matcher(stringToReplace);       while (symbolMatcher.find()) {         String symbolName = symbolMatcher.group(1);         replaceAllInstances(symbolName);       }       return stringToReplace;     }      private void replaceAllInstances(String symbolName) {       if (getSymbol(symbolName) != null && !alreadyReplaced.contains(symbolName)) {         alreadyReplaced.add(symbolName);         stringToReplace = stringToReplace.replace("$" + symbolName, translate(symbolName));       }     }

OK, so now the replace() function simply finds all the symbols that need replacing, and the replaceAllInstances() function replaces them if they haven’t already been replaced. So do these function do one thing each?

Well, the replace() compiles the pattern and build the Matcher() Maybe those actions should be moved into the constructor?

 class SymbolReplacer {     protected String stringToReplace;     protected List<String> alreadyReplaced = new ArrayList<String>();     private Matcher symbolMatcher;     private final Pattern symbolPattern = Pattern.compile("\\$([a-zA-Z]\\w*)");      SymbolReplacer(String s) {       this.stringToReplace = s;       symbolMatcher = symbolPattern.matcher(s);     }      String replace() {       while (symbolMatcher.find()) {         String symbolName = symbolMatcher.group(1);         replaceAllInstances(symbolName);       }       return stringToReplace;     }      private void replaceAllInstances(String symbolName) {       if (getSymbol(symbolName) != null && !alreadyReplaced.contains(symbolName)) {         alreadyReplaced.add(symbolName);         stringToReplace = stringToReplace.replace("$" + symbolName, translate(symbolName));       }     }      protected String translate(String symbolName) {       return getSymbol(symbolName);     }   }

OK, so now certainly the replace() function is doing one thing? Ah, but I see at least two. It loops, extracts the symbolName and then does the replace. OK, so how about this?

   String replace() {       for (String symbolName = nextSymbol(); symbolName != null; symbolName = nextSymbol())         replaceAllInstances(symbolName);        return stringToReplace;     }      private String nextSymbol() {       return symbolMatcher.find() ? symbolMatcher.group(1) : null;     }

I had to restructure things a little bit. The loop is a bit ugly. I wish I could have said for (String symbolName : symbolMatcher) but I guess Matchers don’t work that way.

I kind of like the nextSymbol() function. It gets the Matcher nicely out of the way.

So now the replace() and nextSymbol() functions are certainly doing one thing. Aren’t they?

Well, I suppose I could separate the loop from the return in replace().

   String replace() {       replaceAllSymbols();       return stringToReplace;     }      private void replaceAllSymbols() {       for (String symbolName = nextSymbol(); symbolName != null; symbolName = nextSymbol())         replaceAllInstances(symbolName);     }

I don’t see how I could make these functions smaller. They must be doing one thing. There’s no way to extract any other functions from them!

Uh… Wait. Is that the definition of one thing? Is a function doing one thing if, and only if, you simply cannot extract any other functions from it? What else could “one thing” mean? After all, If I can extract one function out of another, the original function must have been doing more than one thing.

So does that mean that for all these years the authors and consultants (like me) have been telling us to extract until you can’t extract anymore?

Let’s try that with the rest of this class and see what it looks like…

 class SymbolReplacer {     protected String stringToReplace;     protected List<String> alreadyReplaced = new ArrayList<String>();     private Matcher symbolMatcher;     private final Pattern symbolPattern = Pattern.compile("\\$([a-zA-Z]\\w*)");      SymbolReplacer(String s) {       this.stringToReplace = s;       symbolMatcher = symbolPattern.matcher(s);     }      String replace() {       replaceAllSymbols();       return stringToReplace;     }      private void replaceAllSymbols() {       for (String symbolName = nextSymbol(); symbolName != null; symbolName = nextSymbol())         replaceAllInstances(symbolName);     }      private String nextSymbol() {       return symbolMatcher.find() ? symbolMatcher.group(1) : null;     }      private void replaceAllInstances(String symbolName) {       if (shouldReplaceSymbol(symbolName))         replaceSymbol(symbolName);     }      private boolean shouldReplaceSymbol(String symbolName) {       return getSymbol(symbolName) != null && !alreadyReplaced.contains(symbolName);     }      private void replaceSymbol(String symbolName) {       alreadyReplaced.add(symbolName);       stringToReplace = stringToReplace.replace(         symbolExpression(symbolName),          translate(symbolName));     }      private String symbolExpression(String symbolName) {       return "$" + symbolName;     }      protected String translate(String symbolName) {       return getSymbol(symbolName);     }   }

Well, I think it’s pretty clear that each of these functions is doing one thing. I’m not sure how I’d extract anything further from any of them.

Perhaps you think this is taking things too far. I used to think so too. But after programming for over 40+ years, I’m beginning to come to the conclusion that this level of extraction is not taking things too far at all. In fact, to me, it looks just about right.

So, my advice: Extract till you just can’t extract any more. Extract till you drop.

After all, with modern tools it takes very little time. It makes each function almost trivial. The code reads very nicely. It forces you to put little snippets of code into nicely named functions. And, well gosh, extracting till you drop is kind of fun!

Comments

Leave a response