Using Exceptions and result objects to control flow

In C# we do not declare what exceptions a method/function will throw in case of a problem. In Java they do. There was a great discussion in Java world about it and they came to no conclusion. Anyway there should be some plan, some convention that it’s useful to follow in order to use exceptions for our benefit. There can be many different conventions but I’m going to present to you only some of them, that seem to be consistent.

Let’s start from the beginning: what do we use Exceptions for ? Some exceptions are thrown by 3rd party or system functions that you use, some are thrown by you by using ‘throw new Exception’. They are used to signal the occurrence of an unexpected situation, something exceptional. In normal situation exceptions should not happen. In typical positive scenario you should not see any exceptions in application log. Don’t use exceptions to jump in code (like old ‘go to’ command). It’s not a good reason to use exception.

Ok, so we know when to use exception. Now let’s take a look at consequences of using an Exception. When an Exception is thrown, the runtime environment jumps to the closest catch block that matches exception type. Common mistake or conscious  omission that people make is to let an exception to happen or throw an exception and do not think of immediate consequences.

Let’s look at code example here:

        private void ReadFile(String Path)
        {
            using (StreamReader sr = new StreamReader(Path))
            {
                while (!sr.EndOfStream)
                {
                    Console.WriteLine(sr.ReadLine());
                }
            }
        }

There are no try/catch blocks here, so this code does not care about consequences. any exception that may occur (in case file is not found or broken) will be propagated upwards, which means no control and no explanation in log files as to what happened, where and why. If you log stack trace then you may get information that file was not found and even get line number but without information on the Path itself. Maybe it was blank, or in wrong format, or maybe you should create directory first.

Let’s look at some more drastic example:

        private void ReadFile(String Path)
        {
            using (StreamWriter sw = new StreamWriter(System.IO.Path.GetTempFileName()))
            {
                sw.WriteLine("Some temporary text");
            }

            using (StreamReader sr = new StreamReader(Path))
            //exception here, file not found !!!
            {
                while (!sr.EndOfStream)
                {
                    Console.WriteLine(sr.ReadLine());
                }
            }
            File.Delete(tempPath);
        }

Method creates some temporary file and then tries to read some other file. And then deletes temporary file. In case there is an exception when reading file from Path, you will be routed to executing method somewhere upwards in code, and you have no idea where. Also you do not care about temporary file that have been just created and will be left forever.

One of basic rules for using exceptions is to handle consequences of an error inside a method where exception happened. In this case we’re talking about logging all useful info regarding error, and sweeping (cleaning):

        private void ReadFile(String Path)
        {
            String tempPath=null;
            try
            {
                tempPath = System.IO.Path.GetTempFileName();
                using (StreamWriter sw = new StreamWriter(tempPath))
                {
                    sw.WriteLine("Some temporary text");
                }

                using (StreamReader sr = new StreamReader(Path))
                {
                    while (!sr.EndOfStream)
                    {
                        Console.WriteLine(sr.ReadLine());
                    }
                }
                
            }
            catch (FileNotFoundException fnfex)
            {
                Logging.ErrLog(fnfex, "File in error: " + Path);
            }
            catch (Exception ex)
            {
                Logging.ErrLog(ex);
            }
            finally
            {
                File.Delete(tempPath);
            }
          
        }

The reason we have two exceptions to handle is because FileNotFoundException is an expected one, and the other one is just in case it’s not about File not being found (Sometimes you may omit such Exception if you believe there is absolutely no way to happen). So we would like to write in details what’s happening by logging file name that causes error (Path).

But there is another problem to handle, which is that there is no information back to executing function, to the parent, on what happened to these files. To parent function all is hidden now. There are two solutions to this problem: 1) return bool or complex type object that contains result (bool or some enum).  2) re-throw an exception after logging it and sweeping. It’s up to you to decide which path you go here. The problem with first solution is that it does not require the parent to check for result. The problem with second solution is that it is a kind of ugly ‘go to’ command. Let’s see example of first solution and problem:

        private bool SaveFile(String Path)
        {
            try 
            {
                //some dangerous code
            }
            catch (Exception ex)
            {
              Logging.Err(ex);
              return false;
            }
            return true;
        }

        private void ProcessFiles()
        {
            SaveFile("readme.txt"); //no check of result, ignores                       
                                    //problems
        }

As you can see the compiler does not throw any warning or error if we forget to check what was the result of SaveFile. So you have to be very careful using this style as it may lead to disastrous effects (like trying to manipulate file that was not successfully written).

So recently I tend to use Exceptions more and returning results less. And even if I do create methods that return result, they return an object with it (as a complex ReturnResult object, which consists of boolean Success property and ResultObject property which is the returned object, if any). By doing that there is almost no chance I forget to check result.

But let’s get back to exceptions re-throwing for a moment. If you would like to keep stack trace information you need to use ‘throw;’. Example:

        private MessageSender(MessageDTO Message)
        {
            try
            {
                ValidateInputParameters(Message.Sender, Message.Recepients, Body);
                this.Recepients = GetTransformedList(Message.Recepients);
                this.Sender = Message.Sender;
                this.Body = Message.Body;
            }
            catch (MyException)
            {
                throw;
            }
            catch (Exception ex)
            {
                Logging.ErrLog(ex, "Unexpected Error creating MessageSender object");
                throw ;
            }
        }

In C# ‘throw;’ means you are re-throwing the original exception. Usually I’m doing this after sweeping and logging more details of an error. You may ask why I’m not logging exception in ‘catch (MyException)‘ block. It’s because of a convention I use that my custom exceptions are handled internally by my own functions. In this case by method ValidateInputParameters logs error details itself before re-throwing. It’s OOP solution because this method knows best which parameters where wrong and why. Not the executing (parent) method. Also I do not need to log exception again here. Catching MyException allows re-throwing without re-logging (writing to log file)

Exceptions are also useful to prevent from processing input data that is out of scope (range, requirements etc). For example instead of using if (Path==null) throw new ArgumentNullException(“Path”) all the time, I create validation and check functions that throw errors in case something is null or empty. Example:

        private void ReadFile(String Path)
        {
                Check.NotEmpty(Path, nameof(Path)); //throw exception

Check function does not have any try/catch block inside it, it throws an exception when Path is null or contains no characters. It’s much more elegant and readable way of using exceptions for input validation. By the way: always check input parameters if they meet basic requirements, especially null condition. A system cannot process properly based on bad input.

In some cases unhandled Exceptions can be very dangerous. For example you you are in main thread of window application or main thread of a service. It will close your application with some nasty consequences. In such cases you will probably want to use try/catch block around it. But using try/catch, especially nested try/catch in one function makes it unreadable.

       private void ReadAndDeleteFile(String Path)
        {
            try
            {
                using (StreamReader sr = new StreamReader(Path))
                {
                    while (!sr.EndOfStream)
                        Console.WriteLine(sr.ReadLine());
                }
            }
            catch (Exception ex)
            {
                Logging.ErrLog(ex);
                throw;
            }
            finally
            {
                try
                {
                    File.Delete(Path);
                }
               catch (Exception ex)
                {
                    Logging.ErrLog(ex, "oops, something went wrong");
                }
            }
          
        }

See ? try/catch inside try/catch when in fact all this function does is read a file. In such situations just extract other methods from your main method. And if the new helper method is absolutely safe (does not re-throw exceptions) you may want to call it beginning with ‘Safe’ or ‘Try’. (like int32.TryParse in .Net Framework):

        private void ReadAndDeleteFile(String Path)
        {
            try
            {
                using (StreamReader sr = new StreamReader(Path))
                {
                    while (!sr.EndOfStream)
                        Console.WriteLine(sr.ReadLine());
                }
            }
            catch (Exception ex)
            {
                Logging.ErrLog(ex);
            }
            finally
            {
                SafeDelete(Path);
            }
        }

Or even this:

        private void ReadAndDeleteFile(String Path)
        {
            try
            {
                ReadFile(Path); //extracted non safe method
            }
            catch (Exception ex)
            {
                Logging.ErrLog(ex);
                throw;
            }
            finally
            {
                SafeDelete(Path);
            }
        }

Ease of reading code is one of most important factors when handling huge, important software that is going to be maintained for many years. Instead of concentrating your focus on try/catch and validation methods you look at this method and you immediately see that it reads a file and then safely deletes it in any circumstance. The reason I’m using SafeDelete (it catches all exceptions and does not re-throw), is because exception happening inside a finally block is a disaster and you don’t want that.

Sometimes you may choose to create a safe method that creates some object or performs some operation without re-throwing exception. But you want to return an object together with all information on what was happening. Typically in case of success you just return boolean true for success and the object itself (for example Customer record from database, or MessageSender as in example), or null (or safe empty object) with all exceptions and problems that occurred inside executed method.

        public void SendTest(MessageDTO Message)
        {
            var result = Helper.TryCreatingObject(() =>
               new MessageSender(Message));
            if (result.Success)
            {
                var sendingResult = result.ResultObject.Send();
                if (sendingResult.Success)
                    Console.Write("Success sending");
                else
                    Console.WriteLine("Failed to send");
            }
            else
                Console.WriteLine("Invalid input, not sending");
        }

TryCreatingObject returns ReturnResult object, that contains ResultObject (of type MessageSender) and Success (of type boolean). As you can see creation of object and sending an email is a process that may result in three ways. Not two ! One is invalid input. Second is success sending email, and third is failed to send an email. If you used only one boolean result you would lose focus on what was happening. Of course you may refactor this method so it tries to create email object and send email and then return some enum that contains even more results (e.g. enum EmailResults { InvalidInput, SentOK, SentFailedTemporarily, SentFailedPermanent} etc).

As you can see we do not pass any exceptions this time from executed methods to SendTest method. It’s because we specifically want to control flow. But again, be vigilant – if you forget to check result of TryCreateObject or Send methods then you are in big trouble because your software proceeds without acknowledging the fact that no email was sent. Both methods here are safe, meaning they do not re-throw exceptions, just return full result set (including exceptions).

You may think that all methods should contain try/catch block, but I think we should rather try to omit it, when possible, because it makes it harder to read code. It depends if you use 3rd party libraries that you are not 100% sure for reliability, or if you operate in environment which is unstable (poor connection, slow database etc).

The general rule I suggest is that code higher in hierarchy should not contain to much of checks and try/catch decorations because it serves as an index, like in a book, an index that makes it easier to see what a class or public method is responsible for and what it is doing. Which code is more readable at first glance ? This:

        public void SendEmail(MessageDTO Message)
        {
            var result = Helper.TryCreatingObject(() =>
               new MessageSender(Message));
            if (result.Success)
            {
                var sendingResult = result.ResultObject.Send();
                if (sendingResult.Success)
                    Console.Write("Success sending");
                else
                    Console.WriteLine("Failed to send");
            }
            else
                Console.WriteLine("Invalid input, not sending");
        }

or this:

      SendEmail(new MessageDTO { Sender = "dominik@ble.com", Recepients = "adres1@poczta.com,adres2@poczta.com", Body = "To jest body emaila" });

The second one is the same code, but with some portions extracted and moved around. So in the main thread function you just want to SendEmail and not think about handling all possible results. And a method that is somewhere deeper inside will check results and display some results to the user. It works like index or hypertext, because you can always check what this SendEmail method does in detail, if you want to. No Exceptions and no return result. Not at first glance. You want to peek SendEmail details ? Here you are:

static void SendEmail(MessageDTO Message )
 {
            String text = $"email from: { Message.Sender} to:          
            {Message.Recepients}";

            MessageSender.Send(Message,
            () => Console.WriteLine("Invalid input for "+text), 
            () => Console.WriteLine("Failed to send for "+text), 
            () => Console.WriteLine("Send successful for"+text));
 }

Again, try/catch blocks are hidden inside MessageSender.Send function, but you pass three functions (as delegates) for three different outcomes of email creation and sending. This is just an example, you may create some other mechanism, but you see where it is going. Flow control and Exceptions handling should be used wisely and with clear purpose, and if you can hide it – do it, for readability.

Thanks for reading

Dominik Steinhauf

CEO, IT for 20 years, .Net developer, software architect at Creative Yellow Solutions (formerly Indesys)

If you need help with your software project, or need customized software for your company, contact me at:
dominik.steinhauf ( at) cys.biz.pl

Leave a Reply

Your email address will not be published. Required fields are marked *