How often do you clean up your code and how often do you do the code refactoring? If you have no idea what is code refactoring, it is a process to change the existing code to be more readable, flexible, maintainable, and extendable without changing the existing functionality.
Why code refactoring?
It's easier to explain why we need code refactoring by giving an example. The example below is showing an Animal
class with a function that will take an AnimalType
as a parameter. If you pass in AnimalType.Cat
, it will return you Miao
and the same to the dog.
public class Animal {
public string Sound(AnimalType animalType) {
if (animalType == AnimalType.Cat) {
return "Miao";
} else if (animalType == AnimalType.Dog) {
return "Woof";
}
return "";
}
}
So what's the problem with this class
?
If I need to add 10 more animals' sounds into this class, I need to write 10 if-statements. Like the example below.
public class Animal {
public string Sound(AnimalType animalType) {
if (animalType == AnimalType.Cat) {
return "Miao";
} else if (animalType == AnimalType.Dog) {
return "Woof";
} else if (animalType == AnimalType.Bird) {
return "Chick";
} // else if () x 7 more...
return "";
}
}
You will realize that the function will become bloated or some people call it a fat function
. You might think: ok, it's just a function and I don't find an issue extending it. I just have to keep adding the else-if
statement and it will work like a charm.
You probably are right when this function is small. What if after the function has been added 100 over sounds into it, and you are told to implement another function called Fly
? How are you going to handle the new function? Write another if
statement? How do you make sure that you don't miss an animal type and also implementation for the different animals will have different ways to fly is correctly implemented?
public class Animal {
public string Sound(AnimalType animalType) {
// sound implementation
}
public void Fly(AnimalType animalType) {
if (animalType == AnimalType.Cat || animalType == AnimalType.Dog) {
// this if statement might go wrong, because not every animal can fly.
throw new InvalidOperationException("This animal cannot fly!!");
} else if (animalType == AnimalType.Bird) {
// fly implementation
}
}
}
So yea, the above implementation is not sustainable. First of all, the above implementation is hard to read. If I were to find an animal type to change the way it sound
, I need to take more time to search simply because there are too many if
statement. The same goes for removing or adding a new animal type.
For the above implementation, you can do it better by creating an abstract animal
class with a must override Sound
function. Adding abstract
to a class to prevent the Animal
class from being instantiated. At the same time, you can create an animal class by implementing the Sound
function, and yet you won't miss any animal type that is not implementing the Sound
function. By doing so, your class will look slicker, thinner, and easier to read.
public abstract class Animal {
public abstract string Sound();
}
public class Cat: Animal {
public override string Sound() {
return "Miao";
}
}
public class Dog: Animal {
public override string Sound() {
return "Woof";
}
}
// same for the rest of the animals
You might have a question like this: but I would like to get the animal sound by using animal type. Otherwise, I will use the if
statement again to instantiate the animal class and the code will be duplicated everywhere.
You are right, we still need to use the if
statement to instantiate the class. But we will use a simple factory design pattern to encapsulate the implementation.
public class AnimalFactory {
public static Create(AnimalType animalType) {
if (animalType = AnimalType.Cat) return new Cat();
if (animalType = AnimalType.Dog) return new Dog();
// implementation for other animals...
throw new InvalidOperationException("Invalid animal type!");
}
}
// usage
Animal cat = AnimalFactory.Create(AnimalType.Cat);
cat.Sound(); // Miao
You will probably ask: how about the Fly
function. Not every animal can fly, right?
You are right, not every animal can fly, therefore it's not a good idea to implement the Fly
function to all the animals. Otherwise, you will have to implement 100x functions if you have 100 types of animals. You probably can create an interface for the IFlyableAnimal
. This idea is coming from the SOLID principle - Interface Segregation Principle. See the example below:
public interface IFlyableAnimal {
void Fly();
}
public class Bird: Animal, IFlyableAnimal {
public override string Sound() {
return "Chick";
}
public void Fly() {
// some fly implementation
}
}
// usage
Animal bird = AnimalFactory.Create(AnimalType.Bird);
bird.Sound(); // Chick
if (bird is IFlyableAnimal) {
bird.Fly();
}
After looking at the example above, the class becomes thinner and extendable. Whenever we need to create an AnimalType
, we just have to extend the abstract Animal
class and if the animal is flyable, we just need to implement the IFlyableAnimall
interface. The compiler will help to take care of the implementation because if the developer doesn't implement the function, the compiler will show an error. With this design, we are in compliance with the SOLID principle - open-close concept.
When should you refactor your code?
The best time to refactor your code probably is right after you finish writing a feature. You will have a code review session with your peer and discuss the best way to structure your code.
However, you don't always have the luxury to do code refactoring right after the implementation. QA is awaiting your implementation and the product owner doesn't allow you to get extra time to refactor your code because you need to deliver your implementation with the given time.
But It's ok. You can do the code refactoring when you are told to enhance features in the same piece of code again. From there, you can request more time from the product owner by telling them the current code base requires code refactoring, and hence, you need more time.
Conclusion
Practicing code refactoring will brings you or your team a couple of benefits. One of them would be the code looks cleaner and easier to read. When the next developer is told to enhance a brilliant feature in the same piece of code, the development will be easier because it's extendable. At the same time, the development speed will be a lot faster.
On the other hand, if the code is not refactored when it's needed, the next developer will be suffering reading the code, resulting in taking more time to read and enhance a simple enhancement. When worse comes to worst, you will find errors here and there simply because the code is not sustainable.
There's a saying:
The true professional knows that delivering function at the expense of structure is a fool’s errand. It is the structure of your code that allows it to be flexible.
If you compromise the structure, you compromise the future.
The fundamental assumption underlying all software projects is that software is easy to change. If you violate this assumption by creating inflexible structures, then you undercut the economic model that the entire industry is based on.
In short: You must be able to make changes without exorbitant costs.
~ The clean coder by Robert C. Martin
Last but not least: happy coding and happy learning 😆
Top comments (0)