> Where you handle exceptions has nothing to do with where they are thrown.
Funny you say this, when it's demonstrably NOT the case: a throwing method NOT wrapped in a try/catch will NOT have its exceptions handled at the call site. And vice-versa. If you want to retry a particular failing operation, you write try/catch around IT, not several levels up the stack.
You _could_ write it several levels up the stack IF you have precisely typed exceptions, therefore wrapping.
> I don't know why you'd need to rewrap exceptions -- I've never done that. [...] maybe the called code is unable to know the intent
To convey meaningful semantic information about the (business) operation that failed. Updating a record in the database can fail due to business rules (DB constraints), network connection that disappeared, concurrency conflict, transaction deadlock, etc. The user or higher-level code is not interested in the root cause, but in the actual consequence ("Could not update record". And yes, "IsRecoverable" flag, the value of which depends on the inner exception and its properties.).
And yes, the called code rarely knows the intent. There are a bunch of libraries out there being used in diverse contexts. So you catch and wrap the exception. Wrapping wouldn't be needed if library authors were careful about designing their exceptions, but I've rarely seen this to be the case. Even C# guidelines recommend you to use the generic, system-provided exceptions if an "appropriate one" exists. (IMO, a most terrible advice. And I discovered it was terrible by first following it then going back and designing "proper" exceptions for the system.)
> Your FileNotFoundException [...] needs to catch and set that flag.
But the exception type does not have that flag. So you have to wrap it in another exception. (Though all exceptions in C# have a Data field that is object -> object dictionary accessible to anyone. So you could use that.)
> You put your try/catch around whatever operation can be retried at the top level.
What is "top-level" for you? The shell's REPL loop? Exception blocks are non-restartable, so how would REPL continue the path-searching loop that threw FileNotFoundException?
> temporary situation (like a network error)
Ah yes, I love these. Someone pulled the power cable on some router the computer is indirectly connected to. To the program it looks the same as ordinary timeout error. How temporary is it?
> If you want to retry a particular failing operation, you write try/catch around IT, not several levels up the stack.
Generally speaking when I retry and operation it's pretty far up the stack that I restart it. I'm not retrying sending a single byte, I'm retrying the entire file transfer operation (as an example). If it's batch job processing data in a loop, then the processing of each item is typically where I would catch and retry or ignore. If the exception actually said "I think you should retry" then it could retry otherwise it would abort.
> To convey meaningful semantic information about the (business) operation that failed.
Wrapped exceptions tend to provide less information than the root exception. I agree that library authors aren't as careful as they could be about exceptions and you might need to wrap an exception just to make it sane. Generally most libraries throw LibraryException and the real exception, with meaningful information you can action, is in the inner exception. I blame Java's checked exceptions for making that a thing.
> But the exception type does not have that flag.
Right. That's why I proposed it. "If base exception type had a single property... something like "CanRetry"... all exception handling would be simple."
> To the program it looks the same as ordinary timeout error. How temporary is it?
Never forget to put a limit of retries. You could get really clever and put an exponential delay on it.
Funny you say this, when it's demonstrably NOT the case: a throwing method NOT wrapped in a try/catch will NOT have its exceptions handled at the call site. And vice-versa. If you want to retry a particular failing operation, you write try/catch around IT, not several levels up the stack.
You _could_ write it several levels up the stack IF you have precisely typed exceptions, therefore wrapping.
> I don't know why you'd need to rewrap exceptions -- I've never done that. [...] maybe the called code is unable to know the intent
To convey meaningful semantic information about the (business) operation that failed. Updating a record in the database can fail due to business rules (DB constraints), network connection that disappeared, concurrency conflict, transaction deadlock, etc. The user or higher-level code is not interested in the root cause, but in the actual consequence ("Could not update record". And yes, "IsRecoverable" flag, the value of which depends on the inner exception and its properties.).
And yes, the called code rarely knows the intent. There are a bunch of libraries out there being used in diverse contexts. So you catch and wrap the exception. Wrapping wouldn't be needed if library authors were careful about designing their exceptions, but I've rarely seen this to be the case. Even C# guidelines recommend you to use the generic, system-provided exceptions if an "appropriate one" exists. (IMO, a most terrible advice. And I discovered it was terrible by first following it then going back and designing "proper" exceptions for the system.)
> Your FileNotFoundException [...] needs to catch and set that flag.
But the exception type does not have that flag. So you have to wrap it in another exception. (Though all exceptions in C# have a Data field that is object -> object dictionary accessible to anyone. So you could use that.)
> You put your try/catch around whatever operation can be retried at the top level.
What is "top-level" for you? The shell's REPL loop? Exception blocks are non-restartable, so how would REPL continue the path-searching loop that threw FileNotFoundException?
> temporary situation (like a network error)
Ah yes, I love these. Someone pulled the power cable on some router the computer is indirectly connected to. To the program it looks the same as ordinary timeout error. How temporary is it?