Database TDD Part 8: Zealotry

by Jeff Langr

October 21, 2005

Some people purport that you can keep the “cost of change” curve relatively flat. In other words, you can introduce unplanned features any time in the future as cheaply as if you had inserted them today. That’s a bold statement. I agree with it if and only if you refactor with extreme vigilance.

To be successful, you will need to become a refactoring zealot. This means you learn to understand what duplication really means, and ferret it out at every possible opportunity. Every TDD cycle includes writing test code, writing production code, then refactoring. Any new code introduced in the first two parts of the cycle must not degrade the system’s current clean design. Otherwise you cannot claim that the cycle is complete.

With the 10-minute bit of code I wrote to support saving and retrieving users, I’ve already spotted gobs of duplication and expressiveness problems. As I refactor more, I seem to spot even more. It’s like clearing the underbrush beneath trees in your yard. Picking up the larger branches on top alerts you to the presence of the smaller branches snaking through the grass. They too have to be cleaned up if you want to keep your grass looking pristine.

Yesterday I mentioned my desire to use static import. That was my first subject of refactoring today. I moved all of the classes into appropriate packages. I even renamed a few classes (plus one method), and at least one class I renamed more than once! I initially moved JdbcAccess into a package called jdbc, then renamed it to Access (to remove the redundancy between the class name and the package). But then I figured I’d be better off with the generic package name persistence, so I renamed the class back to JdbcAccess. A bit of unfortunate thrashing, indeed, but it was a cheap exercise. The beauty of Eclipse and other IDEs is that you don’t have to have a perfect name first. You can continually improve identifier names as you discover more about what they represent. Very powerful! This makes the third simple design rule (code should be expressive, basically) easy and even fun to tackle.

The new project organization:

  • domain.User

  • domain.UserTest

  • persistence.JdbcAccess

  • persistence.JdbcAccessTest

  • persistence.JdbcAccessExceptionsTest

  • persistence.JdbcException

  • util.StringUtil

  • util.StringUtilCommaDelimitTest

I still don’t like some of these class names. Suggestions?

So now, I could use the static import facility:

    package util;
    
    import junit.framework.*;
    import static util.StringUtil.commaDelimit;
    
    public class StringUtilCommaDelimitTest extends TestCase {
       public void testDegenerate() {
          assertEquals("", commaDelimit(new String[] {}));
       }
    
       public void testOneEntry() {
          assertEquals("a", commaDelimit(new String[] {"a"}));
       }
    
       public void testTwoEntries() {
          assertEquals("a,b", commaDelimit(new String[] {"a", "b"}));
       }
    
       public void testEmbeddedCommas() {
          // not very well defined yet! don't care so far.
          assertEquals("a,,,b", commaDelimit(new String[] {"a,", ",b"}));
       }
    }

I think this is a perfect use of the (relatively) new J2SE 5.0 feature. Eclipse italicizes the method call, so it’s clear that it’s a static method.

The next part of today’s work (I was only up to about 3 minutes worth of refactoring at this point) was eliminating the duplication inherent in putting together a value list for the insert statement. Step one was to do an extract method refactoring to make it clear what was getting cleaned up. In User:

       ...
       public void save() {
          new JdbcAccess().execute("insert into " + TABLE_NAME + " ("
                + User.createColumnList() + ") values (" +
                 createValuesList() + ")");
       }
    
       private String createValuesList() {
          return String.format("'%s', '%s'", name, password);
       }
       ...

Step two: enhance my string utility method, looking for duplication along the way. There was lots of it. Here’s what I ended up with:

User.java

       public void save() {
          new JdbcAccess().execute("insert into " + TABLE_NAME + " ("
                + User.createColumnList() + ") values (" +
                 createValuesList() + ")");
       }
    
       private String createValuesList() {
          Transformer ticWrapper = new Transformer() {
             public String transform(String input) {
                return StringUtil.wrap(input, '\'');
             }};
          return StringUtil.commaDelimit(new String[] { name, password }, ticWrapper);
       }
    
       private static String createColumnList() {
          return StringUtil.commaDelimit(columns);
       }

StringUtilCommaDelimitTest.java

    package util;
    
    import junit.framework.*;
    import static util.StringUtil.commaDelimit;
    
    public class StringUtilCommaDelimitTest extends TestCase {
       ...
       public void testCommaDelimitWithCallback() {
          Transformer duplicator = new Transformer() {
             public String transform(String input) {
                return input + input;
             }};
          assertEquals("aa,bb", commaDelimit(new String[] {"a", "b"}, duplicator));
       }
    }

Transformer.java

    package util;
    
    public interface Transformer {
       String transform(String input);
    }

StringUtilWrapTest.java

    package util;
    
    import junit.framework.*;
    import static util.StringUtil.wrap;
    
    public class StringUtilWrapTest extends TestCase {
       public void testDegenerate() {
          assertNull(wrap(null, '\''));
       }
    
       public void testSingleCharacterString() {
          assertEquals("*a*", wrap("a", '*'));
       }
    
       public void testMultipleCharacterString() {
          assertEquals("-abc-", StringUtil.wrap("abc", '-'));
       }
    }

StringUtil.java

    package util;
    
    
    public class StringUtil {
       public static String commaDelimit(String[] strings, Transformer transformer) {
          StringBuilder builder = new StringBuilder();
          for (int i = 0; i < strings.length; i++) { 
             if (i > 0)
                builder.append(',');
             builder.append(transformer.transform(strings[i]));
          }
          return builder.toString();
       }
    
       public static String commaDelimit(String[] strings) {
          final Transformer nullTransform = new Transformer() {
             public String transform(String input) {
                return input;
             }
          };
          return commaDelimit(strings, nullTransform);
       }
    
       public static String wrap(String source, char wrapper) {
          if (source == null)
             return null;
          StringBuilder builder = new StringBuilder();
          builder.append(wrapper);
          builder.append(source);
          builder.append(wrapper);
          return builder.toString();
       }
    }

Interestingly, eliminating duplication has meant more code up to this point.

Also, it’s inevitable that you’ll have duplication at some level. Here it exists in the wrap method. I’ve chosen to foist the duplication from a volatile area (code in User) to the most abstract level possible, this wrap method. I don’t see this method as ever having to change.

There’s still SQL-related code in the domain class. That might be my next step, unless someone has another thought.

Comments

Anonymous October 21, 2005 at 12:44pm

You are using StringBuilders a lot in these exercises. Given that they are a lot less readable than string concatenation, it seems like a performance optimization to me. In point of fact, the implementation of the wrap method should be just as efficient written as

return wrapper + string + wrapper;

because of compiler optimization.


Anonymous October 21, 2005 at 12:45pm

sorry, that should have been

return string == null ? null : wrapper + string + wrapper;


Jeff Langr November 1, 2005 10:38am

I prefer the guard clause when it comes to something like this. While I don’t mind using the ternary operator at times, I’m picky about when I introduce it. Here I think it just makes things a bit more confusing.


Share your comment

Jeff Langr

About the Author

Jeff Langr has been building software for 40 years and writing about it heavily for 20. You can find out more about Jeff, learn from the many helpful articles and books he's written, or read one of his 1000+ combined blog (including Agile in a Flash) and public posts.