In this article, we will explore refactoring dispensables in C#. We’ll focus on identifying code smells and enhancing our code through practical refactoring techniques.
Let’s start!
Identifying Dispensables in Code
Dispensables are pieces of code that serve no purpose, and removing them can improve code cleanliness. A clean codebase is easier to understand and maintain.
In this category, we can identify several anti-patterns:
- Duplicate code
- Lazy class
- Dead code
- Speculative generality
- Comments
Let’s explore methods for identifying these code smells and discuss solutions for refactoring dispensables!
Refactoring Dispensables of Duplicated Code
According to the DRY principle (Don’t Repeat Yourself), avoiding repeated code enhances code maintainability and reduces the risk of bugs. Developers can introduce duplicated code due to many reasons: a lack of code abstraction, parallel development efforts, or just simply copy-paste for a quicker implementation. Whatever the reason, we should always try not to add code that already exists.
Let’s consider an example and see how we can improve the quality of our code by getting rid of duplicated code.
First, let’s have a look at the Cat
class:
public class Cat { public ICollection<string> Toys { get; set; } public string GatherStrings() => string.Join(',', Toys); }
Next, let’s check the Dog
class:
public class Dog { public ICollection<string> Toys { get; set; } public string CompileToys() => string.Join(',', Toys); }
As we can see, the methods in the Cat
and Dog
classes use the same code to return toys as a single string, separated by commas.
When duplicated code occurs in many places, we often want to extract it and keep it in one place. For this purpose, we refactor our example by creating a new method and moving the code to this method. We call this treatment the extract method refactoring.
Assuming that our system will use this method not only for toys, we can separate it into an extension method so it is much easier to reuse.
Let’s see our new extension method:
public static class CollectionExtensions { public static string ToCommaSeparatedString(this ICollection<string> array) => string.Join(',', array); }
Let’s improve this further by using an abstract class, and we’ll name it the Animal
class. Abstract classes are a common base with shared functionality that multiple classes can inherit. Hence, we can easily encapsulate and reuse code.
Now, let’s move both the Toys
property and the ToCommaSeparatedString()
method to the Animal
class:
public abstract class Animal { public ICollection<string> Toys { get; set; } public string GetToysAsString() => Toys.ToCommaSeparatedString(); }
The Cat
and Dog
classes can now inherit from the Animal
class and do not need to replicate the same code anymore:
public class Cat : Animal { }
public class Dog : Animal { }
With the Animal
abstract class, every new derived class that shares the same functionality can avoid duplicated code.
Getting Rid of Lazy Classes
Each time we create a new class or method, our code becomes more complicated, and we must put more effort into maintaining it. Therefore, we should strive to keep our code straightforward. The lazy class code smell occurs when we have code without much-added value.
To illustrate the Lazy Class code smell, let’s look at a quick example. We have two classes, and the first one is the Ticket
class:
public class Ticket { public Price Price { get; set; } }
Next, let’s see the Price
class:
public class Price { public int Value { get; set; } }
The second class has only one property, specifying the value of the ticket. Perhaps there used to be something more – additional fields, properties, or methods, or maybe there were plans to develop this class, but the requirements have changed. Anyway, as of now, this class doesn’t serve a significant purpose, so there’s no reason to keep it.
Let’s see what our code will look like after a little cleaning:
public class Ticket { public int Price { get; set; } }
In this way, we easily reduce the complexity of our code by removing the redundant class.
In addition, it’s worth mentioning that the counterpart of the Lazy Class is the Primitive Obsession code smell, and it’s essential to exercise caution in both scenarios to maintain a well-balanced and optimized codebase.
Refactoring Dispensables of Dead Code/Speculative Generality
Now, let’s talk about two similar code smells. Dead code and speculative generality have the same problem – code is not used anywhere. The difference between these anti-patterns is the origin of the unused class or method.
Dead code refers to cases where something was used, but due to some changes, it is no longer. This may result, for example, from changing business requirements or refactoring. On the other hand, speculative generality refers to cases where we add code “for the future”. We don’t need it yet, but we will soon. Then, it turns out that some functionalities will not be implemented or implemented differently, and no one remembers the previously added method.
Let’s have a look at a quick example. We create a new Employee
class with two methods: GiveSalaryRaise()
and DecreaseSalary()
:
public class Employee { public int Salary { get; private set; } public void GiveSalaryRaise() { Salary = Salary * 2; } public void DecreaseSalary() { Salary = Salary * 0.75; } }
Now, let’s only invoke the DecreaseSalary()
method in the Program
class:
var employee = new Employee(); employee.DecreaseSalary();
We only care about decreasing the salary by using the DecreaseSalary()
method at this moment. We believe the salary increase feature provided by the GiveSalaryRaise()
method could be useful in the future, but we don’t need it for now, making it a dead code. It’s advisable not to leave unusable code for better maintenance.
Therefore, we need to remove the GiveSalaryRaise()
method for a cleaner code:
public class Employee { public int Salary { get; private set; } public void DecreaseSalary() { Salary = Salary * 0.75; } }
Fortunately, the code analysis in Visual Studio can indicate unused code and help us to manage dead code easily.
Replacing Comments With Good Naming
As a rule of thumb, it’s better to name our classes, methods, or variables well than add comments. If we keep an eye on proper naming, they should be redundant in most cases. Such code is easier to read and maintain, and it ensures that there are no bugs.
Let’s see a quick example illustrating how proper naming makes comments unnecessary. We have a HelperClass
class with a Generate()
method:
public class HelperService { //Method used to generate a new invoice. //The 'a' parameter represents customer for which the invoice should be created. public void Generate(string a) { } }
Without reading the comment, it’s challenging to discern the responsibility of this class, the function of the method, or the required parameters.
Next, let’s have a look at a better version:
public class InvoicingService { public void GenerateInvoiceFor(string customer) { } }
First, we change the name of the class to indicate that it operates on invoices. Additionally, we changed the name of the method and parameter – now it clearly states what it does and for whom.
Once the method’s name has been changed, there’s no need to add comments to describe what happens, as everything becomes self-explanatory.
Conclusion
In conclusion, we’ve looked at identifying code smells and refactoring dispensables in C#. Developers can enhance readability and reduce code complexity by identifying and eliminating these unnecessary elements.