1 minute read

Usually when we run scheduled jobs we like to bunch a few things together for convenience. Let’s say that you have hourly, daily, weekly, monthly task list that needs to be executed. And that you use cron to execute those tasks. A good approach here would be to have those same cron tasks defined in crontab, one entry for each task. However we rarely do that because we have multiple tasks that can run together at the same time so we define a method that will run them one after another. The thing we usually forget is that one of those methods, usually not the last one, will raise an exception and prevent the other methods from running. Something like:

def run_hourly
  update_counters
  send_emails
  sync_logs
end

Now imagine that the update_counters method fails and no emails get sent and the logs aren’t synced (whatever that means). We don’t want that situation to happen, but we also don’t want to blindly ignore exceptions doing something like this:

def run_hourly
  update_counters rescue nil
  send_emails rescue nil
  sync_logs
end

Of course we have linters and tools to warn us that this is bad practice, but we are trying to ship the product and it’s late Friday afternoon just before the big trip. A more sane solution would be to run those methods, catch any exceptions that occur and then continue.

You could define every method with a rescue clause:

def update_counters
rescue StandardError => e
  ErrorService.process(e)
end

But if you want to avoid repeating code and just implement the rescue once, you can define a exception catching block method:

def handle_exception(&block)
  block.call
rescue StandardError => e
  ErrorService.process(e)
end

This way we can rewrite the example above to be more concise, catch any exceptions that occur and run all good methods.

def run_hourly
  handle_exception { update_counters }
  handle_exception { send_emails }
  handle_exception { sync_logs }
end

If you encounter this problem in other places in the code, it’s always easy to extract it into a module and then include that module anywhere else in the code where you need the functionality.

Categories:

Updated:

Comments