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.
Tuesday, February 9, 2016
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)
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)
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
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.
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:
The situation can happen in reverse as well.
Consider the following example:
Subscribe to:
Posts (Atom)
