End of scope rescue
This week I thought we'd take a look at all the nice things Ruby does for exception management.
One of the most annoying ceremonies in languages with exception-based error handling is try / catch blocks (written as begin / rescue in Ruby). In general, code is easy to read when it flows naturally from top to bottom, but the more it gets nested and indented, the less clear-cut it becomes.
In Ruby, there is a beautiful solution to this problem - the end of scope rescue pattern:
def parse_json(json_string)
JSON.parse(json_string)
rescue JSON::ParserError
{}
end
# => parse_json('{"valid": true}') # => {"valid"=>true}
# => parse_json('invalid json') # => {}
function parseJson(jsonString) {
try {
return JSON.parse(jsonString);
} catch (e) {
return {};
}
}
// => parseJson('{"valid": true}') // => {valid: true}
// => parseJson('invalid json') // => {}
It is possible to add rescue statements at the end of methods, but also any block:
def json_parse_all_valid_strings(json_strings)
json_strings.filter_map do |json_string|
JSON.parse(json_string)
rescue JSON::ParserError
nil
end
end
# => json_parse_all_valid_strings(['{"a":1}', 'invalid', '{"b":2}'])
# => [{"a"=>1}, {"b"=>2}]
function jsonParseAllValidStrings(jsonStrings) {
return jsonStrings.map(jsonString => {
try {
return JSON.parse(jsonString);
} catch (e) {
return null;
}
}).filter(item => item !== null);
}
// => jsonParseAllValidStrings(['{"a":1}', 'invalid', '{"b":2}'])
// => [{a:1}, {b:2}]
The end of scope rescue pattern is so common in Ruby that it can feel like a code smell whenever explicit begin / rescue is used.
Finally, there is the controversial one-liner inline rescue:
def json_parse_all_valid_strings(json_strings)
json_strings.filter_map do |json_string|
JSON.parse(json_string) rescue nil
end
end
# => json_parse_all_valid_strings(['{"a":1}', 'invalid', '{"b":2}'])
# => [{"a"=>1}, {"b"=>2}]
function jsonParseAllValidStrings(jsonStrings) {
return jsonStrings.map(jsonString => {
try {
return JSON.parse(jsonString);
} catch (e) {
return null;
}
}).filter(item => item !== null);
}
// => jsonParseAllValidStrings(['{"a":1}', 'invalid', '{"b":2}'])
// => [{a:1}, {b:2}]
This one is controversial because it doesn't allow us to specify the exception type we want to rescue from, so it will rescue from all exceptions. This may end up hiding bugs in the code, so use with caution!
History
End of scope and inline rescue were added in Ruby 1.6, released in 2000.
I'm not aware of any other programming language being this flexible with exception handling syntax. Except I suppose for Crystal, which is intentionally trying to replicate Ruby syntax as much as possible.
Whether exceptions for error management are a good idea in the first place is perhaps debatable, but out of all languages that go down the exceptions route, Ruby certainly provides the nicest syntax for it