There's been a lot of conversation on Twitter and people's blogs about the error handling model, especially with regards to throws
vs. Result<T, E>
. I think that's great because people are really getting engaged in the topic and hopefully the situation will get better.
The latest thing I'm running up against in the cumbersomeness of the do-catch
pattern for handling errors. Not only that, I think it promotes the bad exception handling that Java had with people simply writing a single catch (Exception e)
handler. Sometimes that's OK, I guess.
The thing I like most about throws
is that it cleans up the API signature for functions:
func foo() throws -> Int { ... }
vs. this:
func foo() -> Result<Int, ErrorType> { ... }
It's where we go to handle it that is really starting to get to me:
do {
try foo()
}
catch {
}
My first complaint, I don't think it's a good idea to write giant do-catch
blocks with multiple throwing functions within it:
do {
try foo()
// ...
try foo()
}
catch {
}
I just think that's bad style. It is pushing the error handling further and further from the code that is actually going to error-out. Also, I think it defeats the intended purpose of being intentional about handling errors.
This is what will happen once you get a handful of functions that throw:
func someFunc() {
do {
/*
You know this to be true... we've seen it.
*/
}
catch {
// Um.. I guess I should do something with the "error"?
}
}
Now, I do like the try
and the throws
annotations. I think they add clarity to the code, especially as code grows and needs to be maintained over time. But, I think it might have been cleaner to do something more like guard
.
try foo() catch {
/* handle the error */
}
The thing I really like about this is that it's only the error-handling code that is being nested a level. This keeps the logic flow of the happy path of the code at the same level and calls out, making it more explicit where the bad path is.
Then, if we combine this with guard
, we can get this:
guard let result = try foo() catch {
/* handle the error */
/* also, forced scope exit */
}
/* result is safe to use here */
Of course, if we want to bubble the errors up, the catch
clause could be omitted if the enclosing function/closure also throws.
func someFunc() throws {
try foo() // this is ok because someFunc() can throw
}
Anyhow, just some thoughts as I'm starting to write a lot more nesting that I care too.
I've logged rdar://21406512 to track it.