Live data from Hacker News

Errors and Exceptions

giantfublog.wordpress.com

11–20 of 55 posts

Re: Errors and Exceptions

#11
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…

Most statically typed languages prefer 3 over 4. For the simple reason that it's easier for a compiler to verify that you've handled all the success and error cases. For Haskell though there exists a library which allows the compiler to track exceptions in much the same way as normal values, and thus warn you when you fail to handle an exception in a particular code path.

The Haskell approach would be to use `Maybe` or `Either SomeError`, and have Functor/Applicative/Monad/etc. handle the error propagation. More experimental approaches are using Algebraic Effects, which are like resumable exceptions, tracked in the type system, with ambient handlers.

It's an elegant way to solve this problem, mentioned in the article:

> However returning error codes makes error propagation difficult. When there’s need to propagate the error up the stack several layers, every layer needs to make sure that they do it correctly... ideally most of the code along the way should be error agnostic and not really need to know about the details of the possible errors but still be able to propagate them up to the caller without a hitch.

Re: Errors and Exceptions

#12
And option number five, conditions + restarts, http://www.lispworks.com/documentation/lw61/CLHS/Body/09_a.h..., which is a bit like more formalised callbacks / exceptions depending on usage. Restarts are a great thing for both interactive (using a "continue" restart if e.g. something can be safely skipped) and programmatic usage (to customise an algorithm where you control from the outside whether a restart should be invoked in case of a "soft" exception/condition).

Re: Errors and Exceptions

#13
post #5
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…

I also prefer option 3 over option 4. It bothers me that the article recommends exceptions without seeming to understand their drawbacks (the major one: as soon as you use exceptions you suddenly need to apply nonlocal reasoning everywhere in order to understand what your program will do at any point. They turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want…

For my own understanding, why do you say "(Exceptions) turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want to understand it well."?

My understanding exceptions only bubble up if they weren't handled at the point of failure. This is exactly the same if you don't check the return type of a function that could return error. Both of this situations point to poor programming technique rather than underlying implementation option.

One thing that makes me currently prefer exceptions to error returns is that within a try/with block you can write the code as you'd like to happen and hence easier to understand and maintain. All exception handling can happen within the respective catch blocks.

Re: Errors and Exceptions

#14
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…

> 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. The "warn_unused_result" GCC function attribute makes the compiler emit a warning when you do `func(something);`: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attribute...

The problem is it needs to be applied to every function that returns a value. And I'm pretty sure you can't apply it to C library functions. Gcc seriously needs a global flag for this.

Re: Errors and Exceptions

#15
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 HRESULT facility when formatting.

In C++, exceptions have fatal disadvantage: they don’t work across modules. Two reasons: (1) C++ exceptions aren’t binary compatible across compilers (2) memory management isn’t compatible across compilers either, if some module has called new/malloc, the same module must call delete/free

Re: Errors and Exceptions

#16
post #5

Earlier quoted context omitted.

I also prefer option 3 over option 4. It bothers me that the article recommends exceptions without seeming to understand their drawbacks (the major one: as soon as you use exceptions you suddenly need to apply nonlocal reasoning everywhere in order to understand what your program will do at any point. They turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want…

For my own understanding, why do you say "(Exceptions) turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want to understand it well."? My understanding exceptions only bubble up if they weren't handled at the point of failure. This is exactly the same if you don't check the return type of a function that could return error. Both of this situations point to poor…

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 sense to me) ... But I can't think of a bigger violation of abstraction than requiring you to know everything about everyone you ever call. Of course in reality people don't do this, which is then why programs that use exceptions have so many problems.

And if you say "why would you let exceptions bubble up that much", well, that is the whole point of exceptions, that they bubble up. If you say "to get rid of nonlocality just catch outside every call", well, now that's equivalent to checking return values always, but more error-prone.

Re: Errors and Exceptions

#18
post #16

Earlier quoted context omitted.

For my own understanding, why do you say "(Exceptions) turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want to understand it well."? My understanding exceptions only bubble up if they weren't handled at the point of failure. This is exactly the same if you don't check the return type of a function that could return error. Both of this situations point to poor…

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 spend your time with constant transfering of error properties of your return objects.

This would allow you to check stuff on places you desire (sometimes right after function, sometimes in the UI thread) and have tooling support for managing what is left unchecked.

Let's build something like that for C# using Roslyn! Issues of Java-style checked exceptions can be overcome using proper typing with generics.

Re: Errors and Exceptions

#19
Regarding what he calls "soft errors" (a better name would be environmental errors, I think), it's unfortunate that option 2 (error handlers) is so often neglected by programming language designers (for example Code Complete doesn't mention option 2 at all).

In my view, just like option 4 (exceptions) is more general than option 3 (return values), also option 2 is more general than option 4. The biggest advantage of 2 compared to 4 is that recovery is much easier. However, more generality also unfortunately means more complexity (both for programmer and runtime). That's why many people here prefer 3 to 4 or 2, and neither one of them is really better than the other.

Common Lisp is a good example of language that has good support for all three. It can return multiple values which facilitates option 3, it has usual exceptions as option 4, and most importantly, it also has signals and restarts that serve as option 2.

I would classify what he calls hard errors into two categories. One is inconsistent input and the other internal (logic) error. This depends on level of view, if you are looking only at one module or a bigger whole (inconsistent input into one module from the other can be considered internal error in the whole).

Now, I don't think inconsistent input should be dealt with asserts. If it's worth checking the inconsistent input at the module boundary, do it and return an error (in any of the three ways mentioned above). The caller should decide whether or not this is a logic error (it may also be bad input from the user), and how to handle the failure.

While I agree with the statement that on logic errors one should generally fail hard, sometimes it's not desirable, for instance in server you may want to just restart the wrong thread instead of shutting down the whole server.

Finally, I think asserts are very much underrated in the current development practices, compared to tests. I wish the effort spent on testing frameworks would be spent on frameworks that would let you add lots of asserts into the code and turn them on/off as needed (for example based on required tradeoff between reliability and performance).

Re: Errors and Exceptions

#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() which takes care of a ton of initialization; I just happen to prefer

  try
  {
    MethodA();
    MethodB();
    MethodC();
  }
  catch( const FooException& e )
  {
    //show e
    return 1;
  }
  catch( const BarException& e )
  {
    //show e
    return 1;
  }
over

  auto resulta = MethodA(); 
  if( FAILED( resulta ) )
  {
    //show result
    return 1;
  }
  auto resultb = MethodB(); 
  if( FAILED( resultb ) )
  {
    //show result
    return 1;
  }
  auto resultc = MethodC(); 
  if( FAILED( resultc ) )
  {
    //show result
    return 1;
  }

they don’t work across modules

A problem which fades away if you build all code using the same compiler and build settings, which is a good idea anyway (there's not much which works across modules when mixing e.g. debug and release builds). I've written a lot of C++, and misbehaving across modules is like the one thing I didn't have problems with :]

Post reply on HN