DEV Community

Cover image for Error handling: Are You In Charge or In Chaos?
Olaniyi Philip Ojeyinka
Olaniyi Philip Ojeyinka

Posted on • Edited on

Error handling: Are You In Charge or In Chaos?

As users in today's fast-paced world, our attention spans are shrinking by the day. We want efficiency, we want to be able to open our favourite app, perform an action, and finalize our transaction swiftly. Nobody wants to deal with issues just to accomplish a simple task; it's detrimental to the overall customer experience.

Unfortunately, in the world of software, things don't always go as expected, and that's okay. What matters most is how well-prepared we are for such scenarios and the plans in place to resolve them swiftly.

To navigate such scenarios more smoothly, we can simplify our lives by designing our software with robust error handling in mind. Some practices that significantly contribute to well-handled software include and not limited to:

A. Consistencies: Maintain a consistent approach across your entire codebase. This not only enhances code readability but also minimizes the risk of encountering errors stemming from inconsistent error handling practices. One effective strategy is to establish an abstraction class, function, or helper that can be called when necessary. Another method to enforce consistency is by adopting a uniform approach to handling operation results. A good example is the use of a result template, as illustrated in the TypeScript example below. This approach is adaptable and can be seamlessly incorporated into various technology stacks.

interface Result<T> {
  success: boolean;
  data: T | null;
  error: string | null;
}

Enter fullscreen mode Exit fullscreen mode

you can achieve this in php with

class Result {
    public $success;
    public $data;
    public $error;

    public function __construct($success, $data, $error) {
        $this->success = $success;
        $this->data = $data;
        $this->error = $error;
    }
}
//and to use, just intialise an object with data from your operation
 new Result(true, $result, null);

Enter fullscreen mode Exit fullscreen mode

similarly in ruby

class Result
  attr_accessor :success, :data, :error

  def initialize(success, data, error)
    @success = success
    @data = data
    @error = error
  end
end
Enter fullscreen mode Exit fullscreen mode

or if you are stingy with your storage space for the result instances, you can basically use's you language provided map or hash as follows
in ruby,

 # success 
    { success: true, data: result, error: nil }

#failure

    { success: false, data: nil, error: "error message" }

Enter fullscreen mode Exit fullscreen mode
 // success 

        return ['success' => true, 'data' => $result, 'error' => null];

//failure

        return ['success' => false, 'data' => null, 'error' => "error message"];

Enter fullscreen mode Exit fullscreen mode

or have a uniform way of making use of your language exceptions handling, although i would recommend the above as it provide us the advantage of being able to return early there by making our codebase much more readable and easy to follow and also reduce the performance overhead of try catch/throw/exception/rescue/raise or whatever you language of choice uses.

B. Specificity: Clearly defining and identifying errors is crucial for efficient troubleshooting. When dealing with RESTful APIs, leverage accepted error codes. In the previous example, observe the use of the boolean "success" attribute in our control structure. This approach enhances specificity, relieving contributors of the uncertainty regarding potential issues. By adhering to recognized error codes and providing clear error messages, you streamline the debugging process and empower developers with the information they need for effective issue resolution.

C. Differentiate between Failure and Actual Error: Distinguish between routine failures and critical errors, while both may seem similar, they are not the same. Most Errors result to Failure, Errors could be syntax errors, logical errors and others. Sometimes, its possible for failure to happen as a result of validation, third party provider downtime and other issue that are out of the scope of our app itself. This is important especially when dealing with a financial software, you may want to know which one you are dealing with and how to properly resolve them. For example, when dealing with a payment provider and you need to make payment, for some reason their API isn't available, that isn't an error but a failurem that you may want to confirm before making a conclusion to ensure integrity of the application and in the example provided prevent double crediting/spending.

D. Preparing for the Unknown & Third-Party Communication: Anticipate potential issues, and build in resilience for unexpected scenario.
Avoid outsourcing validation when dealing with third-party interactions to retain control and oversight. Be Paranoid, assume your third party API doesn't know what they are doing and tackle it accordingly, for example, even if third party API returns 200 OK, you can safely still confirm everything is alright using the necessary body attributes as there are cases where a thirdparty API may not give the right response code or follow the communication's pattern/protocol best practices.

E. Handling Timeout: When a service isn't responding in a timely manner, you don't want to keep your users waiting, you want to implement strategies for managing timeouts and implement the right retry and confirmation machanism. For payment related request, you don't want to retry until you can confirm previous request wasn't already attended to.

F. Proper logging, monitoring and reporting:
Despite our best efforts, encountering a bug or two is inevitable. When these situations arise, the key is to investigate quickly and resolve customer issues as soon as possible. Establishing robust logging and monitoring tools becomes paramount in expediting issue resolution because you can't solve a problem that you are not aware of or understand its root cause.

Implementing effective logging mechanisms allows you to capture crucial information about the application's behavior, potential errors, and the sequence of events leading up to an issue. Coupled with monitoring tools, you gain real-time insights into the performance and health of your system. This proactive approach not only aids in identifying and addressing problems promptly but also enhances your ability to provide swift solutions to customer concerns.

G. Client Error Presentation:

In the event of an error occurrence, once the issue has been logged appropriately, the next crucial step is how to present this error to the customer. It's important to mask the actual technical details of the problem and instead provide a clear and meaningful error message to the end user.

The approach to handling and rendering errors to the client can vary based on the team's preferences or project requirements. Teams may opt for an optimistic approach, instilling confidence by emphasizing potential solutions or alternative paths. On the other hand, a pessimistic approach may involve transparently communicating the issue's severity to manage user expectations.

Additionally, the implementation of a retry mechanism can be considered. This allows the system to attempt the operation again, providing a seamless experience for the user while the underlying issue is addressed in the background.

By carefully considering the client error presentation strategy, you not only enhance the user experience during challenging situations but also contribute to maintaining trust and satisfaction among your customers.

Top comments (0)