Hacker News new | ask | show | jobs
by zvrba 1747 days ago
> If base exception type had a single property, something like "CanRetry",

"Can retry" _WHAT_? This can work if you meticulously rewrap low-level exceptions into higher-level ones that reflect the high-level operation that failed.

Concrete example: FileNotFoundException. I'd say that in "normal" circumstances it's not retriable: you're looking for a file, it's not there, so an exception is thrown. In "unusual" circumstances you're polling (i.e., waiting for a file to appear somehow) or you're an OS shell and is searching the path for the location of the program.

1 comments

Where you handle exceptions has nothing to do with where they are thrown. You put your try/catch around whatever operation can be retried at the top level. I don't know why you'd need to rewrap exceptions -- I've never done that.

In that handler, you just need to know if the exception is fatal or a temporary issue for which retrying might succeed. If there was a CanRetry flag on the exception, that makes that determination easy without having to know every potential exception type.

Your FileNotFoundException is a good counter-example as maybe the called code is unable to know the intent. So, yes, in that case the handler has to make this determination based on the type (as we do now) or some code in the middle, that knows the intent, needs to catch and set that flag.

But most of the time one can determine at the point exception is thrown whether or not it's a potentially temporary situation (like a network error) or a unexpected fatal program logic error.

Perhaps "CanRetry" is too prescriptive of a name.

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