Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby on Fails - effective error handling with Rails conventions

Ruby on Fails - effective error handling with Rails conventions

You ask 10 different developers how they handle errors in their applications, you get 10 very different answers or more, that’s wild. From never raising errors to using custom errors, rescue_from, result objects, monads, we see all sorts of opinions out there. Is it possible that all of them are right? Maybe none of them? Do they take advantage of Rails conventions? In this talk, I will show you error-handling approaches based on patterns we see on typical everyday Rails applications, what their tradeoffs are, and which of them are safe defaults to use as a primary choice when defining the architecture of your application.

More Decks by Talysson de Oliveira Cassiano

Other Decks in Programming

Transcript

  1. Exceptions and Errors - Exceptions are a mechanism to express

    unhappy paths - When using this mechanism for scenarios we're able to recover from, we call this an error - In this talk we'll focus on recoverable errors: - Validation errors - Authentication errors - Payment errors - …
  2. Exceptions in Ruby - Exceptions are used to communicate raise

    with rescue - Exception is the base of all exception classes - Errors are usually descendants of the the StandardError class
  3. Rescuing from Exception "just to be safe" - Exception is

    too generic, even if used explicitly "just to be safe" - Error handling should not be used for states that are inherently invalid and/or you can't do anything about them - Rescuing from Exception without a very good reason actually makes your code less safe
  4. Ruby error classes hierarchy What would happen if we rescue

    here? This is the error: file_with_syntax_error.rb:3: syntax error, unexpected end-of-input, expecting `end' or dummy end
  5. Using errors for flow control in the same context -

    Errors and the raise method should not be used in places where a conditional branch would suffice - This is like using errors as: - Inadequate algebraic effects alternatives - A goto statement - It makes the code harder to read and less efficient
  6. Goals - A consistent and pragmatic error-handling strategy - Make

    error scenarios explicit - Provide enough information for the call-site to make decisions - Follow conventions and the Rails-way
  7. Be intentional - Don't try to just "guess" the error

    scenarios, discover them - Before you write the code, design considering the unhappy paths - Errors exist to communicate with the call-site - Handle only what you can recover from gracefully
  8. Item not available Fraud detected Invalid credit card Invalid Order

    data Service is down Out of memory Service is down Database is down … Insufficient funds
  9. Item not available Fraud detected Invalid credit card Invalid Order

    data Service is down Out of memory Service is down Database is down … Insufficient funds
  10. Make the error scenarios explicit - Create custom errors classes

    that express what happened - Especially important when: - A method can fail for more than one reason, or - You need to add more information than just the message to an error - Custom errors classes should be descendants of StandardError - Add methods to enrich the error information
  11. Abstract errors the same way you do for logic -

    Avoid leaky abstractions errors - The origin of an error should be clear on the call-site - Wrap gem-specific errors - Use the Error#cause method
  12. Use polymorphism to keep your options open - Sometimes, we

    need to handle a family of errors in the same way - Listing every error manually when you want them all is error-prone - You don't need anything fancy for that, keep it simple! - Use the Layer Supertype pattern when needed - Here's a secret: we do it all the time in Rails already - Examples of the Layer Supertype pattern in Rails: - ApplicationController - ApplicationRecord - ApplicationJob
  13. Use a centralized strategy - Not centralizing it causes higher

    chances of inconsistencies - Especially important when the app has multiple entry points: - API requests, requests coming from forms, jobs, … - Just using rescue_from doesn't actually make it centralized - Take care not to over-centralize it
  14. Reporting SomeError - - {:controller=>#<CheckoutsController:0x00000000003b60>} Reporting Sidekiq::JobRetry::Skip - SomeError -

    {:job=>#<RecurrentOrderJob:0x00007ffb900b1f60 @arguments=[], @job_id="[...]", [omitted]} It works automatically for unhandled scenarios
  15. Conventions - Rails strength stems from its conventions - But

    it doesn't provide strong conventions for errors - We can create our own following the Rails-way! - Good Rails-way conventions should provide: - Reliable and safe defaults - A practical way to break them when necessary
  16. What about… - Application flow strategies, like: - Result objects

    - Railway oriented programming - Monads - Well, they're great, but… - It's important to keep them contained, otherwise…
  17. Application flow strategies - Abstractions are good to design the

    multiple flows of your app - It's important to stop them from "coloring" all your code - Otherwise, everyone will be required to speak monad/result object - Contain these strategies to the use cases abstractions: - Services, operations, interactors, … - Use ordinary error objects everywhere else Controllers Jobs Channels Services Operations Interactors Infrastructure Business rules (Domain)
  18. Takeaways - Don't use errors for flow control - Be

    intentional and explicit with your error scenarios - Abstract errors the same way you do for logic - Reduce inconsistencies centralizing error-handling - Create and stick to the project conventions - Keep approaches that influence your whole code contained
  19. Links - Ruby Exceptions - Ruby's Exceptional Creatures - Ruby

    Style Guide - Error handling - Error Reporting in Rails Applications @talyssonoc beacons.ai/talyssonoc @codeminer42 codeminer42.com