A Real Man Dares to Delete Code

I have seen a lot during the last couple of years, but I have never seen a single developer who deleted working code. Writers are known to delete complete chapters, artists to start from scratch all over again, but for unknown reasons, developers just don’t delete their code and start over. However, there are situations when deleting code is the only good option. So, there is something new we have to learn.

Sometimes the code is so unmanageable that deleting and rewriting it makes much more sense than fixing it. Before you start deleting code, there is something else worth mentioning. The code itself might be ugly and unmanageable, but it has a unique property which must be preserved no matter what happens afterwards: its functionality.

Quite recently, I was writing an application by copy-pasting snippets from old projects and stackoverflow, and fixing the occurring errors. After a while, the application kind of did what I wanted, but I couldn’t figure out how to add the next functionality. I was pretty sure that the existing code had to be refactored, but the refactoring looked like a one or two day long job. The existing code quality was quite similar to the quality of the following class, but on a larger scale:

package dojo.legacy.currencyformat;

public class HungarianCurrencyFormatter {
  public static void main(String[] args) {
    if (args.length == 1)
      if (!args[0].contains(".")) {
        char[] n = args[0].toCharArray();
        final StringBuilder sb = new StringBuilder(String.copyValueOf(n));
        for (int j = 1; j <= (n.length/3); j++)
            if (n.length - 3 * j != 0) {
              sb.insert(n.length - 3 * j, " ");
            }
        System.out.println("Your number (" + args[0]
           + ") is written like this in Hungarian: \""
             + sb.toString() + "\"");
      } else {
        StringBuffer sb = new StringBuffer();
        final StringBuffer sb_2 = new StringBuffer(args[0].split("\\.")[0]);
        for (int i = 1; i <= (args[0].split("\\.")[0].length() / 3); i++) {
          if (args[0].split("\\.")[0].length() - 3 * i !=0)
            sb_2.insert(args[0].split("\\.")[0].length()-3*(i), " ");
        }
        System.out.println("Your number (" + args[0] +
          ") is written like this in Hungarian: \"" + sb.append(sb_2.toString())
            .append(",").append(args[0].split("\\.")[1]).toString() + "\"");
        return;
      }
  }
}

So, instead of trying to refactor the code, I decided to rewrite it. In order to do that, I needed to preserve the functionality - because that knowledge was still valuable - so I wrote test cases and fully covered the application:

public class HungarianCurrencyFormatterTest {
  private static final String NL = System.getProperty("line.separator");
  private PrintStream originalStandardOutput;
  private OutputStream standardOutput;

  @Before
  public void setUp() {
    redirectStandardOutput();
  }

  @After
  public void tearDown() {
    resetStandardOutputRedirection();
  }

  @Test
  public void shouldReplaceDotWithComa() throws IOException {
    HungarianCurrencyFormatter.main(new String[]{"100.1"});
    assertEquals(
      "Your number (100.1) is written like this in Hungarian: \"100,1\"" + NL,
      standardOutput.toString());
  }

  @Test
  public void shouldReplaceDotWithComaAlsoForNumberGreaterThanThousand()
      throws IOException {
    HungarianCurrencyFormatter.main(new String[]{"1000.1"});
    assertEquals(
      "Your number (1000.1) is written like this in Hungarian: \"1 000,1\"" + NL,
      standardOutput.toString());
  }
  // more test cases (check the github repository)
}

Until this point the procedure is exactly the same as when we do refactoring, but instead of starting to apply the refactoring techniques and figuring out how to do it properly, I simply deleted the code, started to run the test cases and wrote the code until all the existing test cases turned green.

During the procedure, I realized that certain test cases didn’t make any sense, because the code did something else than I expected. So I found a couple of defects, but I kept the test cases as they were, because the defects were part of the original functionality, which I intended to keep, even though it was faulty. I marked them and fixed them after I was done.

My solution (available on github):

public class HungarianCurrencyFormatter {
  private static Locale HUN = new Locale("hu", "HU");
  public String format(String number) {
    NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(HUN);
    String currencyFormattedNumber =
      currencyFormatter.format(Double.parseDouble(number));
    String formattedNumberWithProperGroupSeparator =
      replaceLocaleSpecificGroupSeparatorWithSpace(currencyFormattedNumber);
    return removeFtSuffix(formattedNumberWithProperGroupSeparator);
  }

  private String removeFtSuffix(String number) {
    return number.substring(0, number.indexOf(" Ft"));
  }

  private String replaceLocaleSpecificGroupSeparatorWithSpace(String number) {
    char separator =
      DecimalFormatSymbols.getInstance(HUN).getGroupingSeparator();
    return number.replace(separator, ' ');
  }

  public static void main(String[] args) {
    if (args.length == 1) {
      HungarianCurrencyFormatter formatter = new HungarianCurrencyFormatter();
      System.out.printf(
        "Your number (%s) is written like this in Hungarian: \"%s\"%n",
        args[0], formatter.format(args[0]));
    }
  }
}

In retrospect, it was a good decision, because I finished the whole thing in half a day instead of two days. It was fun and the work went smoothly. I had no difficulties during the work, because I learnt from the previous code how to solve the technical difficulties, and the test cases kept me on the right track.

This experience reminded me of the spike solution from XP. It is a very powerful, but an unfortunately underrated practice: the team shall put something together which helps them explore potential problems and solutions regarding the upcoming work. The spike shall be deleted afterwards, because its mere purpose is to gain knowledge. After having this knowledge, the test driven development is easier, because you don’t have to think about solutions, so you can focus on writing the code instead. So, there is a connection between the spike solution and TDD. We rarely created spikes, we immediately jumped into TDD, and spent an awful lot of time figuring out stuff. I guess that’s why we were going that slow: we were doing two things at the same time.

As you can see, deleting code is not a crime. It is another tool we can use - just don’t forget to write the test cases beforehand. Without them, one can make me some serious damage.

If you want to try out how it feels to write some tests and delete the code afterwards, clone our coding dojo repository and check out the the real man dares to delete code exercise. Have fun!


comments powered by Disqus