Running with Code Like with scissors, only more dangerous

3Mar/080

Exceptional Exception Handling: Taking Exception with the Community

I could have gone on with the title for a while longer.

Frequently when I’m working on an application, it occurs to me that the user doesn’t care about the technical reasons behind an exceptional condition.  Typically, the user simply cares that something went wrong; or, sometimes, the user doesn’t care, especially if you can make the program recover gracefully.

Since the only .NET-sanctioned way to handle errors is through structured exception handling, and since I love SEH, I don’t want to be too hard on it.  But the truth is, there are certainly instances where "On Error Resume Next" would be nice.

Consider the following constructor:

   1:          public ConnectionBase(string server, int port)
   2:          {
   3:              m_server = server;
   4:              m_port = port;
   5:              try
   6:              {
   7:                  m_ipep = new IPEndPoint(Dns.GetHostEntry(server).AddressList[0], port);
   8:              }
   9:              catch (SocketException) { }
  10:          }

This class essentially encapsulates a TcpClient.  Now, what I do here in the constructor is attempt to resolve the host address.  In the event that there is an error, for whatever reason, Dns.GetHostEntry will raise a SocketException, typically telling me that the URI could not be found.

Now, the general wisdom about SEH is to not just "eat" an exception; rather, we prefer to do something worthwhile with it.  But in this case, I haven’t invalidated the state of my class, I haven’t invalidated the state of the system, or done anything to harm my class.  In fact, if the m_ipep (an IPEndPoint object) isn’t initialized when Connect() is called, it attempts to do it as well:

   1:          public virtual bool Connect()
   2:          {
   3:              // ...
   4:              if (m_ipep == null)
   5:              {
   6:                  try
   7:                  {
   8:                      m_ipep = new IPEndPoint(Dns.GetHostEntry(m_server).AddressList[0], m_port);
   9:                  }
  10:                  catch (SocketException se)
  11:                  {
  12:                      OnError(string.Format("Your computer was unable to resolve hostname {0}.  If necessary, add an entry to %SystemRoot%\\system32\\drivers\\etc\\hosts, or flush your DNS resolver cache, and try again.",
  13:                          m_server), se);
  14:                      return false;
  15:                  }
  16:              }
  17:              //...
  18:              return true;
  19:          }

This method correctly bubbles an error to the using classes in the event that the IP end point could not be located.  You might ask, though – what is the advantage of using early resolution?  That is, I could drop the exception code altogether.

Well, if someone is debugging their code using my library and has first-chance exception handling enabled, it is easy enough to spot where a parameter might be causing issues.

Once the class is initialized, someone using a debugger can see the internals of my class, including the resolved end point.  This may help them identify the remote end point’s IP address; perhaps that particular server is not functional.

There are other issues with the "best practices" as well — for instance, they are sometimes at odds with each other.  The .NET Framework Guidelines say not to overuse catch, but also to not to just catch a general exception.  Consider:

   1:              try
   2:              {
   3:                  // create an instance of this variable to make sure that the data provider can be loaded.
   4:                  DataProvider provider = DataProvider.Instance;
   5:              }
   6:              catch (TypeLoadException tle)
   7:              {
   8:                  throw new ConfigurationErrorsException(
   9:                      string.Format("Type '{0}' was not a valid type according to the runtime.", keyProviderType), tle);
  10:              }
  11:              catch (TargetInvocationException tie)
  12:              {
  13:                  throw new ConfigurationErrorsException(
  14:                      string.Format("An exception was raised in the constructor of type '{0}'.  More information may be available in the contained exception.", keyProviderType), tie.InnerException);
  15:              }
  16:              catch (MethodAccessException mae)
  17:              {
  18:                  throw new ConfigurationErrorsException(
  19:                      string.Format("Security policy prevented access to the default public constructor of type '{0}'.", keyProviderType), mae);
  20:              }
  21:              catch (MissingMethodException mme)
  22:              {
  23:                  throw new ConfigurationErrorsException(
  24:                      string.Format("No default public constructor existed on the type '{0}'.  Change the application configuration so that the 'dataProvider' property of the 'dataConfiguration' section points to a type with a default public constructor and inherits from the '{1}' type.", keyProviderType, typeof(KeyProvider)), mme);
  25:              }
  26:              catch (MemberAccessException memae)
  27:              {
  28:                  throw new ConfigurationErrorsException(
  29:                      string.Format("Could not create an instance of the abstract type '{0}'.  Change the application configuration so that the 'dataProvider' property of the 'dataConfiguration' section does not indicate an abstract type.", keyProviderType), memae);
  30:              }

In the code above, I have legitimate reasons for catching these – retrieving the DataProvider.Instance property is documented to raise these exceptions, which are in turn raised by Activator.CreateInstance.  The real problem with this kind of situation is that the outer calling code needs to be shielded, and so ultimately, the calling code handles only a ConfigurationErrorsException.  This in turn is handled and logged to the event log before terminating the Windows Service that hosts this code.

You can read more in the .NET Developer’s Guide – Exception Handling article on MSDN.  Just bear in mind – you probably want different exception handling strategies if you’re writing a user interface vs. library code vs. data access code.  At the end of the day, it’s how much bad stuff you want to show your users, how much bad stuff you can handle internally to your application, and how much bad stuff you’re willing to let your application produce because you over-caught exceptions.

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

ERROR: si-captcha.php plugin says GD image support not detected in PHP!

Contact your web host and ask them why GD image support is not enabled for PHP.

ERROR: si-captcha.php plugin says imagepng function not detected in PHP!

Contact your web host and ask them why imagepng function is not enabled for PHP.

No trackbacks yet.