Live data from Hacker News

Isaacs: try/catch is an anti-pattern

groups.google.com

51–60 of 140 posts

Re: Isaacs: try/catch is an anti-pattern

#51
I think a lot of programmers use try/catch when what they really want is Prolog-style failure:

  my_parser(String, Output) :-

    parseJSON(String, JSON),

    extract_the_values_I_want(JSON, Output).

 if my_parser(String, Output) then

  ...do stuff with Output...

 else

  ...show user "couldn't parse" message...
I've been using Mercury (a strongly typed Prolog) for years now and have never once found the need for exceptions (which Mercury supports) to indicate anything other than catastrophic failure.

Re: Isaacs: try/catch is an anti-pattern

#52
post #9

This seems to be exactly the attitude of Go: "We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional. Go takes a different approach. For plain error handling, Go's multi-value returns make it easy to report an error without overloa…

This is a bad idea that keeps coming back again and again. I see nothing wrong with exceptions, I do have a problem with (1) checked exceptions, and (2) catching exceptions prematurely and (3) people not learning how to use "finally" so they do (2) and rethrow. Languages like Go and Scala roll out various mechanisms that bring us back to the bad old days of C, when we had to check the return/value and or the error co…

I think the point is that your specific cases:

> Many programmers in many situations would be perfectly happy to catch "failure to open a file" and "failure to open a database connection" and "failure to connect to a network host" with one simple handler that logs the failure and either aborts, retries or ignores.

aren't exception appropriate a according to a certain portion of developers. The reason they aren't exception appropriate is that you should expect these things to happen from time to time, ie they are not exceptional.

using exceptions to handle them is part of the problem Go attempts to solve by having multiple return values.

Re: Isaacs: try/catch is an anti-pattern

#53
It looks great on simple examples, but now consider your stacktrace is 5+ levels deep with some generators doing their generalised work on some collection. If the last function fails, then you better be sure that every single level:

- returns the error message and context instead of just error code

- wraps the internal problem properly inside its own error description and context

- cleans up all internal state

Guess what... that's exactly what exceptions do. If don't do the first point, you'll end up with multiple causes producing the same error number. If you don't do the second, you'll get "invalid data" when parsing json (where? what data? which line? which element? why is it invalid?). If you don't do the third, you're going to crash anyway.

So if you don't use exceptions to make your code nicer, you're going to end up implementing the same flow over and over again in places that could just allow the exception to pass through. You might even write some helper/macros functions to... add a file and line number when wrapping previous exception. It feels like reinventing the wheel.

Edit: Forgot to mentioned the collection processing in the end. What is tricky about it is that for a common "map()" to stop on error, you'll need to have a common way of handling errors. Exceptions do this just fine, custom methods - not necessarily.

Re: Isaacs: try/catch is an anti-pattern

#54
Exceptions can make your code much shorter and readable by the virtue of centralized error handling. However, try-catching can be pretty ugly in cases when you need to handle exceptions right away. D language has scope guards to help this. I think we need some kind of better syntax to handle cases like this, but getting rid of exceptions entirely is a bad idea.

Re: Isaacs: try/catch is an anti-pattern

#55
An interesting read. It's always nice to see people stating their unpopular opinions with proper reasoning.

The author's point of view seems to be pretty Javascript/Node.js -centric. I can relate. try/catch and node.js -style asynchronous coding do not work together. However, I'd say that it's the async programming model that is broken, not try/catch.

Writing code in node.js async style is very difficult for humans to do (at least for me), but compilers are excellent in this type of program transformations (continuation passing style). The Haskell and Erlang compilers and runtimes do this automatically. You write regular imperative code but it's executed in an asynchronous manner using co-operative fibers (maybe together with native os threads).

A similar solution is possible with an interpreted language that has some kind of continuations. Every blocking system call could be translated to a co-operative fiber context switch using kqueue/epoll.

What is really baffling to me is that none of the popular interpreted languages (javascript, python, ruby) seem to have decent continuations. I don't know of a reason for not implementing continuations in an interpreter, is there any? Using continuations, one could implement pretty nice async operations and exception handling.

Using return values for error handling is really painful. I've been writing a system call heavy C program, and about 40% to 50% (in LOC) of the code is error handling. In test code coverage, I can reach just over 50% branch coverage as most error conditions don't happen while testing. I don't know of a nice way to make e.g. the network fail at the right time to test my error handling in that case.

C++ is pretty nice when it comes to error handling and exceptions as resources are cleaned up on error. Python's with..as statement or Haskell's Control.Exception.bracket can do the same thing (the latter is just a regular function, not a language builtin). In C, most error handling is required to free some memory you allocated earlier, but even in a garbage collected language you still have system resources you need to free (file handles, sockets, db connections etc).

Re: Isaacs: try/catch is an anti-pattern

#56
post #48
post #44

Earlier quoted context omitted.

You mean collections.defaultdict http://docs.python.org/library/collections.html#collections....

No he doesn't. defaultdict provides a default value for ALL missing values. His approach allows him to target one key in particular, and is a very common python idiom.

I'm confused - where does that come from?

  >>> x.getDefault(1, 'Que?')

  Traceback (most recent call last):
    File "", line 1, in 
      x.getDefault(1, 'Que?')
  AttributeError: 'dict' object has no attribute 'getDefault'

Re: Isaacs: try/catch is an anti-pattern

#57
post #9

This seems to be exactly the attitude of Go: "We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional. Go takes a different approach. For plain error handling, Go's multi-value returns make it easy to report an error without overloa…

This is a bad idea that keeps coming back again and again. I see nothing wrong with exceptions, I do have a problem with (1) checked exceptions, and (2) catching exceptions prematurely and (3) people not learning how to use "finally" so they do (2) and rethrow. Languages like Go and Scala roll out various mechanisms that bring us back to the bad old days of C, when we had to check the return/value and or the error co…

I do have a problem with (1) checked exceptions, and (2) catching exceptions prematurely and (3) people not learning how to use "finally" so they do (2) and rethrow.

I am probably one of those people who catches exceptions prematurely and who hasn't learned to use "finally." If you link to some advice on how to use such things, I'll read it. I want to believe that I can learn a better way to use exceptions, but they just haven't clicked for me.

Then again, I've been using exceptions in an environment (C#) that matches the author's JSON.parse() example very well. The .NET library designers already decided what counts as exceptional, and it's often not possible for me, as a .NET user, to decide much of anything about the use or placement of try/catch.

...when you're building real systems, the complexity of the error handling can approach or exceed the complexity of the "normal" path...

I can understand all too well that a complex error handling path is intimidating, tedious, distasteful, and no fun at all. But in every serious project I've been a part of, error and exception handling has been where the bulk of the design, implementation, and testing work was done. That stuff is precisely how serious systems distinguish themselves from toys. Having a catch-all crash-path doesn't change that, because the serious system is not permitted to crash. As the author here points out, that's what assert() has offered for decades anyway.

Re: Isaacs: try/catch is an anti-pattern

#58
post #12

> Try/catch is goto wrapped in pretty braces. There's no way to continue where you left off, once the error is handled. ... > But still, nothing is as bad as the common "On Error Resume Next" that so many terrible VB programs start with. Assuming "On Error Resume Next" does what I think it does, you can't complain about both of these at once.

"On Error Resume Next" does not do what you think it does. It ignores the error, and marches blithely on. That is very different than "continuing where you left off, once the error is handled."

One of my personal pet peeves is seeing: "try: foo() \ except: pass" in code.

Re: Isaacs: try/catch is an anti-pattern

#59
post #48
post #44

Earlier quoted context omitted.

You mean collections.defaultdict http://docs.python.org/library/collections.html#collections....

No he doesn't. defaultdict provides a default value for ALL missing values. His approach allows him to target one key in particular, and is a very common python idiom.

Wops, you both are right, thanks for pointing it out. I meant to say dict.get, i.e.

  {'a':1}.get('b',5)
Post reply on HN