Exception Handling – Frequency and Log Detail Best Practices

cexception handlingloggingnetprogramming practices

I am working on a fairly complex .NET application that interacts with another application. Many single-line statements are possible culprits for throwing an Exception and there is often nothing I can do to check the state before executing them to prevent these Exceptions.

The question is, based on best practices and seasoned experience, how frequently should I lace my code with try/catch blocks? I've listed three examples below, but I'm open to any advice.

I'm really hoping to get some pros/cons of various approaches. I can certainly come up with some of my own (greater log granularity for the O-C approach, better performance for the Monolithic approach), so I'm looking for experience over opinion.


EDIT: I should add that this application is a batch program. The only "recovery" necessary in most cases is to log the error, clean up gracefully, and quit. So this could be seen to be as much a question of log granularity as exception handling. In my mind's eye I can imagine good reasons for both, so I'm looking for some general advice to help me find an appropriate balance.


Monolitich Approach

class Program{
    public static void Main(){
        try{
            Step1();
            Step2();
            Step3();
        } catch (Exception e) {
            Log(e);
        } finally {
            CleanUp();
        }
    }

    public static void Step1(){
        ExternalApp.Dangerous1();
        ExternalApp.Dangerous2();
    }

    public static void Step2(){
        ExternalApp.Dangerous3();
        ExternalApp.Dangerous4();
    }

    public static void Step3(){
        ExternalApp.Dangerous5();
        ExternalApp.Dangerous6();
    }
}

Delegated Approach

class Program{
    public static void Main(){
        try{
            Step1();
            Step2();
            Step3();
        } finally {
            CleanUp();
        }
    }

    public static void Step1(){
        try{
            ExternalApp.Dangerous1();
            ExternalApp.Dangerous2();
        } catch (Exception e) {
            Log(e);
            throw;
        }
    }

    public static void Step2(){
        try{
            ExternalApp.Dangerous3();
            ExternalApp.Dangerous4();
        } catch (Exception e) {
            Log(e);
            throw;
        }
    }

    public static void Step3(){
        try{
            ExternalApp.Dangerous5();
            ExternalApp.Dangerous6();
        } catch (Exception e) {
            Log(e);
            throw;
        }
    }
}

Obsessive-Compulsive Approach

class Program{
    public static void Main(){
        try{
            Step1();
            Step2();
            Step3();
        } finally {
            CleanUp();
        }
    }

    public static void Step1(){
        try{
            ExternalApp.Dangerous1();
        } catch (Exception e) {
            Log(e);
            throw;
        }
        try{
            ExternalApp.Dangerous2();
        } catch (Exception e) {
            Log(e);
            throw;
        }
    }

    public static void Step2(){
        try{
            ExternalApp.Dangerous3();
        } catch (Exception e) {
            Log(e);
            throw;
        }
        try{
            ExternalApp.Dangerous4();
        } catch (Exception e) {
            Log(e);
            throw;
        }
    }

    public static void Step3(){
        try{
            ExternalApp.Dangerous5();
        } catch (Exception e) {
            Log(e);
            throw;
        }
        try{
            ExternalApp.Dangerous6();
        } catch (Exception e) {
            Log(e);
            throw;
        }
    }
}

Other approaches welcomed and encouraged. Above are examples only.

Best Answer

Catch at the granularity that is meaningful to your application. If you are not going to take a different action for a different exception source, then there is no need to break those things apart.