try/catch/throw (was: My Humble Co-routine Proposal)

22164 views
Skip to first unread message

Isaac Schlueter

unread,
Nov 13, 2011, 12:58:47 PM11/13/11
to nod...@googlegroups.com
I would like to respond a bit to the "try/catch isn't broken, YOU'RE
broken!" attitude in the coro thread.

My goal here is not to convince you. If you disagree, you should
probably keep disagreeing. However, my goal is to show that it is
possible for someone to find try/catch/throw distasteful, and not for
lack of understanding or some mental deficiency. (Unless you think
that I don't understand JavaScript, or am mentally deficient, in which
case you shouldn't take anything I say seriously anyway. I realize
I'm asking for a little bit of credit.)

In my experience, after using it in a few different forms in different
languages, and occasionally with some pleasure and happiness, I have
settled in the last few years on the sentiment that try/catch is a
mistake, and an anti-pattern. Quite a while before coming to Node, I
was already pretty dead-set against the overuse of try/catch/throw in
JavaScript, just dealing with client-side code. I think there even
may be a rant from ca 2008 or so in Yahoo's devel-frontend archives on
the topic.

Throw is great. I love throw. Throw is how you crash a program. It
should be like assert(0) in C. If it's not worth crashing your
program, on par with a SyntaxError or something else that is a clear
indication of a programmer mistake (and not, say, invalid data
encountered at run time), it should not throw. It's great to litter
code with asserts, even if it seems needless at the time, because it
saves you so much debugging time later down the road.

The fact that JSON.parse throws is a bit hideous. There is no
JSON.validate to check for syntax errors, so having JSON.parse throw
means that you *can't* interpret user-supplied JSON without a
try/catch. I much prefer php's json_decode function, since it just
returns `null` on invalid input. You can't reasonably argue that
this:

try {
foo = JSON.parse(input)
} catch (er) {
return "invalid data"
}

is more lightweight than:

foo = JSON.parse(input)
if (!foo) return "invalid data"


Try/catch is goto wrapped in pretty braces. There's no way to
continue where you left off, once the error is handled. What's worse,
in the code that throws, you have no idea where you're jumping to.
When you return an error code, you are fulfilling a contract. When
you throw, you're saying, "I know I was talking to you, but I'm going
to jump over you now, and talk to your boss instead." It's rude. If
it's not an emergency, don't do that; if it is an emergency, then we
should crash.

C programs that test the return value against 0 are much easier to
follow than C++ programs that just soldier on as if errors can't
happen, and let their parent caller catch them.

Making try/catch/throw do something reasonable in async JavaScript
means that you need to keep multiple stacks going, which is
effectively what coros/fibers are. That's fine, I guess, but it's not
free. Translating the code into more complicated JavaScript that does
this manually is another approach, but also not free.

At least JavaScript doesn't have typed catches. Holy black mother of
darkness, that shit is intolerable. But still, nothing is as bad as
the common "On Error Resume Next" that so many terrible VB programs
start with. (A coworker of mine once remarked, "You know how you
debug VB programs? Delete 'On Error Resume Next', fix half the
problems, then put it back." I think a better response would've been
"Delete 'On Error Resume Next', polish your resume, and give your boss
2 weeks' notice.")

The really nice thing about the cb(error, result) approach is that it
constantly reminds the programmer that you *must* acknowledge that an
failure may come from any request you make. The nice thing about
.emit("error", er) throwing is that it makes unhandled errors *crash
the program* which is what they should do, and encourages you to
attach .on("error", handler) to every emitter you interact with.

In the end, in my opinion (which I am very ok with you disagreeing
with), the offensive thing about fibers and streamline is that they
encourage the use of try/catch, which blurs the line between errors
that are *mistakes* (accessing a property of null, calling .write() on
a stream after .end(), etc.), and those which are expected
application-level problems (invalid data, file missing, and so on).


On Sat, Nov 12, 2011 at 17:24, Mark Stone <mark....@gmail.com> wrote:
> Hey I just wanted to interject that as a relative newcomer I am loving
> this discussion (even if I can only follow the details of about 40% of
> it).
>
> I suspect there are no absolute right or wrong answers, but I think
> its healthy to have so vigorous a discussion, and I think it is a good
> sign for the Node community that passions are so strong. And I have a
> lot of confidence in Tim; anything he works on will likely be a net
> gain for Node. People can make their own decisions about whether or
> not to make use of his contributions.
>
> I came to Node because JavaScript is something I've dabbled in for a
> long time, and the notion of server side JavaScript was appealing.
> Having done some reading before I plunged in, I also felt that
> architecturally Node was a much better approach to web serving and web
> services than, say, PHP, at least for the sort of code I was
> interested in writing. I have stayed with Node, and continue to
> struggle to wrap my head around it, because I have become a real
> believer in functional programming.
>
> Getting from "believer" to "adept practitioner" has been a real
> struggle for me. I learned programming in an imperative world; my
> first languages were Algol and Fortran, and my first big project in
> college was to write an Algol compiler for the PDP-1170. Object-
> oriented programming came along after I had stopped taking programming
> classes, and something about it always struck me as awkward. Not just
> hard to learn, but philosophically awkward. Functional programming is
> even harder to learn, and I don't know if I will ever completely get
> there, but it is elegant. Flat out elegant, no question.
>
> As a beginner aspiring to code Node the right way and use it in the
> ways that it is well suited, I don't want a bunch of synchronous
> crutches and workarounds. I want Node, Node modules, and Node examples
> to nudge me towards doing things the correct way. We beginners need
> tools to do what we want, but we also need to be broken of bad habits.
> Don't encourage us to continue those bad habits, and don't be shy
> about telling us off when we're being idiots. Just because JavaScript
> is easy and available, don't let us derail your community or take Node
> in a direction it shouldn't be going in.
>
> You guys all rock. Keep up the good discussion and the good work.
>
> -Mark
> --
> Mark  Stone || mark....@gmail.com || 253-223-2159 || Technical
> Project Manager, Adxstudio
> Co-author  and Editor, "Open Sources", "Open Sources 2.0"
> Alumnus: VA Linux systems, Wizards of the Coast, Microsoft (Server &
> Tools Business)
>
> --
> Job Board: http://jobs.nodejs.org/
> Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
> You received this message because you are subscribed to the Google
> Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com
> To unsubscribe from this group, send email to
> nodejs+un...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/nodejs?hl=en?hl=en
>

vincentcr

unread,
Nov 13, 2011, 2:11:34 PM11/13/11
to nod...@googlegroups.com
Interesting perspective. Perhaps it's my own idiosyncractic style of coding, but I'm finding that 99% of my "error handling" in node.js consists of sticking a "if (err) return callback(err) " or "if (err) throw err" at the start of almost all async invocations.  My opinion is that this violates the DRY principle, and is consequently not very robust to human error. 

also, it's just a convention, which even the node api does not always respect, because after all isn't it non-sensical to have an err param if your function cannot possibly return an error?

with try/catch on the other hand, you get what i think is the best of both worlds: (1) your program appropriately stops its normal processing on error, so you don't have to worry about the success/fail state of each invoke individually, unless you want to; and (2) you can put a catch at the appropriate level so that you don't crash your program if you can avoid it (eg return a 500 to that one request instead).

Jake Verbaten

unread,
Nov 13, 2011, 2:20:16 PM11/13/11
to nod...@googlegroups.com

+1 for being a good read.

+1 for not using try/catch

Liam

unread,
Nov 13, 2011, 2:29:35 PM11/13/11
to nodejs
On Nov 13, 11:11 am, vincentcr <vincen...@jucent.com> wrote:
> with try/catch on the other hand, you get what i think is the best of both
> worlds: (1) your program appropriately stops its normal processing on
> error, so you don't have to worry about the success/fail state of each
> invoke individually, unless you want to; and (2) you can put a catch at the
> appropriate level so that you don't crash your program if you can avoid it
> (eg return a 500 to that one request instead).

Don't return 500. Send a response which will retry the request in 1-3
seconds and page the admin.

Axel Kittenberger

unread,
Nov 13, 2011, 2:52:00 PM11/13/11
to nod...@googlegroups.com
> There is no JSON.validate to check for syntax errors, so having JSON.parse throw
> means that you *can't* interpret user-supplied JSON without a
> try/catch.  I much prefer php's json_decode function, since it just
> returns `null` on invalid input.  You can't reasonably argue that
> this:

No, but the second is broken. JSON can hold null as well, so you do
not destinguish between "null" and "Invalide { JSON" input. As simple
as possible, but not simpler! Throwing errors is in JS the resort of
function that must be able return everything. Its because Javascript
contary to other script languages has no multiple return values.
Parsing the string twice, first for check then for get is double as
CPU expensive just for some coding taste? Creating an object/array is
also not elegent and does not come free for the GC as well.

For this regard I like the java approach on exceptions, typed catches,
and your function either must catch it, or must write in the
declaration that it might throw it (and thus passes through)

The former one actually harder as more characters, its just your
coding style that blows it up.
try { foo = JSON.parse(input) }
catch (err) { return "invalid data" }

This one might not be broken, if it would work, but alas JS can't:
err, foo = JSON.parse(input)
if (!err) return "invalid data"

Isaac Schlueter

unread,
Nov 13, 2011, 3:09:52 PM11/13/11
to nod...@googlegroups.com
On Sun, Nov 13, 2011 at 11:52, Axel Kittenberger <axk...@gmail.com> wrote:
> try { foo = JSON.parse(input) }
> catch (err) { return "invalid data" }

Even when written inline, the try/catch is doing a lot more work than
it would to return a specific error-indicating value, since you can do
this:

try { foo = JSON.parse(input); bar = foo.baz; baz = bar.foo }
catch (er) { return "an error, somewhere" }

It's a goto.

> No, but the second is broken. JSON can hold null as well

Actually, since JavaScript has "undefined" as well as "null", and
"undefined" cannot be valid JSON, it could return undefined instead of
throwing. There's really no excuse there. Anyway, in practice, php's
json_decode is fine.


On Sun, Nov 13, 2011 at 11:11, vincentcr <vinc...@jucent.com> wrote:
> I'm finding that 99% of my "error handling" in node.js consists of
> sticking a "if (err) return callback(err) " or "if (err) throw err" at the
> start of almost all async invocations.

Yeah. That's how you do it.

> My opinion is that this violates the
> DRY principle, and is consequently not very robust to human error.

I find that it's expressive and clear, especially since you often want
to tack additional information onto the error to help follow it on the
way back up.


> also, it's just a convention, which even the node api does not always
> respect, because after all isn't it non-sensical to have an err param if
> your function cannot possibly return an error?

If your function cannot possibly return an error, then it's not doing
any IO, and should just return its result rather than taking a
callback.

Axel Kittenberger

unread,
Nov 13, 2011, 3:30:33 PM11/13/11
to nod...@googlegroups.com
> Actually, since JavaScript has "undefined" as well as "null", and "undefined" cannot be valid JSON,

Indeed, well you can easily write yourself a little wrapper around
JSON.parse that returns undefined. In fact now I must confess I just
remember in one app I recently actually did that :-)

> Yeah.  That's how you do it.

Depends what you do. A decade back, I wrote in C a preprocessor hack
that emulated well TRY and CATCH with setjump(), longjump(); why?
Because I wrote a daemon in the tidy way, and actually you should
check every write to a filedescriptor if it failed.

if (!write(fd, foodata)) { cleanup(); return "fail"; }
if (!write(fd, bardata)) { cleanup(); return "fail"; }
if (!write(fd, bazdata)) { cleanup(); return "fail"; }
if (!write(fd, bladata)) { cleanup(); return "fail"; }
for(i = 0; i <100; i++) if (!write(fd, array[i]) [ { cleanup();
return "fail")}

Bla. Tiresome. Many people just write dirty and ignore many errors
like the return value of write (in C), but thats no excuse. That code
example might have gone with a real goto as well, but mine got into a
few subroutines etc. even this pseudo exceptions made the thing much
nicer.

Do what works for you.

Bruno Jouhier

unread,
Nov 13, 2011, 3:41:49 PM11/13/11
to nodejs
Hi Isaac,

It won't be a surprise that I have a different opinion.

My disagreement comes mainly from two assertions that you make:
1) Throw is how you crash a program.
2) The really nice thing about the cb(error, result) approach is that
it
constantly reminds the programmer that you *must* acknowledge that an
failure may come from any request you make.

As I wrote in the other thread, I've done a lot of programming with
try/catch and the reason why I find it so nice is because I take a
very different approach:

1b) Throw is how you signal that you cannot fulfill your contract (the
reason in in the exception's message).
2b) Feel relaxed about exceptions, don't catch them locally, let them
bubble up, except in a few strategic places where they will be caught
(and your program won't crash, it will recover from there).

What I have seen too often is that people get nervous about
exceptions, and they feel bad about letting them bubble up. Their
reasoning is a bit the following: I'm calling this openFile function,
it may fail so I need to do something about it, right here, at the
point where openFile failed.

The mistake is that 90% of the time, there is nothing you can do
"locally" about openFile failing: very often, the file that you are
opening *should* have been there (for various reasons) and if it is
not there your function won't be able to do what it is supposed to do
(in legal terms, it won't be able to fulfill its contract because one
of its subcontractors could not fulfill its subcontract). So, what can
you do locally? Nothing! Then *do nothing*! Just let the exception
bubble up.

As you walk up the stack, you'll find contracts that get more and more
permissive. Maybe the function that called openFile was doing it to
load a resource (a contract that could not be fulfilled either), but
maybe 5 stack frame up, there is a function that is just trying to
answer a question for the user. At this point, you can catch, format a
special message saying "*attention*: something went wrong and I could
not get your answer. Please contact xyz and give him the following
message: "file abc not found". 5 levels up, the contract was much
looser (it was just "return the answer, or the reason why you could
not answer" and you can fullfil this contract by returning this
message.

So the big mistake I've always seen people make is being too "nervous"
about exceptions and feeling that they have to do something about them
as close as possible to the point where the exceptions were raised.
They need to the exact opposite: feel relaxed about exceptions and let
them bubble up.

If you do this, you end up with very little error handling code, you
have well defined contracts, and very robust programs. At least this
is my experience.

There is actually something a bit immoral in this: the programmers who
care about errors and try to do a good job are the ones who misuse the
feature by catching locally. The lousy programmers who do not care
about errors do the right thing. Fortunately this does not last too
long and the good programmers usually get it and keep their jobs.

But of couse this only works if try/catch can catch the whole flow.
With async callbacks, the flow is broken into lots of smaller flows
and try/catch can only catch one flow at a time so it becomes more or
less unusable.

Bruno

Axel Kittenberger

unread,
Nov 13, 2011, 4:49:26 PM11/13/11
to nod...@googlegroups.com
While I agree, I think what went wrong with the other thread is we
started to tell each other how to code. *That* was the part that was
not productive.
(IIRC It started by the "underscore group" (whatever that actually
mean? we-think-any-sync-ideas-are-evil-group tell the "upperscore
group" they are doing it all wrong, while they were considering new
solutions... then upperscore replied, no actually we think its better
to do it that way, because of that, and the underscore group felt
offended, "you do not tell me I'm broken" etc. Forth and back. So can
we please just accept there are different styles? I notice going
myself different ways with every project, because different approaches
fit different solution domains better.

vincentcr

unread,
Nov 13, 2011, 5:06:43 PM11/13/11
to nod...@googlegroups.com

>> For this regard I like the java approach on exceptions, typed catches,
>> and your function either must catch it, or must write in the
>> declaration that it might throw it (and thus passes through)


I gotta say, this is the first time i see somebody defending checked exceptions. it sounds good in theory, but in practice it pretty much defeats the purpose of try/catch, because it leads to horrors like programmers wrapping the whole function with a general try/catch that silently eats everything, so that the compiler will stop complaining and let them continue their work ("i'll fix it later", they tell themselves, but then they forget). no other language has reimplemented this since.

Liam

unread,
Nov 13, 2011, 5:10:24 PM11/13/11
to nodejs
Isaac & Bruno, I suspect you guys have backgrounds with very different
kinds of apps, which require different styles of error handling.

Everyone ought to preface his opinions with "in this context which I'm
familiar with, where we x & y for customers like abc, ..."

Not too many people have created a huge range of applications with
Javascript, given that it was confined to the browser and some app-
specific scripting until recently.

On Nov 13, 12:41 pm, Bruno Jouhier <bjouh...@gmail.com> wrote:
> But of couse this only works if try/catch can catch the whole flow.
> With async callbacks, the flow is broken into lots of smaller flows
> and try/catch can only catch one flow at a time so it becomes more or
> less unusable.

You can certainly bubble exceptions with

appFunc(input, callback) {
io.async(input.x, function(err, result) {
if (err) return callback(err);
...
});
}

So async interferes with local try/catch (where it's actually quite
valuable), but less so global.

Adam Crabtree

unread,
Nov 13, 2011, 5:15:35 PM11/13/11
to nod...@googlegroups.com
With async code, there's a muddling of issues. In sync code, a logical stack frame and a literal stack frame tend to be coupled, whereas in async code they are not. Let's say I have a series of operations I wish to perform (a logical stack frame). In a sync environment, those operations would all be contained in the same literal stack frame:

a(); b(); c();

However, in async code, each async call will fall into a new stack frame (or stack altogether), while still remaining within the same logical stack frame. This can be confusing and/or frustrating since as a developer I don't want to have to handle errors per literal stack frame, but per logical stack frame. The resulting model feels broken and tedious, especially to individuals coming from other environments/languages, resulting in an extra if-error-throw line per async call per stack frame. In other words, in order to perform n async operations in series, I require n EXTRA lines of code over the sync version, and that's not even including the extra if-error-throw lines per stack frame in a given async call, so the increase in total code actually ends up being something more like, n x m (where m is the average stack depth) of EXTRA LINES OF CODE just to bubble errors that in a synchronous environment are a non-issue.

This gets tedious real quick, and as a software engineer your spidey senses immediately go off telling you that SURELY you must be doing something wrong, you take to the mail list or irc and create a lot of extra noise finding out ultimately that this is just how things are done, and then you just accept it and find yourself resenting async code for being so damn needy and long for that sexy (?!) synchronous code you used to date!

So what's a developer to do?

Well, first, my async trycatch() module, http://github.com/crabdude/trycatch, addresses the issue of try/catch not being asynchronous and try/catch not properly catching the long stack trace. Also, I've rolled it into step (my personal flow-control of choice) and made stepup, http://github.com/crabdude/stepup, which allows async and sync errors to be treated as equals, handled automatically, and coalesced so they can be handled at a logical stack frame level instead.

Some more details:

trycatch() catches all synchronous errors by wrapping each new userland stack with a try/catch while maintaining the long stack trace and providing color coding for said stack trace: black for core, cyan for node_modules based code, and red for everything else (assumed to be non-module, non-core code).
stepup() incorporates trycatch with step's existing try/catch per callback to allow catching of 3rd party errors that exist in a deeper async stack, unable to be caught with a sync try/catch. Also, it goes further by adding error coalescing so avoid the redundancy of the "if(err)cb(er);" or "if(err) throw err;" (this infamous line needs a name... how about "if-error-throw"? =)).

stepup(function onError(e) {
  // called on any of the sync or callback-based errors below
  console.log(e.stack);
  // continue with next callback
  this('this is the value of data');
}, function() {
  throw new Error('one way'); // or a runtime error

  // imagine this internally in a 3rd party module
  setTimeout(function() {
    throw new Error('second way'); // ditto
  }, 0);

  // third way
  fs.readFile('/does_not_exist.js', this);
}, function(data) {
  // note, the err argument is not passed
  console.log('Called after onError');
});

I haven't yet benchmarked trycatch and any effects it may have on performance, but I'd expect they're on par with Tom Robinson's long-stack-trace module from which the technique was borrowed.

Lastly, I'd add that this isn't a cure-all. You still have to handle async callback errors manually that don't pass through stepup's callback system first. Ideally, I'd like to catch all errors automatically, but there's a problem in that there are different types of errors as Isaac states.

From experience, I've found there to be generally 3 types of errors:

1. Internal: core-generated async errors.
2. External: module-generated async errors.
3. Synchronous: Runtime errors and thrown errors.

1 & 2 differ in that 1 can be anticipated as the APIs are published and known, whereas 2 cannot be anticipated (wrapped) and thus cannot automatically be handled except at runtime. Tim Caswell and I have contemplated requiring some form of self-declaration and have separately proposed a built-in meta property for detecting/declaring functions as async. 

Also, trycatch only handles 3, and stepup handles 1 & 2 only when they're passed to the next step. It's difficult to handle either 1 or 2 automatically since they may be expected, which means you'll still find yourself writing if-error-throw lines every time something like stepup is not utilized for callbacks, not a huge issue, but an issue nonetheless. Automatically handling / coalescing errors for 1 & 2 is the next step (no pun intended ;)) for stepup.

Marco Rogers

unread,
Nov 14, 2011, 12:15:48 AM11/14/11
to nod...@googlegroups.com


On Sunday, November 13, 2011 2:15:35 PM UTC-8, Adam Crabtree wrote:
With async code, there's a muddling of issues. In sync code, a logical stack frame and a literal stack frame tend to be coupled, whereas in async code they are not. Let's say I have a series of operations I wish to perform (a logical stack frame). In a sync environment, those operations would all be contained in the same literal stack frame:

a(); b(); c();

However, in async code, each async call will fall into a new stack frame (or stack altogether), while still remaining within the same logical stack frame. This can be confusing and/or frustrating since as a developer I don't want to have to handle errors per literal stack frame, but per logical stack frame. The resulting model feels broken and tedious, especially to individuals coming from other environments/languages, resulting in an extra if-error-throw line per async call per stack frame. In other words, in order to perform n async operations in series, I require n EXTRA lines of code over the sync version, and that's not even including the extra if-error-throw lines per stack frame in a given async call, so the increase in total code actually ends up being something more like, n x m (where m is the average stack depth) of EXTRA LINES OF CODE just to bubble errors that in a synchronous environment are a non-issue.

This is an awesome way to describe the problem domain. Thank you.

Also, based on your description here, I suspect that what you will end up with is something that works like the domains idea, but way harder since you currently have no support from node core. I don't know how far along the domains proposal is and I don't have a ton of info about it. I think the important thing to mention here is that domains is a signal that node core is open to a proposal that handles "the async problem" in an elegant way.

Perhaps you and everyone else should start thinking about how you could make things a little better if you had core support. I suspect whatever the solution is will have a few constraints.

- Doesn't change v8
- Doesn't require a compile step
- Doesn't significantly change the semantic feel of javascript

Other than that, I'm really interested to see what people can come up with.

:Marco

Marcel Laverdet

unread,
Nov 14, 2011, 12:18:03 AM11/14/11
to nod...@googlegroups.com
I have a more rounded reply that I'd like to send out but I'll save for later (no time to collect my thoughts for now) and Bruno hit most of my points anyway.

But I just wanted post about reasonable usage of try/catch. I actually don't use try that much in my code. I do use throw pretty frequently though. I ran a quick search and in my current application I have 6 throws for every try. I expect that ratio to only get bigger as time goes on and my application matures.

Marco Rogers

unread,
Nov 14, 2011, 12:27:03 AM11/14/11
to nod...@googlegroups.com
I had the same thought today; that different types of applications require different styles of error handling. We should talk more about that to give things some context. Keeping in mind that right now, node's primary focus is network-enabled server applications. Those considerations will almost always come ahead of others.

Also I don't think this is what you're saying, but I don't want to see this turn into a "show me your papers" type of argument. You don't need to prove that your ideas are worthy of being heard. The arguments behind your ideas will do that. If you don't have enough experience, your arguments won't hold much water against people who have it. But if you're smart and you can transfer your experience from some other domain effectively, you'll do fine. That's usually how this works.

Also, some people here have built large projects with javascript both before and after node. Isaac is one of them.

I suspect what you're really getting at is that we shouldn't presume to know where another person is coming from with their argument. And we should be careful not to assume the people across from us have the same background as we do. I certainly agree with that. If you're not sure, you should ask :)

:Marco

On Sunday, November 13, 2011 2:10:24 PM UTC-8, Liam wrote:
Isaac & Bruno, I suspect you guys have backgrounds with very different
kinds of apps, which require different styles of error handling.

Everyone ought to preface his opinions with "in this context which I'm
familiar with, where we x & y for customers like abc, ..."

Not too many people have created a huge range of applications with
Javascript, given that it was confined to the browser and some app-
specific scripting until recently.

Bruno Jouhier

unread,
Nov 14, 2011, 4:16:03 AM11/14/11
to nodejs
I did a similar grep | wc on our app. Found 178 throw, 24 catch in 48k
loc, which gives:

* 1 catch every 2,000 lines
* 1 throw every 270 lines.
* a throw/catch ratio of 7.41

Bruno
> > and made stepup,http://github.com/crabdude/stepup, which allows async

darren

unread,
Nov 14, 2011, 10:30:46 AM11/14/11
to nod...@googlegroups.com
Thanks for elucidating the drawbacks of try/catch so well.  I suspect there are a lot of programmers who agree with you but haven't summarised their concerns as nicely as this.  Your post is spot on.

Adam Crabtree

unread,
Nov 14, 2011, 11:05:33 AM11/14/11
to nod...@googlegroups.com
> If it's not worth crashing your program,
...
> it should not throw.

Reasonable, but the counter-argument is that a runtime error or a "programmer mistake" is not always "worth crashing." Correct me if I'm wrong, but your argument more or less boils down to, there are expected errors and unexpected errors (aka mistakes) and unexpected errors should throw and thus crash your program. So the argument I feel is whether all unexpected errors should crash IMMEDIATELY.

This is an important distinction and I believe at the heart of the "anti-pattern" if there is one. Is try/catch being used as a kludge and thus promoting greater problems? Or, is try/catch being used to bail-out of a given logical stack (async stack/contract)? High concurrency and crashing immediately, in my opinion should not be taken seriously together, and this I think is the primary issue of the debate. The purity of your argument is appreciated, but the pragmatic reality is that we want to return a 500 and not take down every other concurrent request in the process. As a Javascript programmer, nay, as a programmer, I know I write buggy code; I know it will crash; and I know I need to expect that to happen and plan accordingly. Running indefinitely in an unknown state should be avoided, but having an effective bail-out rather than crashing immediately is the reasonable response in these cases even if at best it means accepting no more responses, attempting to allow concurrent requests to resolve, and if necessary pushing them to another process to handle.

In practice, I'm an engineer, not a computer scientist. I build tools and adopt patterns that help me accomplish the goals I seek to accomplish irregardless of the "religion" behind it. Historically, there seems to be a sentiment that this is a dark path that leads to inferior implementations and a slow and gradual undermining of one's code integrity, and while I may agree with the potential for such, I'd argue the potential for equally dark things exists on all sides of all arguments and is more a symptom of our humanity manifesting specifically within the context. In other words, try/catch and an async trycatch (http://github.com/crabdude/trycatch ;)) to my knowledge is the only way to provide the same functionality in node that exists in other languages, returning 500s and in the case of a development environment, a 500 with a stack trace.

Please correct me if I'm wrong. As always, thanks for the exceptional work and feedback you provide to the node community!

Cheers,
Adam Crabtree

Mark Nottingham

unread,
Nov 14, 2011, 11:44:17 AM11/14/11
to nod...@googlegroups.com
I'd have much less trouble with try/catch if people actually documented what their code throws, and do so accurately. That requires a lot of discipline (in a dynamic language).

Isaac Schlueter

unread,
Nov 14, 2011, 1:59:38 PM11/14/11
to nod...@googlegroups.com
On Mon, Nov 14, 2011 at 08:05, Adam Crabtree <atcra...@gmail.com> wrote:
>> If it's not worth crashing your program,
> ...
>> it should not throw.
> Reasonable, but the counter-argument is that a runtime error or a
> "programmer mistake" is not always "worth crashing." Correct me if I'm
> wrong, but your argument more or less boils down to, there are expected
> errors and unexpected errors (aka mistakes) and unexpected errors should
> throw and thus crash your program. So the argument I feel is whether all
> unexpected errors should crash IMMEDIATELY.


Ah, thanks, yes, I did not make this clear in my post.

When I say mistakes, I'm referring to stuff like SyntaxErrors, or
calling .write() after .end(). Cases where you clearly just
programmed it wrong. These are the sorts of things that smoke testing
or functional testing usually uncovers, even without disciplined unit
tests, and are a normal part of the development process.

Yes, in production, you maybe want to catch this and write a log or
page someone when it happens. (Let's not revive the whole
process.on("uncaughtException") thread.) Suffice it to say, in
development, you definitely want to crash as fast and helpfully as
possible, and in production, you may want to clean up (restart, crash,
etc.) somewhat more gracefully (perhaps after a 500 response).


When I say "errors", I'm referring to things that are outside of your
control, but reasonable. For instance, a file was deleted, a remote
server could not be reached right now, etc. These are things where,
no matter how correctly you write your code, you may still encounter a
problem, so you've got to deal with it.

Will Conant

unread,
Nov 14, 2011, 4:18:28 PM11/14/11
to nod...@googlegroups.com
I generally agree, but sometimes you have a framework that runs code that might be totally broken. In a perfect world, automated testing and a flawless QA process would keep this from happening, but in my very not perfect world, it happens all the time. I really don't want exceptions to crash my web server, and I don't want connections to just hang when something goes horribly wrong and the uncaughtException event keeps my process alive. This problem is one of the reasons that CPS transforms and fibers have appealed to me. Honestly, though, if Node had something like thread-local storage (save state when a callback is added to the uv loop and then reinstate before the callback is called (including uncaughtException)), you could recover from horrible errors really nicely without try/catch.

Will Conant

unread,
Nov 14, 2011, 4:20:39 PM11/14/11
to nod...@googlegroups.com
I should have said, "I generally agree with Isaac."

Mikeal Rogers

unread,
Nov 14, 2011, 4:37:06 PM11/14/11
to nod...@googlegroups.com
Like a year or so ago I remember Brendan saying, I believe it was on A Minute w/ Brendan, that error handling was being discussed for a future version of js.

I remember him saying something about the traditional try/catch system not being ideal. For one thing, you just get a failed state and some stack info, you don't even *really* get the stack, you can't fix it and attempt to move on or continue, inspection is somewhat limited, etc. 

I think it's relevant since a portion of this thread seems to assume that try/catch is wonderful and we need to extend the pattern as far as possible. Remembering this reminded me that we can do better than try/catch and that's where we should aim.

On Nov 14, 2011, at November 14, 20111:20 PM, Will Conant wrote:

I should have said, "I generally agree with Isaac."

Will Conant

unread,
Nov 14, 2011, 4:56:54 PM11/14/11
to nod...@googlegroups.com
It looks like Bruno suggested the thread-local storage concept over a year ago:


I think it is a fabulous idea because it a) lets you gracefully handle errors in seldom-run totally broken code, and b) would let you keep a sort of virtual stack trace for figuring out the context in which things go wrong even when you can't recover.

Imagine if modules could do something like this:

tls.trace("handling request to /foo/bar");

And then in uncaughtException, you could do:

tls.printTrace();

In some ways, this is WAY better than a real stack trace because you would get in the habit of annotating the general processes in your code. Even a stack trace doesn't tell you which URL caused the error.

Justin

unread,
Nov 14, 2011, 5:21:11 PM11/14/11
to nodejs
Thanks for the trycatch library! We recently used that as a solution
to what I see as the worst hole in node:
Uncaught exceptions are the worst kind, so they're the ones where I
most need the original context, but in node proper, it's all lost. In
particular, I need to know what URL was the origin of the error, so I
can locate the offending code and/or the person to fix the code.
I think domains are the solution to that problem (although it's hard
to tell right now), but being such a glaring hole, I at least needed a
patch for now. Glad I found trycatch.
It seems that the crash-only argument is getting conflated with the
ability to track flow at all. The two can be related, but they don't
have to be.
> Well, first, my async trycatch() module,http://github.com/crabdude/trycatch,
> addresses the issue of try/catch not being asynchronous and try/catch not
> properly catching the long stack trace. Also, I've rolled it into step (my
> personal flow-control of choice) and made stepup,http://github.com/crabdude/stepup, which allows async and sync errors to be

Bruno Jouhier

unread,
Nov 14, 2011, 6:16:13 PM11/14/11
to nodejs
Yep. There is always some event which is at the origin of the function
that your server is currently executing. Could be a request for a web
page, a web service call, data arriving on a socket and it is very
helpful to be able to correlate the current function with the original
event. This is important for exception handling (would be nice if the
agent who initiated the original event could receive an error response
instead of hanging until a timeout happens). And, in the type of
application we're building, there is usually a whole context
associated with the original event. This context is related to WHO
initiated the request: an identity, a security context, a locale
(which influences the way things are formatted), a timezone, etc. We
need to propagate this information through the various callbacks and
events.

You cannot pass this "context" as a function parameter because you
would need to have it in almost every function that you write. Very
impractical and it does not work with functions like toString() that
have a fixed signature (so that they work with string concatenation).

If all your code fits in one function, you can get away with the
closure, but usually the code goes through many functions and many
modules so this does not work either.

What you need is a global, and a global that gets preserved across all
continuations.

This is not really difficult to do:

function handleEvent() {
var context = global.context;
somethingAsync(function(err, result) {
global.context = context;
...
});
}

but this is very tedious and error prone. If you miss one continuation
you're toasted!

In streamline (and please don't take this as advertising, at this
point, streamline is more of a pedagogical tool for me), I propagate
it with the pattern that I just gave above. But this is probably
something that core could do. Note that there is no need to propagate
anything complex, Just the value of one reserved global (which could
be hidden in a module). The idea is that the global will be set to
some new object when the initial event is processed and then the
application will be free to put whatever it needs into this global. In
streamline I'm not exposing the global directly, I have a
flows.setContext/getContext to access it.

Anyway, I'm just contradicted myself tonite. A few hours ago I said
that we should not push new things into core and now I'm describing a
feature that requires core support :-) I didn't have time to dig into
what's proposed for "domains" but it looks like this is the kind of
problem that domains should handle. I don't really need it because I
use a preprocessor that generates the extra code around every async
call but I imagine that a number of people would be happy to have
something like this "out of the box".

Bruno.

Marco Rogers

unread,
Nov 14, 2011, 9:40:35 PM11/14/11
to nod...@googlegroups.com
From what I've heard, this is very similar to how domains are supposed to work. The domain object is the global context you're talking about, and you can hang any other state off of it. And when you create the domain, it will be carried across all async boundaries that occur starting from this point of origin. I'm assuming you'll be able to retrieve this context at any time with getCurrentDomain or some such thing. So no need to pass it around.

It may seem like I'm trying to hype up domains. I'm not. I haven't even seen anything concrete. I'm just relaying the info I've heard and extrapolating a few things. I could also be very wrong. I feel the need to keep adding these disclaimers. But I also think people need to hear more about what's been happening on this topic. The core team is clearly not ready to reveal it yet. I hope I'm not screwing with their plans. Just consider me the rumor mill.

:Marco

--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en



--
Marco Rogers
marco....@gmail.com | https://twitter.com/polotek

Life is ten percent what happens to you and ninety percent how you respond to it.
- Lou Holtz
Reply all
Reply to author
Forward
0 new messages