Live data from Hacker News

Errors and Exceptions

giantfublog.wordpress.com

21–30 of 55 posts

Re: Errors and Exceptions

#21
post #20

Exceptions only work well in managed languages like C#, Java, Python. In C++ they don’t. In C++, error codes FTW. On Windows you usually return HRESULT, call FAILED/SUCEEDED macros to check, call OS-provided FormatMessage API to format an error (the messages are of course localized). If you’re using some 3rd party library that uses its own error codes, it’s usually trivial to pack them in HRESULT when failed, check H…

Don't really agree, this seems too general? It's a matter of preference, what level you're working on and of what is possible. For example you're writing C++ which is going to be wrapped in a C-style api then yes you are going to need those error codes. If you are writing an application using some C++ api with well-defined exceptions then using them might lead to much nicer code. Consider some function in main() whic…

1. Harder to maintain. Your colleague will change MethodB so it now throws an ExceptionB(), and your code will crash with unhandled exception = segfault. With error codes, your colleague may return any FAILED code she finds adequate. As long as you have a standard way of showing messages, the caller doesn’t need to change a single line.

2. You can greatly simplify your return codes example with a macro, e.g.

  #define CHECK( hr ) { HRESULT __hr = (hr); if( FAILED( __hr ) ) return __hr; }
Then you will be able to handle them in the upper level. Also, by changing this macro to slightly more complex variant you can very easily log failed operations (remember that __FILE__, __LINE__, #hr, etc.)

“if you build all code using the same compiler and build settings” — fine, what about using other people’s libraries you can’t or don’t want to compile? Like OS-provided API, CRT, middleware, etc? Also, what happens with your project code style consistency if you’ll decide to replace 3rd party component (that unlikely uses exceptions) with in-house, or vice versa?

P.S. I’ve written a lot of C++ as well, I’ve been developing commercial software since 2000. My experience tells me if you’re going to achieve good quality of your product, it’s seldom sufficient to just print messages to stdout or MessageBox an exception message. Depending on what exactly happened and what the code was doing when something failed, it may be necessary to do something more complex that needs rich execution context. Like write stuff to logs, send error details to the client over a network in the response, close some windows, invoke a caller-provided error handler, etc. You don’t have that context where you catch() them.

Re: Errors and Exceptions

#22
post #16

Earlier quoted context omitted.

Because in order to know which instructions in your procedure might excecute, you need to know about the exception-handling behavior for everyone you call, which requires looking at the source code of everyone you call. User barkkel above in this thread was saying that passing around full return-value information for something like a file-open operation is somehow a violation of abstraction (this idea does not make s…

Strong type system with a good support of checked exceptions would tell you what kind of exceptions can arise from every function call. If you don't check them in your function code, they would add to the list of exceptions that your function can throw. It would basically turn every function you write with return type T into Either with syntax sugar that would transfer exceptions between calls so you don't have to sp…

With this scheme you would end up with pretty bad problems regarding function pointers and lambdas. Because the type is deeply implicit, you would have two function pointers that look compatible but are totally incompatible. Then when you want to assign them to a variable -- how do you know a priori what type to declare the variable?

Re: Errors and Exceptions

#23
post #3

Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError. Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value…

Exceptions have another advantage not mentioned in the article: performance.

If you implement them as "zero cost exceptions", then there is no overhead for the successful path. Option 3 requires error checking even if you return successful, which can only be optimized away maybe if the compiler inlines and moves code around. Since Go and Rust favor static linking this might work. If you want to link libraries it does not.

Re: Errors and Exceptions

#25
post #20

Earlier quoted context omitted.

Don't really agree, this seems too general? It's a matter of preference, what level you're working on and of what is possible. For example you're writing C++ which is going to be wrapped in a C-style api then yes you are going to need those error codes. If you are writing an application using some C++ api with well-defined exceptions then using them might lead to much nicer code. Consider some function in main() whic…

1. Harder to maintain. Your colleague will change MethodB so it now throws an ExceptionB(), and your code will crash with unhandled exception = segfault. With error codes, your colleague may return any FAILED code she finds adequate. As long as you have a standard way of showing messages, the caller doesn’t need to change a single line. 2. You can greatly simplify your return codes example with a macro, e.g. #define…

1. Fair point, though in actual code at a top level (which the example shown was intended to be - as I'm not advocating one must use exceptions everywhere) there would be a catch( const std::exception& ) so it is not an issue.

2. Indeed you can

what about using other people’s libraries you can’t or don’t want to compile? I do want to compile them, or if I can't then either it is not a problem (at least I never had problems with OS provided APIs and CRTs which would give such problems because they are designed to circumvent it) or I might not want to use the libraries (never occurred to me, maybe I'm lacking experience or the particular applications I wrote just never gave me the chance of having to be in such situation).

Also, what happens with your project code style not really an argument, same can be said for switching to a library which does use exceptions

regarding PS: most of my/our applications have so much logging the lack of execution context is usually not a problem. In addition to that in those rare cases where things go completely berserk on Windows we'd use full minidumps. Not sure if that is what you mean with execution context though as I don't see how logging/sending error details/other things you mention would be handled differently when using exceptions vs error codes.

Re: Errors and Exceptions

#26
post #6
post #3

Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError. Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value…

Go's preference for 3 over 4 is a big chunk of the reason I've never used it in anger. Rust's situation is slightly different; code typically uses a monad (Result ) for error propagation, which is mostly isomorphic to exception throwing, but more verbose. And Rust has macros to cope with some of the verbosity.

It may be isomorphic to exception handling, but it promotes a meaningfully-different human style. The exception-based isomorphism to the usual returned-error handling style would look something like:

    try:
        a_single_statement_here()
    except:
        if exception.type == "A":
            #yada yada
        elsif exception.type == "B":
            #yada yada
        else:
            raise
Exceptions by contrast encourage putting a lot of statements together, and much less frequently checking for what the exception may be, and capturing fewer of them. The code snippet above is bad exception style because you shouldn't be putting a try around every statement like that.

Exceptions expressed in the error handling modality probably look something more like:

    val, err = func()
    goto EXCEPTION if err
    nextval, err = func2(val)
    goto EXCEPTION if err
    # etc etc, for the whole "try" block

    return goodval

    EXCEPTION: {
        if err.type == "A" { ... }
        if err.type == "B" { ... }
        return err
    }
which is, intriguingly, also very bad style for an error-returning language; the EXCEPTION block must be written excessively generically, when in fact the error handling may in fact care about the difference between whether the error came from func or func2, and may even want to take different actions (especially w.r.t. retrying). You can of course write those different actions into an exception block, but the style discourages that... your code starts to look messier (as nice as exceptions can be, they're syntactically pretty heavy, and also difficult to factor out the way that error-return handling can be factored out with monads and such (albeit not in Go so much)), it takes more work, so you're less inclined to do it.

It remains unclear to me what "the best" is. Error-returning code is, both in theory and in my practice, more correct, in terms of the programming style it affords; it turns out that in ways you won't see if you've only ever done exceptions-based code that your exception handlers are often incorrect, overly generic, and missing opportunities to intelligently handle bugs. The flip side is that all that extra handling is of course expensive, and it's not 100% clear that it's worth the expense, and exception code, while IMHO clearly generally less correct in both theory and practice, is equally clearly often "good enough".

I find myself wondering if some of the divisions of opinion come from different environments. In a conventional GUI program, I favor the exceptions; frankly so much stuff can go wrong that an enumeration of all possible failures is very difficult to produce, and your answer to almost all of them is likely to be identical ("scream an error to the user and hope they can fix it"). In a network server, I heavily favor the error returning paradigm; in my mature Go codebases that are network servers, I took a survey once and about 1/3rd of the lines that received an error immediately did something with them, other than simply passing them up the stack. Converting that to exceptions would actually lengthen the program and be a royal pain to deal with going forward.

Re: Errors and Exceptions

#27
post #20

Exceptions only work well in managed languages like C#, Java, Python. In C++ they don’t. In C++, error codes FTW. On Windows you usually return HRESULT, call FAILED/SUCEEDED macros to check, call OS-provided FormatMessage API to format an error (the messages are of course localized). If you’re using some 3rd party library that uses its own error codes, it’s usually trivial to pack them in HRESULT when failed, check H…

Don't really agree, this seems too general? It's a matter of preference, what level you're working on and of what is possible. For example you're writing C++ which is going to be wrapped in a C-style api then yes you are going to need those error codes. If you are writing an application using some C++ api with well-defined exceptions then using them might lead to much nicer code. Consider some function in main() whic…

Go read one of Herb Sutter's "Exceptional C++" books. Okay, just read the first one. If you don't close the book and reflect, "I am never writing this shit," Herb didn't do his job.

Writing exception-safe code in C++ is very hard.

Depending on another module's authors to get their C++ exception handling right is the road to madness.

In the example above, the FAILED clauses clearly handle all the failures. In the exception handling example it is not clear if all the failures are caught, or if they are more that are meant to be caught at higher levels, or perhaps exceptions were forgotten. It becomes very difficult to reason about control flow because exceptions distribute it across the program and over time. Someone might add another exception: Oops, you didn't handle FileNotFoundException and your program bombs. You add a new exception that seems reasonable, but you wind up changing a hundred places in your source code (... and your callers! hope you have good customer support).

Exceptions are exceptional, they should not be used to return indications of failure. Exceptions should be used when it's lights-out important that something Really Bad be handled or the program will be killed. Most C++ systems I've worked with catch an exception near the top, do some diagnostics, and either tell the use "wups" or attempt to restart.

A "FileNotFound" exception from an open() function is just colossally stupid. I don't have a nicer way of saying that. If I saw code with this pattern in a project that I was on, I would remove it and make sure it didn't happen again.

Re: Errors and Exceptions

#28
post #16

Earlier quoted context omitted.

Because in order to know which instructions in your procedure might excecute, you need to know about the exception-handling behavior for everyone you call, which requires looking at the source code of everyone you call. User barkkel above in this thread was saying that passing around full return-value information for something like a file-open operation is somehow a violation of abstraction (this idea does not make s…

Strong type system with a good support of checked exceptions would tell you what kind of exceptions can arise from every function call. If you don't check them in your function code, they would add to the list of exceptions that your function can throw. It would basically turn every function you write with return type T into Either with syntax sugar that would transfer exceptions between calls so you don't have to sp…

In layered code, if not recoverable by the current layer, you should always encapsulate checked exceptions thrown by a lower layer - translated into an exception of the current layer (maybe generalized). This simple rule is often declared as overhead. I don't get that. I like code to be precise. The only situations where this gets complicated are those, where the rule is violated. And I think, the rejection of checked exceptions is often based on experiences with bad API design.

Exception handling must be trained and educated. There is no silver bullet.

Re: Errors and Exceptions

#29

He talks about "soft" errors as if the only way to handle them is to propagate them up to the UI layer, displaying a message. In many situations the error handling affects the code paths at a more profound level. Something might be considered an error in the callee, but at the same time it might be an acceptable result in the caller, for which a well-defined path exists. Throwing an exception in such situations is ju…

The perspective is about layered code and reuse. So what is wrong with delegation? My file access library should not know that it is linked in an application with an UI. Throwing an exception just delegates the issue to the code which depends on me. Whether the IO exception is really a problem can only be decided by the user of my library.

Re: Errors and Exceptions

#30
I find anything but a Result ADT to be a poor solution. Errors shouldn't have language-level support (exceptions), and error codes are just a poor implementation of a result ADT - they cannot be enforced by the type system.

Result is a monad, so it doesn't require an alternative code path. It's the cleanest and safest.

"Exceptions" should always be for fatal, irrecoverable errors (array index out of bounds). I'm okay with having them, but I don't think languages should support "catch".

Post reply on HN