Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Advice on generic try catch

This is not so much of a problem but more feedback and thoughts. I have been considering an implementation for methods that have been tested thoroughly through our internal teams. I would like to write a generic exception catch method and reporting service.

I relize this is not as easy as a "try-catch" block, but allows for a uniform method for catching exceptions. Ideally I would like to execute a method, provide a failure callback and log all the parameters from the calling method.

Generic Try-Execute.

public class ExceptionHelper
{
     public static T TryExecute<T, TArgs>(Func<TArgs, T> Method, Func<TArgs, T> FailureCallBack, TArgs Args)
     {
            try
            {
                return Method(Args);
            }
            catch (Exception ex)
            {
                StackTrace stackTrace = new StackTrace();
                string method = "Unknown Method";
                if (stackTrace != null && stackTrace.FrameCount > 0)
                {
                    var methodInfo = stackTrace.GetFrame(1).GetMethod();
                    if (methodInfo != null)
                        method = string.Join(".", methodInfo.ReflectedType.Namespace, methodInfo.ReflectedType.Name, methodInfo.Name);
                }
                List<string> aStr = new List<string>();
                foreach (var prop in typeof(TArgs).GetProperties().Where(x => x.CanRead && x.CanWrite))
                {
                    object propVal = null;
                    try
                    {
                        propVal = prop.GetValue(Args, null);
                    }
                    catch
                    {
                        propVal = string.Empty;
                    }
                    aStr.Add(string.Format("{0}:{1}", prop.Name, propVal.ToString()));
                }
                string failureString = string.Format("The method '{0}' failed. {1}", method, string.Join(", ", aStr));
                //TODO: Log To Internal error system
                try
                {
                    return FailureCallBack(Args);
                }
                catch
                {
                    return default(T);
                }
            }
      }
}

What I know as draw backs.

  • Performance Loss using reflection
  • MethodBase (methodInfo) may not be available through optimization
  • The try-catch around the error handler. Basically I could use the TryExecute wrapper for the try-catch around the error call back however that could result in a stack overflow situation.

Here would be a sample implementation

var model = new { ModelA = "A", ModelB = "B" };
return ExceptionHelper.TryExecute((Model) =>
{
     throw new Exception("Testing exception handler");
},
(Model) =>
{
    return false;
}, 
model);

Thoughts and comments appreciated.

like image 210
Nico Avatar asked Sep 04 '12 00:09

Nico


2 Answers

That's a lot of code to put in a catch, including two more try/catch blocks. Seems like a bit of overkill if you ask me, with a good amount of risk that a further exception can obscure the actual exception and that the error information would be lost.

Also, why return default(T)? Returning defaults or nulls as indications of a problem is usually pretty sloppy. If nothing else, it requires the same conditional to be wrapped around every call to the method to check for the return and respond to... some error that has gone somewhere else now.

Honestly, that usage example looks pretty messy, too. It looks like you'll end up obscuring the actual business logic with the error-trapping code. The entire codebase will look like a series of error traps, with actual business logic hidden somewhere in the entanglement of it. This takes valuable focus off of the actual intent of the application and puts something of background infrastructure importance (logging) at the forefront.

Simplify.

If an exception occurs within a method, you generally have two sensible options:

  1. Catch (and meaningfully handle) the exception within the method.
  2. Let the exception bubble up the stack to be caught elsewhere.

There's absolutely nothing wrong with an exception escaping the scope of the method in which it occurs. Indeed, exceptions are designed to do exactly that, carrying with them useful stack information about what happened and where. (And, if you add meaningful runtime context to the exception, it can also carry information about why.)

In fact, the compiler even subtly hints at this. Take these two methods for example:

public int Sum(int first, int second)
{
    // TODO: Implement this method
}

public int Product(int first, int second)
{
    throw new NotImplementedException();
}

One of these methods will compile, one of them will not. The compiler error will state that not all code paths return a value on the former method. But why not the latter? Because throwing an exception is a perfectly acceptable exit strategy for a method. It's how the method gives up on what it's doing (the one thing it should be trying to do and nothing more) and let's the calling code deal with the problem.

The code should read in a way that clearly expresses the business concept being modeled. Error handling is an important infrastructure concept, but it's just that... infrastructure. The code should practically scream the business concept being modeled, clearly and succinctly. Infrastructure concerns shouldn't get in the way of that.

like image 141
David Avatar answered Sep 27 '22 19:09

David


This is very rarely going to be useful.

It covers only cases where:

  1. The method has a well-defined means of obtaining an appropriate return value in the face of failure.
  2. You'd actually care to log that it happened.

Now, 2 is very common with exceptions of all sorts, but not where 1 is true too.

1 of course is rare, since in most cases if you could produce a reasonable return value for given parameters by means X you wouldn't be trying means Y first.

It also has a default behaviour of returning default(T) - so null or all zeros - if the fallback doesn't work.

This only works where your case 1 above has "something that just returns null as a result because we don't really care very much what this thing does", or where the called method never returns null, in which case you then test for null, which means that your real error-handling code happens there.

In all, what you've got here is a way in which exceptions that would be trappable by real code have to be caught for by testing (and sometimes testing + guesswork) instead, and those that would bring down a program in a clear place with nice debugging information will instead put it into a state where you don't know what's going on anywhere, but at least of the few dozen bugs that got logged before something managed to bring it down fully, one of the is probably the actual problem

When you've a catch on some exception for a particular reason, by all means log the exception. Note that this is not so much to help find bugs (if that exception being raised there is a bug, you shouldn't be catching it there), but to cancel out the fact that having a catch there could hide bugs - i.e. to cancel out the very effect you are deliberately encouraging by putting catches all over the place. (E.g. you expect a regularly hit webservice to fail to connect on occasion, and you can go on for some hours with cached data - so you catch the failure and go on from cache - here you log because if there was a bug meaning you were never trying to hit the webservice correctly, you've just hidden it).

It's also reasonable to have some non-interactive (service or server) app log all exceptions that reach the top of the stack, because there's nobody there to note the exception.

But exceptions are not the enemy, they're the messenger. Don't shoot the messenger.

like image 21
Jon Hanna Avatar answered Sep 27 '22 19:09

Jon Hanna