In about 20 years of a development career, the question of managing errors and save time to manage bugs came a lot of time, with barely never the same answer.
Then I met a guy whose approach seemed to me so evident thant it could not fit all case, but …
All exceptions can fit in 4 top level exception types
Wait … ! What ?
Yes, all exceptions can fit in only 4 new top level exceptions. Lets have a quick look and to what they are intended to.
UserException
The user exception is intended to be used when the user input something wrong.
In a web application, this can typically be used when the model state is invalid.
BusinessException
The business exception is intended to be used when a business rule is violated.
Imagine you want to buy something that cost 10€ but your budget is only 9€.
SystemException
In many cases, especially at present, your application communicate with external resources, as for example a web service in another ecosystem, a disk storage, or whatever. When this external resource is not available for any reason, this is when you will raise a SystemException.
ApplicationException
In any other case, in other words, for all unhandled exception, you will throw an ApplicationException.
Benefits
Apart the structuration of all of your exceptions, as a bug fixer, your quest is to save time to manage bugs and you will always be able to identify the first part of the problem that happened, and that a real advantage when your analyst / product manager comes to you saying “hey, I have an error” and your answer will be “what type ?” and then …
UserException
Your user has typed something wrong, don’t bother me 😉
BusinessException
You’re trying to do something you should not (and you probably asked me to implement)
SystemException
An infrastrucutre dependency is down, lets check which one.
ApplicationException
Ah, there is a bug somwewhere, indeed, let me check.
You get it, you will save time to manage bugs !
How to better manage bugs : Implementation
The implementation is pretty simple. We define an abstract class, and a class for each exception type inheriting from this abstract class (or download the full project).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System; using System.Runtime.Serialization; namespace Project.Exceptions { [Serializable] public abstract class AbstractException : Exception { protected AbstractException() { } protected AbstractException(string message) : base(message) { } protected AbstractException(string message, Exception innerException) : base(message, innerException) { } protected AbstractException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System; using System.Runtime.Serialization; namespace Project.Exceptions { [Serializable] public class UserException : AbstractException { public UserException() { } public UserException(string message) : base(message) { } public UserException(string message, Exception innerException) : base(message, innerException) { } protected UserException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } |
Do the same for all other classes (BusinessException, SystemException and ApplicationException) and you’re done. All you have to do now is to use them properly.
1 2 3 4 5 6 |
if (!this.ModelState.IsValid) { throw new UserException("The validation of your form did not succeed."); } |
Great article! The breakdown of exception types into UserException, BusinessException, SystemException, and ApplicationException is really clear and practical. This structured approach will definitely help streamline our debugging process and save a lot of time. Thanks for sharing such valuable insights!
Great article! The structured approach to managing exceptions in C# is both clear and practical. I particularly appreciate the distinction between UserException, BusinessException, SystemException, and ApplicationException. One question: how do you recommend handling exceptions in a microservices architecture where different services might have different logging and handling strategies? Thanks for sharing your insights!
Hi Marc,
In a microservices architecture, it’s essential to implement consistent exception handling and logging strategies across services. One approach is to define a common set of exception types and handling protocols shared across all services. Each service can implement these protocols while maintaining flexibility for service-specific exceptions. Centralized logging systems, like ELK stack or Azure Monitor, can be used to aggregate and monitor logs across services. Implementing correlation IDs helps in tracing requests across different services, facilitating better debugging and error tracking.