From Chaotic If-Else to Elegant, Maintainable Class Structure
Leandro Siqueira
AI Evaluation Specialist | Multimodal Data Annotator | Software Development Expert | Empowering Professionals in Technical & Soft Skills
In the world of software development, there’s one concept that often differentiates novice developers from seasoned professionals: clean code. Written with clarity, structure, and an emphasis on maintainability, clean code is the backbone of sustainable and scalable systems.
However, transitioning from "dirty" code to clean code doesn't happen overnight. It requires practice, patience, and a mindset focused on quality.
In his seminal book Clean Code, Robert C. Martin emphasizes that well-written code is not just functional, but also understandable and easy to modify.
In this article, we’ll explore two code examples: one that embodies clean code principles and another that doesn’t. Through these examples, we’ll highlight how small decisions can impact the readability, maintainability, and even performance of your projects.
A Typical Dirty Code Example
Let’s begin with an example of bad code. Imagine we’re building a function to calculate tax on a sale, where there are three different types of taxes:
At first glance, the code seems fine. However, upon a more thorough analysis, a series of issues will be revealed that could cause headaches down the road.
First, the function CalculateTax and the taxType parameter, although simple, don’t provide enough context or clarity about their purpose.
?? What is a "taxType"?
It could refer to multiple kinds of taxes, but from this code alone, we don’t know what these "types" represent. Are they types of taxes based on regions, products, or something else? The naming is vague, and it’s difficult to understand the full context without looking elsewhere in the codebase.
?? What does CalculateTax mean?
This function might seem straightforward, but consider a scenario where your application grows and you start calculating taxes for different business entities, regions, or conditions. A method name like CalculateTax becomes too generic.
This code also violates one of the S.O.L.I.D. principles. Let's remind ourselves of what the Open/Closed Principle teaches:
Software entities (classes, modules, functions) should be OPEN for extension, but CLOSED for modification.
This means that the behavior of a module can be extended without modifying its existing source code. This is achieved by adding new functionality through inheritance, interfaces, or composition, rather than directly altering the existing code.
In the case of the original code, to add a new tax type, the TaxCalculator class itself would have to be modified, which goes against the principle:
Also, it’s not immediately clear what each condition in the if-else block represents. Each tax type is being evaluated based on a string value ("A", "B", "C"), and the tax rate is applied based on these conditions. But the logic is cluttered and relies on magic strings, which can be error-prone.
领英推荐
A Practical Refactor
To improve the code, the first thing to do is define an interface (ITaxType) that will have only one method (Calculate). This method is where each tax type will define its specific logic for calculating the tax.
Next, we create the concrete classes for each tax type (TaxA, TaxB, and TaxC in the example). Each of these classes will implement the interface and provide its own specific calculation logic:
Now that we have our tax types implemented as separate classes, we can refactor the TaxCalculator class to use a data structure called a dictionary for tax types.
This way, the code becomes more modular, easier to extend, and more maintainable. Instead of modifying the TaxCalculator class each time a new tax type is added, we simply need to create a new class that implements the ITaxType interface and add it to the dictionary.
Note: A dictionary in C# is a collection that stores key-value pairs. It allows for fast lookups, where the key is used to access the corresponding value. In our case, the key can be the tax type (e.g., "A", "B", "C"), and the value will be the instance of the appropriate class that implements the ITaxType interface.
Performance Consideration
While the primary focus of this refactor is to improve maintainability, scalability, and adherence to clean code principles, there’s an additional performance benefit worth noting. By replacing the if-else chain with a dictionary lookup, the tax calculation logic now benefits from constant time complexity, O(1), for each lookup. In contrast, the if-else structure has a linear time complexity, O(n), meaning that as the number of tax types grows, the performance could degrade.
This refactor not only makes it easier to add new tax types but also results in better performance when the number of tax types increases, especially in scenarios where the program might need to handle many different tax categories.
Conclusion
By leveraging interface-based design, polymorphism, and the Open/Closed Principle, we’ve transformed a rigid and complex codebase into a more flexible, scalable, and maintainable solution. These clean code practices not only improve the efficiency of the development process but also prepare the codebase for future growth and easy extensibility.
Clean code is about making your software easier to read, maintain, and extend, and by applying these principles, you’re setting your project up for long-term success.
#CleanCode #SoftwareDevelopment #CodeQuality #Refactoring #Scalability #Maintainability #Polymorphism #DesignPatterns #ProgrammingTips #TechBestPractices #CSharp #SoftwareEngineering #DeveloperMindset