diff options
| author | Ahmed Abdelhalim <[email protected]> | 2025-09-17 12:19:43 +0200 |
|---|---|---|
| committer | Ahmed Abdelhalim <[email protected]> | 2025-09-17 12:19:43 +0200 |
| commit | 326ecfbb3147cc72cccab91f80cc4a1633384aca (patch) | |
| tree | bbc49681f3e164e8fed78c994aef2e4d4cf92c14 /content/blog/exception-handling/index.md | |
| parent | fde15d29f006e200ce8509cb6ddf4a66d2bad18f (diff) | |
Add markdown linting and fix some linting issues
Diffstat (limited to 'content/blog/exception-handling/index.md')
| -rw-r--r-- | content/blog/exception-handling/index.md | 132 |
1 files changed, 66 insertions, 66 deletions
diff --git a/content/blog/exception-handling/index.md b/content/blog/exception-handling/index.md index f6aeda4..ce4d58a 100644 --- a/content/blog/exception-handling/index.md +++ b/content/blog/exception-handling/index.md @@ -181,32 +181,32 @@ def do_some_analysis! end ``` -You might have noticed the error in the previous example; -`return` is misspelled. Although modern editors provide some protection against this specific type of syntax error, -this example illustrates how `rescue Exception` does harm to our code. +You might have noticed the error in the previous example; +`return` is misspelled. Although modern editors provide some protection against this specific type of syntax error, +this example illustrates how `rescue Exception` does harm to our code. At no point is the actual type of the exception (in this case a `NoMethodError`) addressed, or intended to be addressed. ## 3. Never `rescue` more exceptions than you need to -The point 2 is a specific case of this general rule: -We should always be careful not to over-generalize our exception handlers. -The reasons are the same; whenever we catch more exceptions than we should, -we end up hiding parts of the application logic from higher levels of the application, -not to mention suppressing the developer’s ability to handle the exception themselves. +The point 2 is a specific case of this general rule: +We should always be careful not to over-generalize our exception handlers. +The reasons are the same; whenever we catch more exceptions than we should, +we end up hiding parts of the application logic from higher levels of the application, +not to mention suppressing the developer’s ability to handle the exception themselves. **This severely affects the extensibility and maintainability of the code.** -If we do attempt to handle different exception subtypes in the same handler, -we introduce fat code blocks that have too many responsibilities. -For example, if we are building a library that consumes a remote API, handling a `MethodNotAllowedError` (HTTP 405), +If we do attempt to handle different exception subtypes in the same handler, +we introduce fat code blocks that have too many responsibilities. +For example, if we are building a library that consumes a remote API, handling a `MethodNotAllowedError` (HTTP 405), is usually different from handling an `UnauthorizedError` (HTTP 401), even though they are both `ResponseError`s. -As we will see, often there exists a different part of the application +As we will see, often there exists a different part of the application that would be better suited to handle specific exceptions in a more [DRY][7] way. -So, define the [single responsibility][8] of your class or method, -and **handle the bare minimum of exceptions that satisfy this responsibility requirement**. -For example, if a method is responsible for getting stock info from a remote a API, -then it should handle exceptions that arise from getting that info only, +So, define the [single responsibility][8] of your class or method, +and **handle the bare minimum of exceptions that satisfy this responsibility requirement**. +For example, if a method is responsible for getting stock info from a remote a API, +then it should handle exceptions that arise from getting that info only, and leave the handling of the other errors to a different method designed specifically for these responsibilities: ```rb @@ -222,19 +222,19 @@ def get_info end end ``` -Here we defined the contract for this method to only get us the info about the stock. -It **handles endpoint-specific errors**, such as an incomplete or malformed JSON response. -It doesn’t handle the case when authentication fails or expires, or if the stock doesn’t exist. -These are someone else’s responsibility, and are explicitly passed up the call stack +Here we defined the contract for this method to only get us the info about the stock. +It **handles endpoint-specific errors**, such as an incomplete or malformed JSON response. +It doesn’t handle the case when authentication fails or expires, or if the stock doesn’t exist. +These are someone else’s responsibility, and are explicitly passed up the call stack where there should be a better place to handle these errors in a DRY way. ## 4. Resist the urge to handle exceptions immediately -This is the complement to the point 3. -An exception can be handled at any point in the call stack, and any point in the class hierarchy, -so knowing exactly where to handle it can be mystifying. -To solve this issue, many developers opt-in to handle any exception as soon as it arises, -but investing time in thinking this through will usually result +This is the complement to the point 3. +An exception can be handled at any point in the call stack, and any point in the class hierarchy, +so knowing exactly where to handle it can be mystifying. +To solve this issue, many developers opt-in to handle any exception as soon as it arises, +but investing time in thinking this through will usually result in finding a more appropriate place to handle specific exceptions. @@ -253,12 +253,12 @@ def create end end ``` -(Note that although this is not technically an exception handler, functionally, it serves the same purpose, +(Note that although this is not technically an exception handler, functionally, it serves the same purpose, [since `@client.save` only returns `false` when it encounters an exception][12].) -In this case, however, repeating the same error handler in every controller action is the opposite of DRY, -and damages **maintainability** and **extensibility**. -Instead, we can make use of the special nature of exception propagation, +In this case, however, repeating the same error handler in every controller action is the opposite of DRY, +and damages **maintainability** and **extensibility**. +Instead, we can make use of the special nature of exception propagation, and handle them only once, in the [parent controller class][13], `ApplicationController`: ```rb @@ -281,86 +281,86 @@ def render_unprocessable_entity(e) end ``` -This way, we can ensure that all of the `ActiveRecord::RecordInvalid` errors +This way, we can ensure that all of the `ActiveRecord::RecordInvalid` errors are properly and DRY-ly handled in one place, on the base `ApplicationController` level. -This gives us the freedom to fiddle with them if we want to handle specific cases at the lower level, +This gives us the freedom to fiddle with them if we want to handle specific cases at the lower level, or simply let them propagate gracefully. ## 5. Not all exceptions need handling -When developing a gem or a library, many developers will try to encapsulate the functionality -and not allow any exception to propagate out of the library. +When developing a gem or a library, many developers will try to encapsulate the functionality +and not allow any exception to propagate out of the library. But sometimes, it’s not obvious how to handle an exception until the specific application is implemented. -Let’s take [`ActiveRecord`][14] as an example of the ideal solution. -The library provides developers with two approaches for completeness. +Let’s take [`ActiveRecord`][14] as an example of the ideal solution. +The library provides developers with two approaches for completeness. The [`save`][15] method handles exceptions without propagating them, simply returning `false`, -while [`save!`][16] raises an exception when it fails. -This gives developers the option of handling specific error cases differently, +while [`save!`][16] raises an exception when it fails. +This gives developers the option of handling specific error cases differently, or simply handling any failure in a general way. -But what if you don’t have the time or resources to provide such a complete implementation? +But what if you don’t have the time or resources to provide such a complete implementation? In that case, if there is any uncertainty, it is best to **expose the exception**. -Here’s why: We are working with changing requirements almost all the time, -and making the decision that an exception will always be handled in a specific -way might actually harm our implementation, damaging **extensibility** and **maintainability**, +Here’s why: We are working with changing requirements almost all the time, +and making the decision that an exception will always be handled in a specific +way might actually harm our implementation, damaging **extensibility** and **maintainability**, and potentially adding huge technical debt, especially when developing libraries. -Take the earlier example of a stock API consumer fetching stock prices (point 3.). -We chose to handle the incomplete and malformed response on the spot, -and we chose to retry the same request again until we got a valid response. -But later, the requirements might change, such that we must fall back to saved historical stock data, +Take the earlier example of a stock API consumer fetching stock prices (point 3.). +We chose to handle the incomplete and malformed response on the spot, +and we chose to retry the same request again until we got a valid response. +But later, the requirements might change, such that we must fall back to saved historical stock data, instead of retrying the request. -At this point, we will be forced to change the library itself, -updating how this exception is handled, because the dependent projects won’t handle this exception. -(How could they? It was never exposed to them before.) -We will also have to inform the owners of projects that rely on our library. -This might become a nightmare if there are many such projects, +At this point, we will be forced to change the library itself, +updating how this exception is handled, because the dependent projects won’t handle this exception. +(How could they? It was never exposed to them before.) +We will also have to inform the owners of projects that rely on our library. +This might become a nightmare if there are many such projects, since they are likely to have been built on the assumption that this error will be handled in a specific way. Now, we can see, **how this situation degrades the library’s usefulness, extensibility, and flexibility.** So here is the bottom line: **if it is unclear how an exception should be handled, let it propagate gracefully.** -There are many cases where a clear place exists to handle the exception internally, -but there are many other cases where exposing the exception is better. -So before you opt into handling the exception, just give it a second thought. +There are many cases where a clear place exists to handle the exception internally, +but there are many other cases where exposing the exception is better. +So before you opt into handling the exception, just give it a second thought. **A good rule of thumb is to only insist on handling exceptions when you are interacting directly with the end-user.** ## 6. Follow the convention -The implementation of Ruby, and, even more so, Rails, follows some naming conventions, +The implementation of Ruby, and, even more so, Rails, follows some naming conventions, such as distinguishing between `method_names` and `method_names!` with a “bang.” -In Ruby, the bang indicates that the method will alter the object that invoked it, -and in Rails, it means that the method will raise an exception if it fails to execute. +In Ruby, the bang indicates that the method will alter the object that invoked it, +and in Rails, it means that the method will raise an exception if it fails to execute. **Try to respect the same convention, especially if you are going to open-source your library.** -If we were to write a new `method!` with a bang in a Rails application, we must take these conventions into account. +If we were to write a new `method!` with a bang in a Rails application, we must take these conventions into account. There is nothing forcing us to raise an exception when this method fails. -Another Ruby convention, attributed to [Jim Weirich][17] ([Thanks for everything Jim. We will miss you][18]), +Another Ruby convention, attributed to [Jim Weirich][17] ([Thanks for everything Jim. We will miss you][18]), is to use `fail` to indicate method failure, and only to use `raise` if you are re-raising the exception. > An aside, because I use exceptions to indicate failures, -> I almost always use the `fail` keyword rather than the `raise` keyword in Ruby. -> Fail and raise are synonyms so there is no difference except that `fail` -> more clearly communicates that the method has failed. -> The only time I use `raise` is when I am catching an exception and re-raising it, -> because here I’m not failing, but explicitly and purposefully raising an exception. +> I almost always use the `fail` keyword rather than the `raise` keyword in Ruby. +> Fail and raise are synonyms so there is no difference except that `fail` +> more clearly communicates that the method has failed. +> The only time I use `raise` is when I am catching an exception and re-raising it, +> because here I’m not failing, but explicitly and purposefully raising an exception. > This is a stylistic issue I follow, but I doubt many other people do. [Source][19] -Many other language communities have adopted conventions like these around how exceptions are treated, +Many other language communities have adopted conventions like these around how exceptions are treated, and **ignoring these conventions will damage the readability and maintainability of our code.** ## 7. Logger.log(everything) -This practice doesn’t solely apply to exceptions, +This practice doesn’t solely apply to exceptions, but if there’s one thing that should always be logged, it’s an exception. -*Special thanks to [Avdi Grimm][20] and his awesome talk [Exceptional Ruby][21], +*Special thanks to [Avdi Grimm][20] and his awesome talk [Exceptional Ruby][21], which helped a lot in the making of this article.* *This article was originally published with the help of an editor on [Toptal in September 2017][22].* |
