skip to Main Content

In the given JavaScript code snippet, there are two functions – handleClientError and handleServerError – that handle client-side and server-side errors, respectively. The handleError function is used to determine which error type needs to be handled based on the error parameter passed to it.

However, if a new error type, let’s say ABCSideError, needs to be handled, the code currently requires modifying the handleError function to add a new conditional check for the ABCSideError type and define a new function to handle it.

How to avoid this manual process of adding new checks and functions for each new error type, means that adding a new error type should only require defining a new function and nothing else.

try{
  // some code
}catch(error)
{
  handleError(error)
}

function handleError(error) {
  if (error instanceof ClientSideError) {
    return handleClientError(error);
  }
  return handleServerError(error);
}

function handleClientError(error) {
  // handle error from client side
}

function handleServerError(error) {
   // handle error from server side
}

Despite multiple attempts, I have not been able to find an implementation that does not require an if-else or switch statement to handle new error types dynamically.

2

Answers


  1. The Open-Closed Principle has nothing to do with "if-else" or "switch" statements. The OCP states that classes should be open for extension, but closed for modifications. In other words, don’t break your clients by allowing new behaviors to escape from your current classes. That’s it.

    By encapsulating the error handling and not throwing or rethrowing new errors, you’ve already achieved the goal of the OCP, which is to not make breaking changes to clients that currently depend on this code.

    Avoiding "if-else" and "switch" statements is a part of the refactoring process, where they’re considered "code smells" that indicate some form of refactoring may be desirable. But this doesn’t exactly apply here, as this is an error handler for external libraries that throw errors that are created out of your control. You don’t have the opportunity to modify them. Instead, the error handler becomes the place to encapsulate them so the new errors can’t violate the OCP. If that requires extra "if instanceof" tests, then that’s what you have to write.

    Login or Signup to reply.
  2. Here is an example. if you have a large and growing number of error types, this could be a way.

    class ErrorHandler {
      handleError(error) {
        // This will be overridden in subclasses
      }
    }
    
    class ClientErrorHandler extends ErrorHandler {
      handleError(error) {
        if (!(error instanceof ClientSideError)) {
          return null;
        }
        // handle error from client side
      }
    }
    
    class ServerErrorHandler extends ErrorHandler {
      handleError(error) {
        if (!(error instanceof ServerSideError)) {
          return null;
        }
        // handle error from server side
      }
    }
    

    Then, when calling the function, you would DI the array of handlers. That way riskyOperation requires no modification even if you have new concrete error handlers.

    function riskyOperation(errorHandlers) {
      try {
    
        // do stuff ...
    
      } catch (error) {
        for (let handler of errorHandlers) {
          if (handler.handleError(error)) {
            break;
          }
        }
      }
    }
    
    riskyOperation([new ClientErrorHandler(), new ServerErrorHandler()]);
    

    Key concepts of OCP are:

    • abstraction
    • polymorphism
    • the scope of the client and servers/services
    • DI

    and then the client requires no modifications even if the number of services grows.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search