Avoiding Code Smells in C#: Best Practices and Examples

Mohamed Hendawy
6 min readDec 19, 2023

--

Code smells are indicators of potential issues in your code that may lead to maintenance challenges, reduced readability, and increased chances of introducing bugs. As a C# developer, it’s crucial to be aware of these code smells and adopt best practices to write clean, maintainable, and efficient code. In this article, we will explore common code smells in C# and provide examples along with strategies to avoid them.

1. Long Methods:

Long methods are difficult to understand and maintain. Break down lengthy methods into smaller, more focused ones to improve readability and maintainability.

// Code Smell
public void ProcessData()
{
// ... 100 lines of code ...
}

// Refactored
public void ProcessData()
{
ExtractMethod1();
ExtractMethod2();
// ...
}

private void ExtractMethod1()
{
// ...
}

private void ExtractMethod2()
{
// ...
}

2. Large Classes:

Large classes are often a sign of poor design. Consider breaking down large classes into smaller, cohesive ones that adhere to the Single Responsibility Principle (SRP).

// Code Smell
public class DataProcessor
{
// ... lots of methods and fields ...
}

// Refactored
public class DataProcessor
{
private FileReader _fileReader;
private DataValidator _dataValidator;

public DataProcessor(FileReader fileReader, DataValidator dataValidator)
{
_fileReader = fileReader;
_dataValidator = dataValidator;
}

// Methods related to file reading
// ...

// Methods related to data validation
// ...
}

3. Inconsistent Naming:

Inconsistent naming conventions can make your code confusing. Adopt a consistent naming style for variables, methods, and classes.

// Code Smell
public class processData
{
public void manipulateData()
{
int a;
string b_c;
}
}

// Refactored
public class DataProcessor
{
public void ManipulateData()
{
int counter;
string dataString;
}
}

4. Complex Conditional Statements:

Complex conditional statements reduce code readability. Simplify conditions and extract them into well-named methods to improve code comprehension.

// Code Smell
public void ProcessData(int value)
{
if (value > 0 && value % 2 == 0 || value < -10)
{
// ...
}
}

// Refactored
public void ProcessData(int value)
{
if (IsValueValid(value))
{
// ...
}
}

private bool IsValueValid(int value)
{
return (value > 0 && value % 2 == 0) || value < -10;
}

5. Duplicate Code:

Duplicate code increases the risk of inconsistencies. Create reusable methods or extract common functionality into separate classes.

// Code Smell
public void ProcessDataA()
{
// ... common code ...
// ... specific code for A ...
}

public void ProcessDataB()
{
// ... common code ...
// ... specific code for B ...
}

// Refactored
public void ProcessDataCommon()
{
// ... common code ...
}

public void ProcessDataA()
{
ProcessDataCommon();
// ... specific code for A ...
}

public void ProcessDataB()
{
ProcessDataCommon();
// ... specific code for B ...
}

6. Comments Overuse:

Excessive comments can be a sign that your code is not self-explanatory. Aim for self-documenting code by using clear and meaningful names for variables, methods, and classes.

// Code Smell
public void ProcessData(int a, int b)
{
// Add a to b
int result = a + b;
// Display the result
Console.WriteLine(result);
}

7. Mutable State:

Frequent changes to object states can introduce bugs and make code harder to reason about. Favor immutability and limit the use of mutable state where possible.

// Code Smell
public class MutablePerson
{
public string Name { get; set; }
public int Age { get; set; }
}

// Refactored
public class ImmutablePerson
{
//private setters
public string Name { get; }
public int Age { get; }

public ImmutablePerson(string name, int age)
{
Name = name;
Age = age;
}
}

8. Inefficient String Concatenation:

Repeatedly concatenating strings using the + operator can be inefficient. Use StringBuilder for better performance, especially in loops.

// Code Smell
string result = "";
for (int i = 0; i < 1000; i++)
{
result += i.ToString();
}

// Refactored
StringBuilder resultBuilder = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
resultBuilder.Append(i);
}
string result = resultBuilder.ToString();

9. Lack of Error Handling:

Ignoring exceptions or not handling errors gracefully can lead to unpredictable behavior. Always include proper error handling in your code.

// Code Smell
public int Divide(int a, int b)
{
return a / b;
}

// Refactored
public int Divide(int a, int b)
{
if (b == 0)
{
// Handle division by zero
throw new ArgumentException("Cannot divide by zero.");
}
return a / b;
}

10. Tight Coupling:

Highly coupled code can be challenging to maintain and modify. Aim for loose coupling by using interfaces and dependency injection.

// Code Smell
public class OrderProcessor
{
public void ProcessOrder()
{
PaymentProcessor paymentProcessor = new PaymentProcessor();
paymentProcessor.ProcessPayment();
// ...
}
}

// Refactored
public class OrderProcessor
{
private readonly IPaymentProcessor _paymentProcessor;

public OrderProcessor(IPaymentProcessor paymentProcessor)
{
_paymentProcessor = paymentProcessor;
}

public void ProcessOrder()
{
_paymentProcessor.ProcessPayment();
// ...
}
}

11. Feature Envy:

Feature envy occurs when a method seems more interested in the data of another class than its own. This may indicate a need for refactoring to improve encapsulation.

// Code Smell
public class ShoppingCart
{
public decimal CalculateTotal(Order order)
{
// Feature envy, accessing properties of Order excessively
return order.Quantity * order.Price;
}
}

// Refactored
public class Order
{
public decimal CalculateTotal()
{
// Encapsulate the calculation within the Order class
return Quantity * Price;
}
}

12. Data Clumps:

Data clumps occur when a set of variables or parameters are frequently grouped together. This may indicate that these variables belong to a common abstraction.

// Code Smell
public class OrderProcessor
{
public void ProcessOrder(string orderId, DateTime orderDate, decimal amount)
{
// ...
}
}

// Refactored
public class Order
{
public string OrderId { get; set; }
public DateTime OrderDate { get; set; }
public decimal Amount { get; set; }
}

public class OrderProcessor
{
public void ProcessOrder(Order order)
{
// ...
}
}

13. Inappropriate Intimacy:

Inappropriate intimacy occurs when classes are tightly coupled and have access to each other’s internal details. This can make the code more fragile and harder to maintain.

// Code Smell
public class Customer
{
public string Name { get; set; }
public void SendInvoice(Invoice invoice)
{
// Customer knows too much about Invoice internals
invoice.Generate();
}
}

// Refactored
public class Customer
{
public string Name { get; set; }
public void SendInvoice(Invoice invoice)
{
// Customer interacts through a clean interface
invoice.Send();
}
}

public class Invoice
{
public void Send()
{
// Implementation details for sending an invoice
}
}

14. Lazy Class:

Lazy class occurs when a class doesn’t provide enough value or functionality to justify its existence. It may be better to merge or eliminate such classes.

// Code Smell
public class UnusedClass
{
// Minimal functionality or unused class
}

// Refactored
// Merge the functionality of UnusedClass into another class or eliminate it if unnecessary

15. Shotgun Surgery:

Shotgun surgery occurs when a single change in code requires modifications in multiple places. This suggests a lack of proper encapsulation.

// Code Smell
public class Configuration
{
public string DatabaseConnectionString { get; set; }
public string LogFilePath { get; set; }
// ... many other properties ...
}

// Refactored
public class DatabaseConfiguration
{
public string ConnectionString { get; set; }
}

public class LoggingConfiguration
{
public string LogFilePath { get; set; }
}

// Usage
var databaseConfig = new DatabaseConfiguration { ConnectionString = "..." };
var loggingConfig = new LoggingConfiguration { LogFilePath = "..." };

16. Cyclic Dependency:

Cyclic dependencies occur when two or more classes depend on each other directly or indirectly. This can make the code harder to understand and maintain.

// Code Smell
public class ClassA
{
public void MethodA()
{
// Depends on ClassB
}
}

public class ClassB
{
public void MethodB()
{
// Depends on ClassA
}
}

// Refactored
// Introduce an interface or abstract class to break the cyclic dependency

By addressing these code smells, you can contribute to creating cleaner, more maintainable, and readable code. Regular code reviews and a commitment to continuous improvement are essential practices for avoiding and correcting code smells in your C# projects.

--

--