Slide 1

Slide 1 text

Ruby on Fails Effective error-handling with Rails conventions @talyssonoc | @codeminer42

Slide 2

Slide 2 text

Talysson Oliveira Software architect & Technical development manager at Codeminer42 @talyssonoc beacons.ai/talyssonoc

Slide 3

Slide 3 text

codeminer42.com @codeminer42

Slide 4

Slide 4 text

Exceptions and Errors Common error-handling anti-patterns Effective error handling

Slide 5

Slide 5 text

Exceptions and Errors Common error-handling anti-patterns Effective error handling

Slide 6

Slide 6 text

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 - …

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Ruby error classes hierarchy rescue default raise default

Slide 9

Slide 9 text

Exceptions and Errors Common error-handling anti-patterns Effective error handling

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Using errors for flow control in the same context

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

✓ ✓

Slide 15

Slide 15 text

Exceptions and Errors Common error-handling anti-patterns Effective error handling

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Be intentional

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

How do we express these errors scenarios in our code?

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Item not available

Slide 24

Slide 24 text

Item not available

Slide 25

Slide 25 text

Abstract errors the same way you do for logic

Slide 26

Slide 26 text

ActiveRecord::RecordInvalid

Slide 27

Slide 27 text

Leaky error handling

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Abstracting errors

Slide 30

Slide 30 text

Abstracting errors

Slide 31

Slide 31 text

Abstracting errors

Slide 32

Slide 32 text

No error info lost Lost original error for debugging? #

Slide 33

Slide 33 text

Use polymorphism to keep your options open

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Slide 41

Slide 41 text

Use a centralized strategy

Slide 42

Slide 42 text

✓ ✓ ✓ 🤨

Slide 43

Slide 43 text

✓ ✓ ✓ 🤨

Slide 44

Slide 44 text

✓ ✓ ✓ 🤨

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Defining an error reporter

Slide 47

Slide 47 text

Calls ErrorReporter#report Using the reporter

Slide 48

Slide 48 text

Calls ErrorReporter#report Using the reporter

Slide 49

Slide 49 text

Using the reporter Reporting CheckoutService::FraudDetected - - {:controller=>#}

Slide 50

Slide 50 text

Reporting SomeError - - {:controller=>#} Reporting Sidekiq::JobRetry::Skip - SomeError - {:job=>#

Slide 51

Slide 51 text

Use good conventions

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Define a base error class

Slide 54

Slide 54 text

Next step?

Slide 55

Slide 55 text

ApplicationError is too generic Next step?

Slide 56

Slide 56 text

Define and use a safe centralized default

Slide 57

Slide 57 text

Define and use a safe centralized default

Slide 58

Slide 58 text

Define and use a safe centralized default This is a safe centralized strategy

Slide 59

Slide 59 text

Breaking a convention Convention broken successfully

Slide 60

Slide 60 text

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…

Slide 61

Slide 61 text

Original image by @ShyRyanW

Slide 62

Slide 62 text

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)

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Thank you, @talyssonoc beacons.ai/talyssonoc @codeminer42 codeminer42.com

Slide 65

Slide 65 text

Links - Ruby Exceptions - Ruby's Exceptional Creatures - Ruby Style Guide - Error handling - Error Reporting in Rails Applications @talyssonoc beacons.ai/talyssonoc @codeminer42 codeminer42.com