Live data from Hacker News

Exceptions (2003)

joelonsoftware.com

41–50 of 93 posts

Re: Exceptions (2003)

#41

I am not a huge fan of exceptions per se, but it’s important to understand that they are a heuristic for minimizing ‘worry’ about things that are unlikely. I am not saying it’s a good thing, I am saying it’s the way people naturally work. Let’s say there is a function that’s 20 lines long, and if you did a thorough analysis of possible error conditions, regardless of likelihood, you might come up with 50 or more. We…

50 error conditions in a 20-line function doesn't really sound even remotely realistic. Probably only 5 of the 20 lines are actually calling functions that might return errors, and in most cases, we don't care what type of error is being returned. So if we're talking about 5 error-checks in 20 lines, then yes, we absolutely should write code to address them from the start . I mean, I can understand not dealing with e…

Nonsense.

Is the network up?

Is the connection to SQL up?

Did someone just turn off the SQL machine half way through the query?

Can we find the server?

Are there any rows?

Did the SQL compile?

Do I have rights on this table?

Have you just terminated me as a result of a deadlock?

Did you return a Null when I was expecting a value?

Did you return a float when I was expecting an int?

Did my value just overflow?

Did you just return 0 and I tried to use it in division?

Did I just try to access the session but some other idiot clear it?

Did I just try to call a method on an object that is in fact null?

And that's all possible in a three liner off the top of my head. I'm sure there's plenty more than that that are possible! I didn't even start on the file ones...

Re: Exceptions (2003)

#42
post #17

Everyone here seems to agree that "exceptions are for exceptional conditions". The problem is that when you get down to details, there is disagreement about what exactly is an "exceptional condition". e.g. - you are trying to open a file for reading. The file does not exist. Is this exceptional? That depends on context, but the function that opens the file, being in an independent library, is usually designed without…

The rule is that all functions should return a valid result or not return at all. Aka the Samurai Principle: http://c2.com/cgi/wiki?SamuraiPrinciple

However, an important point is that a "valid result" maybe a status indicating what went wrong. For example, a function that reads an http url may return (OK, 200), (NOT_FOUND, 404), (REDIRECT, 301) and so on. But in addition to that, the function may also throw an exception if a dns lookup error occurred for example.

Opening files on the other hand, can fail unexpectedly for a billion different reasons. Most of which an opening function can't detect or do anything about and therefore can't return a valid result if they occur. Therefore it must throw an exception.

Re: Exceptions (2003)

#43
post #17

Everyone here seems to agree that "exceptions are for exceptional conditions". The problem is that when you get down to details, there is disagreement about what exactly is an "exceptional condition". e.g. - you are trying to open a file for reading. The file does not exist. Is this exceptional? That depends on context, but the function that opens the file, being in an independent library, is usually designed without…

Vague value judgments ("only for exceptional conditions") are the inevitable result of a failure to reason.

The semantic function of exceptions is just a way for summing additional values onto the return type of a function because it has results that are not contained within the primary type. In this way, they are a more general and better typed version of NULL (which has it's places - contrary to the modern dogma, these sort of features are needed due to inherent complexity). The standard ways of attempting to avoid this are to either use sentinel values that exist in your standard return type like fd == -1 for an error (thereby making your program less typed), or to create a top-level sum type for every aggregated function return type (cluttering your program with nominal types). Multiple values make the most sense, but those are ad-hoc product types, so you're eschewing the type system in favor of informal invariants.

The syntactic function of exceptions is to avoid constantly repeating (check for error, return error), which often leads to the poor practices of ignoring errors or calling a global exit(). One goal of programming languages is to automate, so it makes sense to capture this oft-repeated pattern. But problems arise when people end up forgetting that every function can have a possible return immediately following it.

It seems some syntactic middleground is needed to signal the complete return type of a function definition, and the possibility that a given function call may quick-return. Honestly (and I hate to say it), but Java probably started down the right track with checked exceptions, but being a B&D language it ended up being waaaay too verbose. And lacking a way to aggregate types along anything but the baked-in hierarchy, people fell into using generic and uninformative 'throws Exception'. And open types make it so there's little point trying to enumerate exhaustive causes. But that doesn't mean that one can't start with the idea of non-silent but syntactically lightweight exceptions and come up with something that avoids a lot of the pitfalls.

Re: Exceptions (2003)

#44
post #6

If you're catching exceptions all over the place, or worse using them for flow control as part of normal operations, you're doing it wrong. Exceptions should indicate a major error that you can't easily recover from and as such should be caught and logged at the top of the stack, i.e. the main thread run method or request handler. When used that way, they give you very useful information as to what went wrong and whe…

"Exceptions should indicate a major error that you can't easily recover from"

Maybe in Java and C++, but in Common Lisp we have restarts that allow you to recover from an exception. I like to use the example of attempting to write to a file when the disk is full, because:

1. It is possible to recover from the exception (e.g. ask the user to delete some files)

2. It makes no sense for the I/O library to do all the things needed to recover

3. It is a maintenance headache for client code to do all the things needed to recover

With restarts things would look like this: the I/O library would set up a restart for write that would retry the operation, the client code would catch the exception, prompt the user to free some space, and when the user indicates the space is free the restart is invoked. The I/O library knows the right way to restart the operation, and client code knows whether or not that should happen, and you get code that does not just quit over a disk being full.

Re: Exceptions (2003)

#45

Exactly fits the philosophy of Google Go - http://blog.golang.org/error-handling-and-go I think his point of there being easy syntax for multiple returns is critically important to make this sort of error handling non annoying - which Go does remarkably well. I think this factor has a lot to contribute to the fact that you get the warm fuzzy feeling after your code compiles. You feel confident that you have already h…

But why is this preferable to an either monad? The majority of the time when a subfunction fails with error, I return the error from the function calling it, so I'd rather just have a do notation or something take care of that for me.

Re: Exceptions (2003)

#46

Exactly fits the philosophy of Google Go - http://blog.golang.org/error-handling-and-go I think his point of there being easy syntax for multiple returns is critically important to make this sort of error handling non annoying - which Go does remarkably well. I think this factor has a lot to contribute to the fact that you get the warm fuzzy feeling after your code compiles. You feel confident that you have already h…

The name of the language is Go, not "Google Golang" That said, this was, for me, the single weirdest thing to get used to when starting to program in Go, coming from a mostly Python/Java exception-style background. (I imagine it's easier if you're a C programmer). However, once I got into the swing of it, I realized I really, really like Go's error handling, and I can't imagine going back to Python's exceptions volun…

I have to say, I much prefer people referring to it as Golang, otherwise it is impossible to search for.

Re: Exceptions (2003)

#47
post #6

If you're catching exceptions all over the place, or worse using them for flow control as part of normal operations, you're doing it wrong. Exceptions should indicate a major error that you can't easily recover from and as such should be caught and logged at the top of the stack, i.e. the main thread run method or request handler. When used that way, they give you very useful information as to what went wrong and whe…

If you're catching exceptions all over the place, or worse using them for flow control as part of normal operations, you're doing it wrong.

That is a subjective view, and certainly not a universal one. In Python, for example, exceptions are routinely used for flow control purposes; see StopIteration.

Re: Exceptions (2003)

#48

Earlier quoted context omitted.

50 error conditions in a 20-line function doesn't really sound even remotely realistic. Probably only 5 of the 20 lines are actually calling functions that might return errors, and in most cases, we don't care what type of error is being returned. So if we're talking about 5 error-checks in 20 lines, then yes, we absolutely should write code to address them from the start . I mean, I can understand not dealing with e…

Nonsense. Is the network up? Is the connection to SQL up? Did someone just turn off the SQL machine half way through the query? Can we find the server? Are there any rows? Did the SQL compile? Do I have rights on this table? Have you just terminated me as a result of a deadlock? Did you return a Null when I was expecting a value? Did you return a float when I was expecting an int? Did my value just overflow? Did you…

Functions should be scoped appropriately so that they only deal with one thing at a time. A function that checks for network connectivity isn't going to be checking for missing rows. Those are different problems, and should be handled by different functions.

Using your example, we have a function that queries a database. It will be given a valid database connection, and return the result of the query.

There is no "valid database connection" logic, since that is handled elsewhere. There is no result validation logic, since that is handled upstream. This function only cares about A) querying and B) returning a value (possibly null). The only exceptions that is handles is when something specific to it's domain goes wrong - for example, unauthorized access to a table. That is an error that is above networking (the connection worked fine) but clearly not a data validation problem (no data), so we handle the exception here.

If you find yourself throwing exceptions "across problem domains", that's a good indicator that your functions are doing too much.

Re: Exceptions (2003)

#49
post #17

Everyone here seems to agree that "exceptions are for exceptional conditions". The problem is that when you get down to details, there is disagreement about what exactly is an "exceptional condition". e.g. - you are trying to open a file for reading. The file does not exist. Is this exceptional? That depends on context, but the function that opens the file, being in an independent library, is usually designed without…

Going one step further on the file example, I had a college professor who wrote a function read from a file that had no end except an exception thrown by the read because the file was at its end[1]. He said the end-of-file was an exceptional circumstance for a function that expected to read and process a line. I doubt anyone would say end-of-file is unexpected, but I am not sure I would say it was exceptional. 1) som…

In Java, everyone would tell you this is wrong. In Python, this is the normal way to end an iteration (specifically, throwing StopIteration). It's not a huge leap to see a stream as an iteration over bytes. So, what's considered exceptional seems somewhat culturally dependent.

Re: Exceptions (2003)

#50

Earlier quoted context omitted.

50 error conditions in a 20-line function doesn't really sound even remotely realistic. Probably only 5 of the 20 lines are actually calling functions that might return errors, and in most cases, we don't care what type of error is being returned. So if we're talking about 5 error-checks in 20 lines, then yes, we absolutely should write code to address them from the start . I mean, I can understand not dealing with e…

Nonsense. Is the network up? Is the connection to SQL up? Did someone just turn off the SQL machine half way through the query? Can we find the server? Are there any rows? Did the SQL compile? Do I have rights on this table? Have you just terminated me as a result of a deadlock? Did you return a Null when I was expecting a value? Did you return a float when I was expecting an int? Did my value just overflow? Did you…

I totally disagree. These are all the same:

    Is the network up? / Is the connection to SQL up? / Did someone just turn off the SQL machine half way through the query? / Can we find the server?
These are not errors, just normal business logic that has to be handled:

    Are there any rows? / Did you return a Null when I was expecting a value?
These are not runtime errors, they're just debugging during development (with possible exception of overflow, depending on context):

    Did the SQL compile? / Do I have rights on this table? / Did you return a float when I was expecting an int? / Did my value just overflow? / Did you just return 0 and I tried to use it in division?
And likewise, these all just have to do with the design of your program, which you either know you have to deal with or not:

    Have you just terminated me as a result of a deadlock? /  Did I just try to access the session but some other idiot clear it? / Did I just try to call a method on an object that is in fact null?
I already said that you may not have to worry about things like memory allocation errors, depending on your needs. Most of the stuff listed above is either redundant, or has more to do with the design of your program. I stand by my point that, in most programming (say, back-end web stuff), you're handling more like 5 errors per 20 lines, not 50 per 20.

And that, yes, those 5 (or however many) errors should be planned for from the start.

Post reply on HN