How to get a result of a function but still know if it failed and why?

How to get a result of a function but still know if it failed and why?

Many years of working on my own or other people's code led me to a conclusion that there are no clear and obvious ways or typical conventions of handling one of the most fundamental things in programming – the returning of results regardless if they are what we expect or errors. This leads to an important question : 

Do we really know what is the result of a function ?

At least people do not seem to follow any visible and constant strategy. 

Let's start digging into the problem with a simple example. Let's create an application that divides one integer number by another.

 

Let's create code for 'Calculate' button:

private void btnCalculate_Click(object sender, EventArgs e)
{
   int dividend = int.Parse(txtDividend.Text);
   int divider = int.Parse(txtDivider.Text);
   int result = dividend / divider;
   txtResult.Text = result.ToString();
}

Consider a positive scenario where numbers put into text boxes are nice integers, and the result of division 10 by 5 is 2. 

Now let's divide this code as it should be divided, so the button is not responsible for calculation. We're putting aside the problem of conversion from text to integer (from textbox value into an integer variable) for now – it is a different story altogether. Calculation, then, is moved into another function, which accepts two parameters and returns a result number.

private void btnCalculate_Click(object sender, EventArgs e)
{
   int dividend = int.Parse(txtDividend.Text);
   int divider = int.Parse(txtDivider.Text);
   int result = Calculate(dividend, divider);
   txtResult.Text = result.ToString();
}

private int Calculate(int dividend, int divider)
{
   int result = dividend / divider;
   return result;
}

Of course, from the user’s perspective, this works exactly in the same manner as the previous version where it was the button that did the calculation. But what happens if we use zero as the divider ? The results are starting to get weird. Float division by zero returns Infinity which is ok according to IEEE 754, but is not mathematically correct. With integers however we'll get the "Division by zero" exception.  (You can learn more about the math problem here: https://www.youtube.com/watch?v=BRRolKTlF6Q )

So regardless of which variable types you use – integers or floats, the result is not what the end user expects. What is typically expected is that either zero is prevented from being used as the divider or the user is informed that there was a problem and the calculation cannot be done. But wait ! The Calculate function does not return any information of what the cause of the problem the problem might be, it just throws an error and the applications goes bust t !

OK, but we're smart and we know how to handle exceptions using try/catch.

private int Calculate(int dividend, int divider)
{
   try
   {
      int result = dividend / divider;
      return result;
   }
   catch (DivideByZeroException ex)
   {
      return -1;
   }
}

But hey, what is the -1 result doing here ?? Well, you probably did something similar in the past and you've seen many Microsoft functions that return -1 (or zero, or Infinity) if something goes wrong (e.g. key not found, no items in index) but handle exceptions themselves. 

This is a very bad coding habit

We mixed the result of the calculation with the information about the problem. If we used zero as divider the result would have been the same as in the case of dividing -1 by 1. It would be -1. As you can see, returning -1 is a very, very wrong idea. Also returning Infinity (for float division) is wrong because even if we used the IEEE 754 convention, the result would be wrong from the mathematical point of view. Division by zero may equal either plus or minus infinity, so one result is wrong anyway.

OK, what can we do ? Well, there is a number of solutions to this problem.

Solution 1 (not so good)

We could delegate exception handling to the view (button click) function. Now if you try to divide by zero, you get a nice message that it is impossible. In fact you don't care what the problem is, you don't do anything except informing the user. txtResult.Text will not be set, because the exception is thrown before setting its value. Not very elegant, I know, but it works.

private void btnCalculate_Click(object sender, EventArgs e)
{
   try
   {
      int dividend = int.Parse(txtDividend.Text);
      int divider = int.Parse(txtDivider.Text);
      int result = Calculate(dividend, divider);
      txtResult.Text = result.ToString();
   }
   catch (Exception ex)
   {
      MessageBox.Show(ex.Message);
   }
}

The problem here is that we move problem handling to a place where it does not belong, which is a violation of SRP - Single Responsibility Principle. We're handling the problem outside the Calculate function. Imagine a Calculate function which consists of thousands of operations and that the result is crucial to life or death (medical or military equipment). Or that it is “only” built into a piece of banking software. Imagine that the calculations go wrong and you lose your entire bank account. Here is what is wrong with our function at the moment:

Problem handling should be done at the level of the function that performs an operation and not thrown back to the caller.

The caller could be a different function, different class, or even different SOAP application that was coded in Java and has no idea of what we're processing. Also, the Calculate function should be responsible for rollback of any transaction it started, clearing memory that it used (if needed) and, last but not least, it should log error messages using a logging facility (either text error file, event viewer or SQL based log table). Client application should only serve as a place to display data, not interpret problems. A detailed description of a problem should be created inside the Calculate function.

If you are thinking to move MessageBox.Show function to Calculate - please don't do this. MessageBox.Show belongs to the view, while Calculate is a mathematical function - we need to make each function and class responsible for itself.

Solution 2 - much better

Another solution may be for our Calculate function to returns both the calculated number AND the information whether the calculation was successful or not.

private int Calculate(int dividend, int divider, out Boolean Success)
{
   Success = false;
   try
   {
      int result = dividend / divider;
      Success = true;
      return result;
   }
   catch (DivideByZeroException ex)
   {
      Logging.ErrLog(ex);
      Success = false;
      return -1;
    }
}

private void btnCalculate_Click(object sender, EventArgs e)
{
   int dividend = int.Parse(txtDividend.Text);
   int divider = int.Parse(txtDivider.Text);
   Boolean isSuccess;
   int calculatedNumber = Calculate(dividend, divider, out isSuccess);
   if (isSuccess== true)
   {
   txtResult.Text = calculatedNumber.ToString();
   }
   else
   txtResult.Text = "can't divide";

}

 

Note that we still have to return -1 as the result of division by zero. The reason for this is that the Calculate function’s result has to be an integer – it can’t be empty so something needs to be returned (in case of Nullable<int> we could return null). We've just included Boolean based information of whether calculation went wrong or not. Also note that view function btnCalculate_Click accepts a boolean result and decides how to pass this information to the user. Now we have the responsibility distributed to proper places and the user is not presented with a -1 result when something goes wrong. The division of 10 by 0 returns an error message but the division of -1 by 1 returns -1, as it should.

Solution 3 - if you hate out or ref

Some folks don't like using out or ref type of parameters, saying it is not elegant. Maybe, but by using out in Calculate function we've returned both the result of the division and the information whether the calculation was processed correctly or not. Also, the Calculate function sent a detailed description of its execution to the log for further analysis.

If you don’t like returning more than one result there is a number of solutions to this problem. One may be to return an aggregated object instead of one int and one bool.

We could create a Result class which consists both of a calculated number and a result:

class Result
{
   int resultNumber;
   Boolean isSuccess;

public int ResultNumber
{
   get { return resultNumber; }
}

public Boolean IsSuccess
{
   get { return isSuccess; }
}

public Result(int resultNumber, Boolean isSuccess)
{
   this.resultNumber = resultNumber;
   this.isSuccess= isSuccess;
}
}

Now we have to modify the Calculate function, so it returns a Result object

private Result Calculate(int dividend, int divider)
{
   try
   {
      int result = dividend / divider;
      return new Result(result, true);
   }
   catch (DivideByZeroException ex)
   {
      Logging.ErrLog(ex);
      return new Result(-1, false);
   }
}

and adapt btnCalculateClick to the new situation:

private void btnCalculate_Click(object sender, EventArgs e)
{
   int dividend = int.Parse(txtDividend.Text);
   int divider = int.Parse(txtDivider.Text);
   Result result = Calculate(dividend, divider);
   if (result.IsSuccess== true)
   {
      txtResult.Text = result.ResultNumber.ToString();
   }
   else
      txtResult.Text = "can't divide";
   }

Now that's elegant !

Responsibilities are set, we use neither out nor ref parameters, we know whether calculation went wrong or not and the calculated number is returned separately.

Of course, this is just an example of a result class, there are thousands of different result sets possible. We could also program a smart return class with the capability to contain results of any type, not just integer, but this far outside the scope of this article. 

Remember: always take care to return both the result of an operation and the information about what happened inside it.

Thanks for reading and regards

Dominik Steinhauf
CEO, .Net developer, software architect
www.indesys.pl

If you need help with your software project, or need customized software for your company, contact me at: dominik.steinhauf@indesys.pl

 

One of readers suggested that there could be Result<T> class to return function result and object. Yes, this is also a very good solution. More on type parameters and generics here in my another article: http://linkd.in/1Gq5K8f

Like
Reply
Katarzyna Młynarczyk

Accredited Service Design Master | Entrepreneur | AI Design | Insights as a Service IaaS | ALP - Center for Leadership, She's Innovation and Women Leaders EU 2024 programmes fellow | Sustainable Services

9y

Great to see your activity Dominik :) Keep on going!

To view or add a comment, sign in

Insights from the community

Explore topics