Search code examples
robustnessfail-fast-fail-early

Fail early vs. robust methods


I'm constantly (since years) wondering the most senseful way to implement the following (it's kind of paradoxic for me):

Imagine a function:

DoSomethingWith(value)
{
    if (value == null) { // Robust: Check parameter(s) first
        throw new ArgumentNullException(value);
    }

    // Some code ...
}

It's called like:

SomeFunction()
{
    if (value == null) { // Fail early
        InformUser();

        return;
    }

    DoSomethingWith(value);
}

But, to catch the ArgumentNullException, should I do:

SomeFunction()
{
    if (value == null) { // Fail early
        InformUser();

        return;
    }

    try { // If throwing an Exception, why not *not* check for it (even if you checked already)?
        DoSomethingWith(value);
    } catch (ArgumentNullException) {
        InformUser();

        return;
    }
}

or just:

SomeFunction()
{
    try { // No fail early anymore IMHO, because you could fail before calling DoSomethingWith(value)
        DoSomethingWith(value);
    } catch (ArgumentNullException) {
        InformUser();

        return;
    }
}

?


Solution

  • This is a very general question and the right solution depends on the specific code and architecture.

    Generally regarding error handling

    The main focus should be to catch the exception on the level where you can handle it.

    Handling the exceptions at the right place makes the code robust, so the exception doesn't make the application fail and the exception can be handled accordingly.

    Failing early makes the application robust, because this helps avoiding inconsistent states.

    This also means that there should be a more general try catch block at the root of the execution to catch any non fatal application error which couldn't be handled. Which often means that you as a programmer didn't think of this error source. Later you can extend your code to also handle this error. But the execution root shouldn't be the only place where you think of exception handling.

    Your example

    In your example regarding ArgumentNullException:

    • Yes, you should fail early. Whenever your method is invoked with an invalid null argument, you should throw this exception.
    • But you should never catch this exception, cause it should be possible to avoid it. See this post related to the topic: If catching null pointer exception is not a good practice, is catching exception a good one?
    • If you are working with user input or input from other systems, then you should validate the input. E.g. you can display validation error on the UI after null checking without throwing an exception. It is always a critical part of error handling how to show the issues to users, so define a proper strategy for your application. You should try to avoid throwing exceptions in the expected program execution flow. See this article: https://msdn.microsoft.com/en-us/library/ms173163.aspx

    See general thoughts about exception handling below:

    Handling exceptions in your method

    If an exception is thrown in the DoSomethingWith method and you can handle it and continue the flow of execution without any issue, then of course you should do those.

    This is a pseudo code example for retrying a database operation:

    void DoSomethingAndRetry(value)
    {
       try
       {
           SaveToDatabase(value);
       }
       catch(DeadlockException ex)
       {
           //deadlock happened, we are retrying the SQL statement
           SaveToDatabase(value);
       }
    }
    

    Letting the exception bubble up

    Let's assume your method is public. If an exception happens which implies that the method failed and you can't continue execution, then the exception should bubble up, so that the calling code can handle it accordingly. It depends one the use-case how the calling code would react on the exception.

    Before letting the exception bubble up you may wrap it into another application specific exception as inner exception to add additional context information. You may also process the exception somehow, E.g log it , or leave the logging to the calling code, depending on your logging architecture.

    public bool SaveSomething(value)
    {
       try 
       {
           SaveToFile(value);
       }
       catch(FileNotFoundException ex)
       {
           //process exception if needed, E.g. log it
           ProcessException(ex);
           //you may want to wrap this exception into another one to add context info
           throw WrapIntoNewExceptionWithSomeDetails(ex);
       }
    }
    

    Documenting possible exceptions

    In .NET it is also helpful to define which exceptions your method is throwing and reasons why it might throw it. So that the calling code can take this into consideration. See https://msdn.microsoft.com/en-us/library/w1htk11d.aspx

    Example:

    /// <exception cref="System.Exception">Thrown when something happens..</exception>
    DoSomethingWith(value)
    {
       ...
    }
    

    Ignoring exceptions

    For methods where you are OK with a failing method and don't want to add a try catch block around it all the time, you could create a method with similar signature:

    public bool TryDoSomethingWith(value)
    {
       try 
       {
           DoSomethingWith(value);
           return true;
       }
       catch(Exception ex)
       {
            //process exception if needed, e.g. log it
            ProcessException(ex);
            return false;
       }
    }