Overview
Digital Results simplifies the process of handling binary success/failure results in an elegant way that promotes a more proactive approach to errors. A Result can take in either a Value or a collection of Errors. Your method returns a Result type that allows the receiver to selectively take different actions based on a successful operation or a failed operation.
The Traditional Method
How often do you find yourself rewriting the same pattern of checking values and catching exceptions returned by a method before returning a response to your user?
The traditional method of handling results from a method is to wrap it in a try/catch block. A cautious coder might research the method to determine the possible exceptions and then catch each distinct Exception type. You might even go as far as validating your input before passing it to the method. But let’s be honest. You are probably under a deadline, do a catch all, hope for the best, and debug failures later.
Consider the following example:
decimal GetAverage(int sum, int count)
{
return (decimal)sum / (decimal)count;
}
void GetResult(int sum, int count)
{
try
{
var result = GetAverage(sum, count);
Console.WriteLine($"The average is {result}");
}
catch (Exception ex)
{
Console.WriteLine("Failed to calculate the average due to {0}", ex.Message);
}
}
Very simply, the TraditionalResult method calls a GetAverage method to calculate the average of two numbers and return a result to the console. The method will throw a DivideByZero exception if the user supplies 0 for the count. Every consumer of the GetAverage method will need to implement the same try/catch block. Every consumer of the GetAverage method will need to be aware of all the possible exceptions and any rules that apply to the inputs that can be fed to the method. In this example, you could move all this to the GetAverage method. However, in a complex system, this method might be part of a library that should not be aware of the user interface, in this case the Console.
The Digital Results Method
This is where Digital Results comes into play. Digital Results provides a uniform response indicating success or failure of the method returning the Result. It also wraps the Value or Errors returning either/or for your code to consume directly. No more checking for null and catching exceptions at the UI layer.
Consider the following:
Result<decimal> GetAverage(int sum, int count)
{
try
{
return (decimal)sum / (decimal)count;
}
catch (Exception ex)
{
return ex;
}
}
void GetResult(int sum, int count)
{
var result = GetAverage(sum, count);
var message = result.Match(
success => $"The average is {success}",
failure => $"Failed to calculate the average due to {failure[0].Description}."
);
Console.WriteLine(message);
}
In this example, the GetAverage method now returns a Result<decimal> type. The GetResult method can now use the Result.Match method feature to selectively return a value based on the success or failure of the GetAverage method.
Refactoring the GetAverage method to use Result as a return type is as simple as changing the method signature and adding an Error return in this case through a try/catch block. Implicit converters allow you to instantiate a result without fully declaring the type keeping the code simple. Simple return the same value you did before. The Result will implicity wrap the value in a Result type. Likewise, the Error path is as simple as passing back the Exception.
The Error logic moves to the method that does the calculation without making the method aware of the user interface. This gives you a better separate of concerns. The calling method can then provide a different response to the user depending on the success or failure of the GetAverage method. It gives the consumer a cleaner look that makes it clear there is a different path for success and failure.
This approach also allows you to code more proactively. Rather than catching unexpected exceptions in your UI layer, you instead check the values in place. This pushes the logic to the place where you have more knowledge of what values are valid and the correct state of a result.
You can chose to continue to catch and expose exceptions. You can also create a set of known Errors with more meaningful and user readable descriptions. Wrapping an Exception in an Error allows the best of both worlds – user friendly response which still retain the underlying exception data.
How to use Digital Results
You can include DigitalResults in your project today by using your favorite Nuget package manager to get DigitalResults from Nuget.
Add the namespace DigitalCaesar.Results.
using DigitalCaesar.Results;
Add either Result or Result<T> as a return type to a method.
Use a Result on void methods that do not return a value but could end in a successful or failed state with Errors.
public Result DoSomething(int someNumber)
{
if(someNumber > 0)
return true;
return new Error("Low Number","The value of someNumber must be greater than 0.");
}
Use a Result<T> on methods that do return a value and could end in a successful or failed state with Errors.
public Result<int> DoSomething(int someNumber)
{
if(someNumber > 0)
return someNumber;
return new Error("Low Number","The value of someNumber must be greater than 0);
}
Do you like what you see?
For more information, you can check out the code on GitHub. If you like it, give it a Star, consider Sponsoring us, and feel free to contribute.
Would you like to see more? Check out our blog and other products.