Click here to Skip to main content
15,867,835 members
Articles / All Topics

Exceptions

Rate me:
Please Sign up or sign in to vote.
4.22/5 (6 votes)
28 Jan 2009CPOL7 min read 31.2K   8   8
How to handle Exceptions from a user / system administrator's point of view

Introduction

This is my first contribution to the World Wide Web, so be kind.

In the last couple of years, actual programming became less a challenge than it used to be. My move from VB6 to C#.NET gave some new impulses (I'm still learning the .NET Framework) but my focus shifted more to the technical designs and instruction of other developers.

For the last few months, I am instructing, by chatting and emailing documents, a few developers in India. And believe me, this put the challenge back in the job.

As I already mentioned, this involves quite a few documents. So why not put these documents on The Code Project and see what others have to say about it?

One of the first things I noticed when reviewing source code was the way exceptions were handled. If you take the intended audience (user and/or system administrators) into consideration, this part could do with some improvements. Now let's see if the rest of the world agrees with me.

Note: English is my second language, sorry for any wrong use of words/phrases.

Exceptions

Exceptions interrupt the happy path (http://en.wikipedia.org/wiki/Happy_path) in my application. For me, there are only three kinds of exceptions:

  1. Technical exceptions
  2. Business exceptions
  3. Actual bugs

To distinguish the first two, there is a very simple rule; if exactly the same call (with the same parameters and in the same context) could succeed at another point in time then it is a technical exception, otherwise it is a business exception.

Note: The timeframe we are talking about (for sync process) is generally a few milliseconds. If a server is really busy, then the client would get a (technical) time out exception. A few milliseconds later, the server could have time again to process such a request.

The reasons why we distinguish between these two kinds is:

  • Determining cause of problem by a system administrator.
    If it is a technical exception, then he should solve it.
    If it is a business exception, the user should solve it.
  • Should a client retry or not?
    Especially for a-synchronized processes, this is very useful.

Did you note that I do not blame the developer? I will come to that, do not worry.

Business Exception

In general, this is caused by tripping a (valid?) business rule. For example; storing the information about a user in a database could require a first name. If we did not receive such a first name, we raise a business exception.

If the exact same call is made a second time, we will raise exactly the same exception.

Technical Exception

In general, this has something to do with broken down hardware or unreachable external services. For example; storing the information about a user in a database requires a database. If the database is offline, we raise an exception. If the exact same call is made a second time, and the database is back online, this call would succeed.

Logical Exception

A logical exception is a very special kind of exception. It shows itself as a technical exception (we did not throw it) but resending the same call will always result in the same exception. So in nature, it is a (unhandled) business exception. Examples are:

  • Invalid data conversion like converting a string to an integer
  • Changes in external interfaces / incorrect version of external components
  • Invalid results of calculations like ‘division by zero’
  • Etc.

Generally this is a bug, in the widest sense of the word, and should be fixed by a developer (you did not think I would forget to blame the developers, did you?).

Sample

As an example, an application (in C#) which fetches details of a single user;

C#
public string GetDetailsOfUserAsXml(string userIdentification)
{
  //Get the data
  Guid UserId = new Guid(userIdentification);
  DataSet.userDataTable UserDataSet;
  UserDataSet = GetDetailsOfUser(UserId); // Call to data access layer

  //Convert the data
  string DataToReturn;
  string Username = UserDataSet.Rows[0]["name"].ToString();
  string UserAge = UserDataSet.Rows[0]["age"].ToString();
  //Dirty solution to build XML but please ignore for this example :-)
  DataToReturn = "<data><name>" + Username + "</name>" +
                 "<age>" + UserAge + "</age></data>";
  return DataToReturn;
}

If a user enters a valid and existing GUID, then the method follows the happy path and will return a response like:

XML
<data><name>me</name><age>36</age></data>

A typical novice developer would say “Ok, everything works. Done.”

Wrong Input

No, we are not done yet. Let's consider the situation where the user did not enter a GUID but the name ‘me’.

You would get an ugly logical exception: “GUID should contain 32 digits … “.

This exception was to be expected but does not help this user to solve the problem. So we update the application:

C#
public string GetDetailsOfUserAsXml(string userIdentification)
{
//Get the data
Guid UserId;
try
{
  UserId = new Guid(userIdentification);
}
catch (Exception)
{
  throw new Exception("Could not convert the received 
      userIdentification (" + userIdentification + ") to a guid");
}
…

The resulting error is now: Could not convert the received userIdentification (me) to a guid.

The user of this service now knows that we expected a Guid as userIdentification instead of the word ‘me’.

Please note that the user in this case is another developer, a normal user does not know the meaning of the word guid, let alone enter a guid as a parameter.

Unexpected Results (1)

We are not done yet. What happens if a user enters a identification which does not exist in the database?

Again an ugly logical exception: “There is no row at position 0“.

This particular exception is not needed, it is a valid ID but there is simply no data to display. Lets fix this ‘bug’;

C#
//Convert the data
  string DataToReturn;
  string Username = string.Empty;
  string UserAge = string.Empty;
  if (UserDataSet.Rows.Count != 0)
  {
    Username = UserDataSet.Rows[0]["name"].ToString();
    UserAge = UserDataSet.Rows[0]["age"].ToString();
  }
  //Dirty solution to build XML but please ignore for this example :-)
  DataToReturn = "<data><name>" + Username + "</name>" +
                 "<age>" + UserAge + "</age></data>";
  return DataToReturn;
}

The result is now an empty XML:

XML
<data><name/><age/></data>

Unexpected Results (2)

What else could go wrong, you might think. Consider this; the user enters the Guid belonging to ‘someone’ as parameter, but gets the response:

XML
<data><name>me</name><age>36</age></data>

Huh? This is not the expected result, we get the data for ‘me’ and not ‘someone’. The only reason is that the ID is mentioned twice in the database.

Note: This is not exactly a real life example but could happen after an upload at database level.

In this case, the input is valid and we get a valid response. But, in contradiction to the previous chapter, now we have to raise an exception instead of preventing one. Let's also fix this bug:

C#
//if (UserDataSet.Rows.Count == 0) do nothing, just use the empty strings
if (UserDataSet.Rows.Count == 1)
{
  Username = UserDataSet.Rows[0]["name"].ToString();
  UserAge = UserDataSet.Rows[0]["age"].ToString();
}
if (UserDataSet.Rows.Count > 1)
{
  throw new Exception("Could not identify a unique user using
    userIdentification '" + userIdentification + "', please contact your
    system administrator");
}
//Dirty solution to build XML but please ignore for this example :-)
DataToReturn = "<data><name>" + Username + "</name>" +
               "<age>" + UserAge + "</age></data>";
return DataToReturn;
}

The result is now a nice business exception: “Could not identify a unique user using userIdentification …”.

By the way, do not forget to fire the DBA for making such (non unique IDs) a major mistake.

What is Missing?

Ok, a lot of talking about exceptions, but I did not use a try-catch. Why?
Simply because I am not planning to do anything with an exception, so why catch it?

I only catch exceptions if I have to add information to an exception or at the highest level. In the last case, I present the user with a nice message box or place the exception in the eventlog and/or log file.

Please also note that exceptions should not be used to control the flow through the application. Also see the next chapter.

What Not To Do (1)

Exceptions are costly and should not be used to code normal application flow logic. Rather add additional checks. See the example below:

C#
public string TestFileLoopExceptionBased()
{
string Filename = "C:\\Inetpub\\wwwroot\\ExpSample\\App_Data\\TestDoc.txt";
string Result = string.Empty;
long StartTime = DateTime.Now.Ticks;
for (int i = 0; i < 100; i++)
{
  try
  {
    Result = System.IO.File.ReadAllText(Filename);
  }
  catch (Exception)
  { 
    //Ignore, file did not exist
  }
}
Result = (DateTime.Now.Ticks - StartTime).ToString();
return Result;
}
public string TestFileLoop()
{
string Filename = "C:\\Inetpub\\wwwroot\\ExpSample\\App_Data\\TestDoc.txt";
string Result = string.Empty;
long StartTime = DateTime.Now.Ticks;
for (int i = 0; i < 100; i++)
{
  if (System.IO.File.Exists(Filename))
  {
    Result = System.IO.File.ReadAllText(Filename);
  } 
}
  
Result = (DateTime.Now.Ticks - StartTime).ToString();
return Result;
}

Results:

  • TestFileLoop (valid filename) took 156250 ticks
  • TestFileLoopExceptionBased (valid filename) also took 156250 ticks
  • TestFileLoop (invalid filename) again took 156250 ticks
  • TestFileLoopExceptionBased (invalid filename) took 4531250 ticks

As you can see, the method based on exceptions took just a little bit more time, 29 times more!

Please note that in a real life situation, you should have a combination of both methods. The method TestFileLoop() does not contain any additional checks or save guards for other exceptions like:

  • What happens if the file can't be opened? 
  • What happens if the length of the file can't be determined? 
  • What happens if enough memory can't be allocated? 
  • What happens if the read fails? 
  • What happens if the file can't be closed?

The purpose for this sample was to show how expensive (in CPU cycles) exceptions, in relation to logical checks, are.

What Not To Do (2)

Consider the following piece of code:

C#
public string GetDetailsOfUserAsXml(string userIdentification)
{
try
{
//A lot of coding goes here ...
//Result could be <data><name/><age/></data>;
}
catch (Exception Exp)
{
return Exp.Message;
}
}

Instead of raising an exception, I return the error message (as a valid response!) to the calling party. A small program which could use this web service:

C#
public void btnGetData_Click()
{
//Get the details
string UserDataAsXml;
try
{
  UserDataAsXml = WebService.GetDetailsOfUserAsXml(textbox1.Text);
  //Parse the results, extract the name and display it
  XmlDocument UserData = new XmlDocument();
  UserData.LoadXml(UserDataAsXml);
  string UserName;
  UserName = UserData.SelectSingleNode("data/name").InnerText;
  MessageBox.Show("Username is " + UserName);
}
catch (Exception Exp)
{
  MessageBox.Show("Unable to display name because " + Exp.Message);
}
}

The developer expects exceptions and, if an exceptions occurs, displays a message informing the user what happened.

Now analyze what happens if an exception occurs in GetDetailsOfUserAsXml. Let's say the exception returned is "Could not identify a unique user using userIdentification 'x', please contact your system administrator";

The client receives an answer (not an exception!) and tries to parse this as XML. This would result in an actual exception:

System.Xml.XmlException: Data at the root level is invalid. Line 1, position 1 

At this point, nobody has an idea what went wrong!

Conclusion

Just a few pointers:

  • Catch exceptions if you:
    • present them to the user
    • if you want to add additional information before re-throwing them
  • Add exceptions if the results are different from the expectations for the current method. GetSingleRecord() should never return multiple records!
  • Prevent exceptions if possible.
  • Catch exceptions at the last possible point. In general, this is in the event a user started.
  • Messages in exceptions are one of the very few points in any application where a user actually reads instructions! So make sure your exceptions have something to tell the user; what went wrong and what to do to prevent/fix it.
  • Exceptions are costly, do not code normal application flow based on exceptions. Rather add additional checks.

History

  • 28th January, 2009: Initial post 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
Currently I am the technical supervisor for an outsourcing project in India.

Senior Programmer at ADP - Oct. 2002 till present
Programmer at Ecsoft - Oct. 2000 till Sep. 2002
Programmer at Centric - Jan. 1998 till Sep. 2000

Comments and Discussions

 
GeneralOne thing to note Pin
Shukaido12-Feb-09 5:38
Shukaido12-Feb-09 5:38 
I just wanted to point out that the use of the generic "catch(Exception e)" is rather frowned upon, because it can "swallow up" pertinant errors.

It would be better to have multiple catches for specific exceptions like Stack overflow, IO operations, or mathematical errors so that those don't get swallowed up in your "The code is screwed up at line 23" error messages and you can output a user friendly, but descriptive message to the user as well as logging the exact exception language in your log file.

Just thought I'd point that out.
QuestionConditional throwing? Pin
supercat929-Jan-09 15:25
supercat929-Jan-09 15:25 
AnswerRe: Conditional throwing? Pin
Shukaido12-Feb-09 5:26
Shukaido12-Feb-09 5:26 
GeneralRe: Conditional throwing? Pin
supercat912-Feb-09 6:29
supercat912-Feb-09 6:29 
GeneralRe: Conditional throwing? Pin
Shukaido12-Feb-09 8:07
Shukaido12-Feb-09 8:07 
GeneralRe: Conditional throwing? Pin
supercat912-Feb-09 11:12
supercat912-Feb-09 11:12 
GeneralRe: Conditional throwing? Pin
Shukaido12-Feb-09 11:38
Shukaido12-Feb-09 11:38 
GeneralRe: Conditional throwing? Pin
Shukaido12-Feb-09 8:25
Shukaido12-Feb-09 8:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.