From 832f7e46be5dabd08604afd478916eb559dd03ad Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Wed, 27 Dec 2023 20:01:30 -0500 Subject: [PATCH] docs: Add two new sections to README.md --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7d094ea..9142b69 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ See the [API documentation](https://mrdave1999.github.io/SimpleResults/api/Simpl - [Operation Result Pattern](#operation-result-pattern) - [Why did I make this library?](#why-did-i-make-this-library) - [Why don't I use exceptions?](#why-dont-i-use-exceptions) + - [Differences between an expected and unexpected error](#differences-between-an-expected-and-unexpected-error) - [Anecdote](#anecdote) + - [What happens if I use exceptions for all cases?](#what-happens-if-i-use-exceptions-for-all-cases) - [Interesting resource about exceptions](#interesting-resource-about-exceptions) - [Installation](#installation) - [Overview](#overview) @@ -69,22 +71,22 @@ I couldn't change this format because the front-end used it, so I didn't want to ## Why don't I use exceptions? -I usually throw exceptions when developing open source libraries to alert the developer immediately that an unexpected error has occurred and must be corrected. In this case, it makes sense to me to throw an exception because the developer can know exactly where the error originated (by looking at the stack trace). +I usually throw exceptions when developing open source libraries to alert the developer immediately that an unexpected error has occurred and must be corrected. In this case, it makes sense to me to throw an exception because the developer can know exactly where the error originated (by looking at the stack trace). However, when I develop applications, I very rarely find a case for using exceptions. -However, when I develop applications I very rarely find a case for using exceptions. - -For example, I could throw an exception when a normal user enters empty fields but this does not make sense to me, because it is an error caused by the end user (he/she manages the system from the user interface). So in this case throwing an exception is useless because: +For example, I could throw an exception when a normal user enters empty fields but this does not make sense to me, because it is an error caused by the end user (who manages the system from the user interface). So in this case throwing an exception is useless because: - Stack trace included in the exception object is of no use to anyone, neither the end user nor the developer. This is not a bug that a developer should be concerned about. - Nobody cares where the error originated, whether it was in method X or Y, it doesn't matter. - It is not an unexpected error. An exception is thrown to indicate an unexpected error. Unexpected errors are those that are not expected to occur, and they are not recoverable. - - For example, if the database server is not online, it will produce an unexpected error in the application, so there is no way for the application to recover or handle the error. + - For example, if the database server is not online, it will produce an unexpected error in the application, so there is no way for the application to recover. And there are many more examples of errors caused by the end user: the email is duplicated or a password that does not comply with security policies, among others. -I only throw exceptions for unexpected errors otherwise, I create **result objects** and use return statements in my methods to terminate execution immediately when an expected error occurs. +I only throw exceptions for unexpected errors; otherwise I create **result objects** and use return statements in my methods to terminate execution immediately when an expected error occurs. + +### Differences between an expected and unexpected error It is necessary to understand the differences between an expected and unexpected error in order to know when to throw exceptions. In fact, in practice, third-party dependencies are responsible for reporting unexpected errors, so the developer only has to worry about identifying the expected errors of his business application. @@ -110,6 +112,39 @@ This was a surprise to me! I didn't know! I was expecting an exception but it wa > If I had thrown an exception, I would have found the error very quickly, just by looking at the stack trace. In this case, it is very useful the exception object, for me and other developers and yes, divide by zero is an **unexpected error**, an exception should be thrown. +### What happens if I use exceptions for all cases? + +Your application will work for sure but some things will happen: + +- New maintainers of your application will learn that it is okay to throw exceptions in all situations. This is bad for their learning, since they don't really understand what exceptions in C# were designed for. + +- You make your code confusing, since you do not respect the official definition of what an exception is in C#. + +- You need to create custom classes that inherit from the Exception type, otherwise you end up using the Exception type in many places, making your code not explicit, that is, the Exception type does not mean anything, it does not express anything to the developer. + +- You need to document those methods that throw exceptions, otherwise the consumer will not know which exceptions to handle, and will end up reviewing the source code of the method. + +- Performance. Yes, throwing exceptions is very expensive. Although in many applications there may not be any impact, it is not a justification for wasting resources unnecessarily. In other words, if you have an alternative to exceptions, why not use it? + - You can read more about it [here](https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/intro-to-clr.md#exceptions). + +- If your application is a web application, you will have to find a mechanism to translate the exception object to HTTP status code, so you will have to create base classes like InvalidDataException to catch it from a global exception handler. + - For example: `DuplicateEmailException` inherits from `InvalidDataException` and in turn, it inherits from `Exception`. So you must think well in the hierarchy of types using inheritance. + +- You make your code not explicit, so it affects readability. Let's take a look at this example code: [RegisterCustomerUseCase](https://github.com/olafthielke/CleanArchitecture/blob/c18586b8b9a76bb1b9473693afc1cdc94f5e53e3/BusinessLogic/UseCases/RegisterCustomerUseCase.cs#L6C1-L41C2). + - This code is not that detailed: + ```cs + public async Task RegisterCustomer(CustomerRegistration reg) + { + // What is this??? + await Validate(reg); + var customer = reg.ToCustomer(); + await Repository.SaveCustomer(customer); + await Notifier.SendWelcomeMessage(customer); + return customer; + } + ``` + That line is confusing because I don't know if that line of code was added on purpose or if someone forgot to handle the error with `try-catch`. As you will see, that line of code does not express its intention. + ### Interesting resource about exceptions - [Exceptions for flow control in C# by Vladimir Khorikov](https://enterprisecraftsmanship.com/posts/exceptions-for-flow-control)