Welcome to the world of refactoring! In this lesson, we're learning about Code Smells, which are patterns in code that hint at potential problems. Our mission is to help you spot these smells and understand how to improve them or, in programming terms, how to refactor them. We'll delve into the concept of code smells, examine different types, and apply real-world code examples to solidify your understanding. Let's get started!
Code smells are signs that something could be amiss in our code. You could compare them to an unpleasant smell in a room. But instead of indicating rotten food or a dirty sock, they signal that our code may not be as readable, efficient, or manageable as it could be.
Consider this bit of code:
C#1public class PriceCalculator 2{ 3 public static int Calculate(int quantity, int price) 4 { 5 return quantity * price; 6 } 7 8 public static void Main(string[] args) 9 { 10 int total = Calculate(5, 3); 11 Console.WriteLine(total); 12 } 13}
The function name Calculate
is too vague. What exactly does it calculate? For whom? This ambiguity is a sign of a bad naming
code smell. Let's see how this and other code smells can be improved!
If you notice the same piece of code in more than one place, you may be looking at an example of the Duplicate Code smell. Duplicate code leaves room for errors and bugs. If you need to make a change, you might overlook one instance of duplication.
Here's an example:
C#1int totalApplesPrice = quantityApples * priceApple - 5; 2int totalBananasPrice = quantityBananas * priceBanana - 5;
This code performs the same operation on different data. Instead of duplicating the operation, we can create a method to handle it:
C#1public class PriceCalculator 2{ 3 public static int CalculatePrice(int quantity, int price) 4 { 5 int discount = 5; 6 return quantity * price - discount; 7 } 8 9 public static void Main(string[] args) 10 { 11 int totalApplesPrice = CalculatePrice(quantityApples, priceApple); 12 int totalBananasPrice = CalculatePrice(quantityBananas, priceBanana); 13 Console.WriteLine(totalApplesPrice); 14 Console.WriteLine(totalBananasPrice); 15 } 16}
With this solution, if we need to change the discount
or the formula, we can do so in one place: the CalculatePrice
method.
A method that does too many things or is too long is harder to read and understand, making it a prime candidate for the Too Long Method smell.
Consider this example:
C#1public class OrderProcessor 2{ 3 public bool ProcessOrder(Order order) 4 { 5 Console.WriteLine("Processing order..."); 6 if (order.IsValid()) 7 { 8 Console.WriteLine("Order is valid"); 9 if (order.PaymentType.Equals("credit_card")) 10 { 11 ProcessCreditCardPayment(order); 12 SendOrderConfirmationEmail(order); 13 } 14 else if (order.PaymentType.Equals("paypal")) 15 { 16 ProcessPaypalPayment(order); 17 SendOrderConfirmationEmail(order); 18 } 19 else if (order.PaymentType.Equals("bank_transfer")) 20 { 21 ProcessBankTransferPayment(order); 22 SendOrderConfirmationEmail(order); 23 } 24 else 25 { 26 Console.WriteLine("Unsupported payment type"); 27 return false; 28 } 29 Console.WriteLine("Order processed successfully!"); 30 return true; 31 } 32 else 33 { 34 Console.WriteLine("Invalid order"); 35 return false; 36 } 37 } 38}
This function handles too many aspects of order processing, suggesting a Too Long Method
smell. A better approach could involve breaking down the functionality into smaller, more focused methods.
For example, the updated code can look like this:
C#1public class OrderProcessor 2{ 3 public bool ProcessPayment(string paymentType, Order order) 4 { 5 if (paymentType.Equals("credit_card")) 6 { 7 ProcessCreditCardPayment(order); 8 } 9 else if (paymentType.Equals("paypal")) 10 { 11 ProcessPaypalPayment(order); 12 } 13 else if (paymentType.Equals("bank_transfer")) 14 { 15 ProcessBankTransferPayment(order); 16 } 17 else 18 { 19 Console.WriteLine("Unsupported payment type"); 20 return false; 21 } 22 return true; 23 } 24 25 public bool ProcessOrder(Order order) 26 { 27 Console.WriteLine("Processing order..."); 28 if (!order.IsValid()) 29 { 30 Console.WriteLine("Invalid order"); 31 return false; 32 } 33 if (ProcessPayment(order.PaymentType, order)) 34 { 35 SendOrderConfirmationEmail(order); 36 Console.WriteLine("Order processed successfully!"); 37 return true; 38 } 39 else 40 { 41 return false; 42 } 43 } 44}
Comments within your code should provide useful information, but remember, too much of a good thing can be a problem. Over-commenting can distract from the code itself and, more often than not, it's a sign the code isn't clear enough.
Consider this revised method, which calculates the area of a triangle, now with comments:
C#1public class Triangle 2{ 3 public static double CalculateTriangleArea(double baseValue, double height) 4 { 5 // Calculate the area of a triangle 6 // Formula: 0.5 * base * height 7 double area = 0.5 * baseValue * height; // Area calculation 8 return area; // Return the result 9 } 10}
While comments explaining the formula might be helpful for some, the code itself is quite straightforward, and the comments on the calculation itself might be seen as unnecessary. If the method's name and parameters are clear, the need for additional comments can be minimized.
Here is how we could change this:
C#1public class Triangle 2{ 3 // Calculates the area of a triangle using the given base and height. 4 public static double CalculateTriangleArea(double baseValue, double height) 5 { 6 double area = 0.5 * baseValue * height; 7 return area; 8 } 9}
In this version, a single comment placed at the method level provides a high-level overview of what the method does, which is sufficient given the clarity of the method name and parameters. Comments within the method have been removed as they were unnecessary.
Finally, we have Bad Naming. As the name suggests, this smell occurs when names don't adequately explain what a variable, method, or class does. Good names are crucial for readable, understandable code.
Take a look at the following example:
C#1public int Func(int a, int b) 2{ 3 return a * 10 + b; 4}
The names Func
, a
, and b
don't tell us much about what is happening. A better version could be this:
C#1public int CalculateScore(int baseScore, int extraPoints) 2{ 3 return baseScore * 10 + extraPoints; 4}
In this version, each name describes the data or action it represents, making the code easier to read.
We've discovered Code Smells and studied common types: Duplicate Code
, Too Long Method
, Comment Abuse
, and Bad Naming
. Now you can spot code smells and understand how they can signal a problem in your code.
In the upcoming real-world example-based practice sessions, you'll enhance your debugging skills, and improve your code's efficiency, readability, and maintainability. How exciting is that? Let's move ahead!