Writing clean code is crucial for development. If writing code is art then writing clean code would be a masterpiece.
Check out the first post of the clean code tips series :
1- Give meaningful names
Using meaningful names makes code self-explanatory, reducing the need for additional comments.
Bad Example :
public class Emp
{
public string n;
public int a;
public void d()
{
Console.WriteLine($"Name: {name}, Age: {age}");
}
}
Good Example :
public class Employee
{
public string name;
public int age;
public void DisplayInfo()
{
Console.WriteLine($"Name: {name}, Age: {age}");
}
}
2- Avoid returning null
Returning null can lead to NullReferenceException
errors when the caller attempts to access properties or methods on the returned object. It can also make the code harder to read and maintain, as the caller must always check for null before using the returned value.
Suppose we have this piece of code :
public class UserService
{
public User? GetUserById(int id)
{
if (id <= 0)
{
return null;
}
.... Rest logic of method ...
return user;
}
}
We can improve it with this, instead of returning null we can throw an exception and then handle it separately :
public sealed class UserService
{
public User GetUserById(int id)
{
if (id <= 0)
{
// throws an exception instead of returning null
throw new ArgumentException("Invalid user Id");
}
.... Rest logic of method ...
return user;
}
}
3- Keep class | method size small
A class should have one reason to change.
Smaller classes are easier to manage, test, and maintain.
At some points, this asks us to follow the Single Responsibility Principle.
Suppose we have this example in which we have a lot of code, with different concerns :
public sealed class OrderProcessing
{
public void ProcessOrder(Order order)
{
if (!ValidateOrder(order))
{
throw new ArgumentException("Invalid order");
}
ChargePayment(order);
UpdateInventory(order);
SendConfirmationEmail(order);
LogOrderDetails(order);
}
private bool ValidateOrder(Order order) { }
private void ChargePayment(Order order) { }
private void UpdateInventory(Order order) { }
private void SendConfirmationEmail(Order order) { }
private void LogOrderDetails(Order order) { }
}
This could be improved like this where we have separate classes for each concern :
public sealed class OrderValidator
{
public bool Validate(Order order){}
}
public sealed class PaymentProcessor
{
public void Charge(Order order){}
}
public sealed class InventoryManager
{
public void Update(Order order){}
}
public sealed class EmailService
{
public void SendConfirmation(Order order){}
}
public sealed class OrderProcessing
{
private readonly OrderValidator _validator;
private readonly PaymentProcessor _paymentProcessor;
private readonly InventoryManager _inventoryManager;
private readonly EmailService _emailService;
public OrderProcessing(
OrderValidator validator,
PaymentProcessor paymentProcessor,
InventoryManager inventoryManager,
EmailService emailService)
{
_validator = validator;
_paymentProcessor = paymentProcessor;
_inventoryManager = inventoryManager;
_emailService = emailService;
}
public void ProcessOrder(Order order)
{
if (!_validator.Validate(order))
{
throw new ArgumentException("Invalid order");
}
_paymentProcessor.Charge(order);
_inventoryManager.Update(order);
_emailService.SendConfirmation(order);
}
}
Although there is no hard-code rule about how many lines of code a method should contain, 20-40 lines are enough for a method.
4- Don’t reinvent the wheel
Reinventing the wheel can lead to unnecessary duplication of effort, wasting valuable time and resources that could be better spent on developing unique features.
What exactly is reinventing the wheel, it means when we already have code for some issue and we start writing manually code to solve that cause then we are reinventing the wheel :
public sealed class CustomLogger
{
public void Log(string message)
{
using (var writer = new StreamWriter("log.txt", true))
{
writer.WriteLine($"{DateTime.Now}: {message}");
}
}
}
We can achieve that by just injecting the ILogger interface, considering logging is already configured :
public sealed class Application
{
private readonly ILogger _logger;
public Application(ILogger logger)
{
_logger = logger;
}
}
Read about how to implement logging in .NET :
5- Use appropriate tools/IDE
Using the right tools or IDE enhances productivity by providing features like syntax highlighting, code completion, and error checking, which help catch mistakes early.
Keep reading the new update of Visual Studio, and invest time in learning about existing features of tools as well.
Additionally, these are two practices that I follow in my .NET projects, and I would recommend that you do the same:
1- Enabling Editor Config File
Enabling the editor config file in .NET projects to apply some sort of rules on all code
root = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
2- Adding the props file
This props file is used to configure common settings and apply them across all projects for example:
<Project>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
Whenever you’re ready, there are 2 ways I can help you:
If you want to boost your .NET skills and do some code in action you can do that by subscribing to my YouTube Channel
Promote yourself to 11,000+ subscribers by sponsoring this newsletter
In addition to nulls, I'd suggest initializing string properties and fields with `string.Empty`. Same reasons as null.
I do this because I do a lot of parsing and if you think about it, there's no reason a non-db string value should be null. If it does happen to be null, then either a table field in our db came back null (which I dislike), or we simply forgot to set it, making for an easy fix.
Null has no context, but an empty string does. It has intent behind it, whereas null has ambiguity all the way up and down the stack!
Also, in C#, I have written extension methods like: `idStr.ToInt(fallback: -1)`. Inside the method is a ternary that checks for null or empty and returns the parsed id value and returns an int id of -1 if the parsing or null fails. The intent behind this is to discourage null strings and, if a string is null, have a reasonable, logical fallback value for business logic. And id the value is, say, a guid string, then the mehtod will throw an exception anyways.
Boom. All cases handled, and not a single deep, bug-prone if/else or try catch to be found.