Tuesday, February 9, 2016

Refactoring - Decompose Conditional

Simple code is easier to understand and maintain. One of the aim of refactoring techniques is to be sure the code is as simple as it can be.

This refactoring can be applied in the case of a series of conditions that might be to difficult to follow and easy to get it wrong.

Let's write some code that is calculating the taxes one has to pay. In our example, there are 5 levels of tax, depending on the income and number of kids.

if (income < 1000) && (kids == 0) { //level 1
    tax = income * 0.2
} else if (income < 1000) && (kids > 0) { //level 2
    tax = income * 0.1
} else if (income < 2000) && (kids == 0) { //level 3
    tax = 200 + (income - 10000) * 0.3
} else if (income < 2000) && (kids > 0) { //level 4
    tax = 100 + (income - 1000) * 0.2
} else {
    tax = 400 + (income - 2000) * 0.4 //level 5
}

It is not easy to follow the code, let alone change. Imagine what would have happened if there were 20 levels instead of 5.

We can simplify it by extracting a function for each conditional and each branch:

if checkLevel1() {
    tax = computeTaxLevel1()
} else if checkLevel2() {
    tax = computeTaxLevel2()
} else if checkLevel3() {
    tax = computeTaxLevel3()
} else if checkLevel4() {
    tax = computeTaxLevel1()
} else {
    tax = computeTaxLevel5()
}

Of course, each of the function are defined as follows

func checkLevel1() -> Bool {
   return (income < 1000) && (kids == 0)
}

func computeTaxLevel1() -> Double {

    return income * 0.2
}

The other ones are similar.

Now the code is clear and also for changing one level, one knows exactly where to do it: in the function corresponding to that level.

Monday, February 8, 2016

Refactoring - Consolidate Duplicate Conditional Fragments

Apply this refactoring when the same code is duplicated in the branches of a if/else or switch statement.

Move the common code outside of the statement.

Let's consider the following code that is creating the full name using the first and last name:

var firstName, lastName: String?

//....

var fullName = ""
if firstName == nil {
    if lastName == nil {
        fullName = "unknown name"
        print(fullName)
    } else {
        fullName = lastName!
        print(fullName)
    }
} else {
    if lastName == nil {
        fullName = firstName!
        print(fullName)
    } else {
        fullName = firstName! + " " + lastName!
        print(fullName)
    }
}

We can easily notice that for each branch the full name is printed.

We can move it outside the branch as follows:

var fullName = ""
if firstName == nil {
    if lastName == nil {
        fullName = "unknown name"
    } else {
        fullName = lastName!
    }
} else {
    if lastName == nil {
        print(fullName)
    } else {
        fullName = firstName! + " " + lastName!
    }
}
print(fullName)

Friday, February 5, 2016

Refactoring - consolidate conditional expressions

This refactoring is applied when there are multiple conditional tests that have the same outcome. You can combine them in the same condition.

Consider the following piece of code that is checking if the length of a username is correct (that is between 5 and 20).

let username:String?

username = "test"

var lengthCorrect = true
if username == nil {
    lengthCorrect = false
} else if username?.characters.count < 5 {
    lengthCorrect = false
} else  if username?.characters.count > 20 {
    lengthCorrect = false
}

As you can see all the tests have the same outcome: length is incorrect

The first step would be to combine all the checks into the same if statement:

if (username == nil) || (username?.characters.count < 5) 
   || (username?.characters.count > 20) {
    lengthCorrect = false
}

Of course, the next step is to extract this code into its own function:

func checkUsernameLength(username:String?) -> Bool {
    return !((username == nil) || (username?.characters.count < 5) 
           || (username?.characters.count > 20))
}

Now, the code to check the the validity of the username is simply:

var lengthCorrect = checkUsernameLength(username)


Thursday, February 4, 2016

Refactoring - Replace Type Code with Polymorphism

This type of refactoring applies when a class has a property that represent a type of some kind and the code is depending on it. This usually manifests through if/else or switch statements.

Let’s continue the example of the Karateka but simplify it a bit.

Wednesday, February 3, 2016

Refactoring - Replace type code with enumeration

Karate is a Japanese style of martial arts that rewards its participant attaining a certain level of sport mastery with color belts.

Let's have a class for a Karateka, that is a Karate practitioner.

let kWhiteBelt = 0, kYellowBelt = 1, kOrangeBelt = 3 
let kGreenBelt = 4, kBlueBelt = 5, kBrownBelt = 6, kBlackBelt = 6

class Karateka {
    var name = ""
    var belt = kWhiteBelt
    
    func isBeginner () -> Bool {
        return belt == kWhiteBelt
    }
    
    func isMaster () -> Bool {
        return belt == kBlackBelt
    }
}

The code works fine but it is not an elegant one.

Tuesday, February 2, 2016

Refactoring: Replace magic numbers with constants

Magic numbers are values with unexplained meaning and multiple occurrences in the code, according to Wikipedia. Here is an example:

func validateUsername(username:String?) {
    if let username = username {
        let length = username.characters.count
        if length <= 3 {
            print("Username too short.")
            print("It needs to be longer than 3.")
        } else if length >= 10 {
            print("Username too long.")
            print("It needs to be shorter than 10.")
        } else {
            print("username valid")
        }
    } else {
        print("username cannot be nil")
    }
}

Monday, February 1, 2016

Refactoring: Change bidirectional association to unidirectional

In the previous post we analyzed how, sometime, adding another association between the objects can drastically improve performance and reduce the amount of code we need to write.

The situation can happen in reverse as well.

Consider the following example: