Code Kata Walkthrough - Part II

Let’s pick up where we left off in the last post. We were in the GREEN state with a string calculator that was able to sum an empty string, one number, and two numbers separated by a comma. Looking at the code kata, the next requirement is that we handle an unkown amount of numbers.

First, we will write our failing test case to get us back in the RED state:

it("should return the sum of three numbers when passed three numbers separated by a comma", function() {
    expect(calc.add("5,4,3")).toBe(12);
});

The actual result of this test case is 9, because we hardcoded the sum to be nums[0] + nums[1]. This obviously does not work with more than two numbers. Let’s fix this and get back in the GREEN state:

Calculator.prototype.add = function(str) {
    if(str.length == 0) {
        return 0;
    } else if(str.includes(",")) {
        nums = str.split(",");
        sum = 0;
        for (i = 0; i < nums.length; i++) {
            sum += parseInt(nums[i]);
        }
        return sum;
    } else {
        return parseInt(str);
    }
}

So we changed our hardcoded indicies into a for loop that can handle an unknown amount of numbers. Now let’s consider refactoring. This method still seems pretty small and concise to me. There’s no duplicated code. Let’s move onto the next requirement, which is to handle new line characters as delimiters in addition to commas. Our first test case looks like this:

it("should return the sum of two numbers when passed two numbers separated by a new line", function() {
    expect(calc.add("6\n4")).toBe(10);
});

We are back in the RED state. Let’s fix it:

Calculator.prototype.add = function(str) {
    if(str.length == 0) {
        return 0;
    } else if(str.includes(",")) {
        nums = str.split(",");
        sum = 0;
        for (i = 0; i < nums.length; i++) {
            sum += parseInt(nums[i]);
        }
        return sum;
    } else if(str.includes("\n")) {
        nums = str.split("\n");
        sum = 0;
        for (i = 0; i < nums.length; i++) {
            sum += parseInt(nums[i]);
        }
        return sum;
    }
    else {
        return parseInt(str);
    }
}

And we’re back in the GREEN state. Now let’s consider the REFACTOR phase. This time we definitely have some refactoring to do. There is clearly duplicated logic that needs to be cleaned up. A quick side note on refactoring.

ALWAYS REFACTOR IN THE GREEN STATE

It is very important to only refactor when all of your test cases are passing, and you know that your code is in a stable state. By refactoring in the GREEN state, we can slowly change our code and confirm that it is working along the way. With each small change during the refactoring process, we want to execute our unit tests to make sure that we did not break anything. This makes it very easy to determine which change caused your code to turn RED, and fix it. If you refactor in the RED state, then you have no way to determine if your code is actually working. You’ll end up trying to refactor while also trying to fix the test case, and you will continue to dig yourself into a deeper hole. Get your test cases passing, then refactor. If you end up in the RED, then undo until the last GREEN state and try again. Don’t dig yourself deeper.

With that said, let’s take a look at the refactored code. This post would be entirely too long if I showed you every code change I make in the refactoring process and when I am executing my tests, so I will just show you the final code. But keep in mind that I did this step by step and after every change that I made, I executed my tests to make sure that they were still passing.

Calculator.prototype.add = function(str) {
    if(str.length == 0) {
        return 0;
    } else if(str.includes(",") || str.includes("\n")) {
        nums = str.split(/[,\n]/);
        sum = 0;
        for (i = 0; i < nums.length; i++) {
            sum += parseInt(nums[i]);
        }
        return sum;
    } else {
        return parseInt(str);
    }
}

I consolidated the two else if statements into one that includes both the comma and new line. I then use a regex to split the string on whichever one is present. I’ll wrap this up for now. Until next time!

Written on October 24, 2017