- author: Nick Chapsas
Three Formerly Used .NET Best Practices and Why I Don't Use Them Anymore
In this video, I share three practices that I used to use in the past, talked about in my channel and blogs, but stopped following in recent times. I will explain why I don't use them anymore and what I do instead.
Disclaimer: The fact that I no longer use these best practices does not mean they are not useful. They did not work for my coding style, but if they work for you, do not alter it. If it ain't broken, don't fix it.
1. Dependency Injection (DI) Installer
Dependency Injection (DI) is a design pattern for writing reusable and testable code. It allows us to get rid of hard-coded dependencies and make our code more flexible, scalable, and easy to test.
In the past, I used to have an
installer class that logically grouped a section of the DI to install services from an assembly containing. I'd point to an assembly marker like
program.cs to detect all installers in a given assembly. By doing this, you end up having a nice, isolated, clean syntax. It looks great, but two critical issues come with it:
Visibility: It's challenging to keep track of the registration that occurs here because, at the end of the day, something in another assembly or your assembly will register, but you cannot see it. As a developer, I prefer visibility to magic.
Ordering: Because these are based on the assemblies that are exported and the types, I can't maintain the order they're being looped around. I can't guarantee that something comes first before another. If specific registration order matters to you, you can't control it.
Nowadays, I use simple extension methods to have a similar logical grouping of APIs. By having it in an extension method on the
ServiceCollection, I can explicitly control the order exactly how I want. It is more visible and relinquishes control to the developer, as I'd much prefer this approach rather than relying on magic.
A mapper is a tool that transfers data from one object to another. In most cases, you don't have to write code and only configure it. You have an object A, object B, and you can create a map between the two. It seems great, and in the past, I even advocated for it.
However, I stopped using it because of timing and efficiency concerns. When working with the mapper, things can get out of sync because, as you start changing things, these issues may arise at runtime instead of compile time. Some of these issues won't fail at compile time, and even if you have unit tests, you need to have the right pipeline to execute and fail on compile-time.
Although Source-generated Mappers make it less of a problem, by writing my own mappers, I have full control over the mapper. I prefer to control what's going on and do things manually, as I get a clearer view of what's happening and don't use fancy features of mappers that can lead to Bad Code.
3. Guard Clauses
Guard Clauses, such as
Throw if Null, can be used to check if there is a null or empty input. I don't like using it because I don't like throwing exceptions for such coding concerns. Exceptions should be exceptional, and issues like these should use more valid code to return things appropriately.
Rather than using packages that offer this functionality, I prefer a more explicit flow that is less visible. Packages usually wrap it in a nice and fluent API, and it looks cool when you read it. But when you run the code, exit it, and try to follow the flow, it becomes problematic, leading to bad code.
In summary, these best practices have served me in the past, but as I grew as a developer, I can see their shortfalls, making them less suitable for my coding style now. This article aims to give you insights into the best practices that you may be using right now, but you may need to evaluate if they suit you.
What are the .NET best practices that you're not using anymore? Why did you stop? Leave a comment below and let's learn from each other. Thanks for reading!