9 ms·
Exceptions (2003)
- Piskvorrr 13y ago"Monday, October 13, 2003" - more like "the old new GOTO"; but apart from the out-of-the-blue rant about PHP (version 4, I guess?) at the bottom, this still seems applicable.
- rvkennedy 13y agoHis argument is really against using exceptions as part of normal operation, which I agree with. I've noticed when writing plugins to big monolithic programs like 3DS Max and game engines that these tend to throw and catch exceptions as a matter of course, which is a pain when you're trying to debug your own plugin code. An exception should mean "normal operation cannot continue", and should signify a bug in your code. As such, it is an exceptionally good software development tool.
- pionar 13y agoI agree. Exceptions are a very useful tool, when used appropriately. Problem is, they're not used very appropriately.
- smtddr 13y agoI got a question. Do you think it's okay use exceptions to end what would be otherwise an infinite loop? Python example: data = "" socket.settimeout(1.5) while 1: try: data += socket.recv(1024) except Exception,e: store_data(data) break If this is not okay, what would you change? (And yes, I know there are edge'ish cases were I'd miss some data here)
- hrjet 13y agoIt would be okay as an optimisation in a very hot spot in your code, after a lot of benchmarking. I would frown upon using that as a general practice.
- zanny 13y agoExceptions for control flow are a slippery slope. in the socket case, it would be better to parse a terminator frame from your recieved data and just do socket.close() like normal than to hack a timeout or internal socket close, or negociate a fixed transmission length in advance. This is on the basis code like this is hard to debug, moreso than any glorious exception free master style. You aren't guaranteed to have recieved your entire transmission in your exception block because you are catching any recieve exception. You do want exception handling here, but not as control flow, you want it as damage control if you get an unexpected early termination, not when you get desired behavior.
- btilly 13y agoThis is NOT OK. You're swallowing all exceptions and assuming that they are your special case. The fix is to have a specific nonambiguous name for your exception, so that other error conditions still work properly. As examples consider the StopIteration and GeneratorExit exceptions from Python's standard library. (See http://docs.python.org/2/library/exceptions.html http://docs.python.org/2/library/exceptions.html for a list of built-in exceptions.)
- dsego 13y agoExceptions aren't a debugging tool. You should use asserts for debugging. These asserts are then disabled via compile switches for the production version. Or you can write unit tests.
- betterunix 13y agoI think Common Lisp has a good approach with its restarts system. I try to write something to a file but there is not a enough disk space? How about telling the user, then invoking a restart when the user says, "There is more space available!" and continuing execution as if nothing went wrong? The problem with exceptions is that there is no way to recover from them in most languages, because the exception handler is found by unwinding the stack. What I do not like about the "check return values" approach is: 1. It means that client code must understand how to handle error conditions. No disk space? Well whoever called the top-level function that invoked write needs to figure out what to do if there is any chance of recovery. It is a maintenance headache that can quickly accumulate bugs. 2. In both Java and C++ there are functions that cannot return values: constructors, and in C++ destructors. No, it is not acceptable for a program to crash just because a constructor call failed. No, it is not any better to have every class have a special variable that indicates that the constructor failed. No, having empty constructors is not the answer, and it is certainly not going to help with destructor calls (the C++ standard library actually requires some destructors to silently fail because of the issues with error reporting).
- asgard1024 13y agoRestarts are perfect! This bothered me greatly when I read Code Complete. It doesn't discuss error handlers (which restarts are a form of) in error handling chapter at all. I really wish more people knew about this option. Maybe it would even get into mainstream languages.
- michaelwww 13y ago> [as an alternative to exceptions] "It is true that what should be a simple 3 line program often blossoms to 48 lines when you put in good error checking, but that's life Was he being serious? I'd rather not wade through all that error checking cruft to see normal flow. Exceptions (or goto) don't create buggy hard to read programs, people do.
- crazygringo 13y agoExcept that error handling is part of normal flow. Wishing it wasn't, or just laziness, leads to bugs. It's not cruft, it's integral. That's the whole point.
- michaelwww 13y agoI disagree. Building an error checking scaffolding around every possible and unlikely error obscures the expected flow of control. If I checked for every possible error condition before I started the engine of my car in the morning it would take me an hour to get out of the driveway. I'd much rather have the car tell me when something was wrong and not start. If the system you are using already throws exceptions, you might as well use them too.
- um304 13y agoExactly my point. In my experience, exceptions have helped me significantly reduce my code, increase readability and quicken bug tracking.
- olegp 13y agoIf 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 where, while making your program more robust and resilient to errors. We've found this to be the case time after time at https://starthq.com https://starthq.com, which runs on Node but uses fibers via https://github.com/olegp/common-node https://github.com/olegp/common-node.
- wvenable 13y agoI think most people have been brain damaged by checked exceptions in Java. It comes with this expectation that need to have to catch block in almost every single function. But if you do that, you've just recreated error codes and multiple returns! Exceptions are meant be thrown frequently and caught very infrequently. Catch in the few places where recovery is possible and where you can log the error. That's it.
- betterunix 13y ago"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.
- Chris_Newton 13y agoIf 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.
- FreeFull 13y agoSome Haskellers treat exceptions as something that you only use if the program has entered an unrecoverable condition and needs to crash.
- deletes 13y agoIsn't that what exceptions are used for in any language?
- ghc 13y agoCertainly not. In Python, for example, exceptions are used to handle exceptional conditions. Here's an example. Say I'm writing a function and I have a dictionary I need to access values from often. Now say that 85% of the time the key I need is in the dictionary, but 15% of the time it is not. I could do this: if key in my_dict: execute_operation(my_dict[key]) else: pass So that if the key is not in the dictionary I do nothing. But this can be expressed more clearly like this: try: execute_operation(my_dict[key]) except KeyError: pass This is considered to be clearer because it expresses the fact that the key should be in the dictionary, but in some cases is not. And interestingly, performance reflects this. The first method is more efficient in cases where the key is usually not in the dictionary. The second method, on the other hand, is more efficient when the key is usually present in the dictionary. So, in summary, exceptions are used in Python to represent cases where successfully executing the code within the try block is normal, but exceptional conditions could once in awhile occur. Almost all uses of exceptions occur for conditions where the program should not crash. When the program should crash we just let the exception bubble up to the top level where we gracefully shut down the program and call an exit function.
- thwarted 13y agoUnfortunately, the second one eats an uncaught KeyError raised deep inside execute_operation, not just my_dict[key] failing. Which may be easy to overcome if you wrote all the code and libraries that execute_operation calls, but nearly impossible otherwise.
- 13y ago
- dicroce 13y agoI don't use exceptions for normal flow. I use them for errors. While it is true that exceptions create innumerable code paths through a function, RAII makes that manageable. If you're not taking advantage of RAII in your C++ code, you might as well be writing C code.
- BIackSwan 13y agoExactly fits the philosophy of Google Go - http://blog.golang.org/error-handling-and-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 handled all the error cases (that you care about) in your code. EDIT - Obligatory nitpick accepted. ;)
- rancor 13y agoThat was my first thought as well, glad to see that Joel is on the same page. After writing a fair amount of Go recently, I've come to believe that usage of explicit error returns only appears to increase code density. In reality, it exposes the complexity of correct error handling and forces you to factor your error handling logic accordingly, rather then letting you sweep it all into a few top level handlers.
- chimeracoder 13y ago<nitpick type="obligatory">The name of the language is Go, not "Google Golang"</nitpick> 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 voluntarily. Returning errors makes code much more readable, and it also reduces the risk of code suddenly failing because of an exception that got thrown somewhere that you can't even find easily. Go also gets a more subtle point correct: errors are interfaces, not types, which means that you can use any type as an error, as long as it supports the "Error() string" method. This is irrelevant 99% of the time, but I've seen a few pieces of code which utilize this feature very effectively.
- mdkess 13y agoI have to say, I much prefer people referring to it as Golang, otherwise it is impossible to search for.
- zzzcpan 13y agoSure, exceptions are bad most of the time. But sometimes they are really useful, like in heavily recursive code, i.e. recursive parsers. Catching exceptions in a single top level function and throwing in every other one makes code much cleaner, since you don't have to propagate and handle errors on every function call and you have single exit point on top level function anyway.
- beagle3 13y agoEveryone 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 this context. If it does throw an exception, some people complain that "of course it'e expected that file won't be there sometimes! that's not exceptional". If it doesn't throw an exception, some people complain that "we tried to open a file, but didn't succeed, of course that's an exception". But if you want to avoid an exception in this case, you'll need to check for existence before opening (LBYL "look-before-you-leap"), and get into a race condition (TOCTOU "time-of-check,time-of-use"), which is really bad. So it very often happens that you are forced by your ecosystem to use exceptions for normal control flow. Claims that you can only use it for "exceptional / unexpected" tend to be incompatible with a project in which you do not develop/control all of the library you use to your strict standard of exceptionalness.
- wvenable 13y ago> you are trying to open a file for reading. The file does not exist. Is this exceptional? It's actually handy to have an API that includes both situations. For example, parse and tryParse in C#. Parse will trigger an exception if it fails but tryParse will not. When used in your code, this actually documents what the programmer is expected. If an open and tryOpen operations existed, you would know whether or not the programmer expects the file to exist or not.
- protomyth 13y agoGoing 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) something like this (it has been 20yrs) init data structure S open file A loop read line from A process line and add to S next catch EOF close A return S catch file-not-found return empty S any mistakes are my memory not my old professor
- hacknat 13y agoThis old gem, huh?
- dsego 13y agoThis is a problem of architecture. Should you mix error handling with application logic? You have to handle errors somewhere, but they pollute the otherwise clean problem-solving code. One solution is to just let the exceptions bubble up and the other is to handle them immediately. If you let them bubble up, you can have a separate module that can do the right thing, notify the user, restart the app or something else. This way, the error handling is somewhat cleanly separated into its own thing. Sometimes you want to handle errors immediately, because only the code where the exception happens, knows how to deal with it. This is where return codes might be better. A lot of it depends on the desired behaviour. Sometimes you want the software to immediately exit or restart if there is a critical exception, sometimes you can safely ignore errors if real-time experience is more important. Sometimes all you want is to log the exception and/or notify the user. Does anyone have experience with aspect-oriented programming and does it help solve any of these problems?
- samatman 13y agoWhile I'm not familiar with the Erlang paradigm that inspires it, the dire[1] library in Clojure is designed to decomplect error handling from code that may generate an error. I like this approach a lot. [1]: https://github.com/MichaelDrogalis/dire https://github.com/MichaelDrogalis/dire
- spo81rty 13y agoYou can make methods easily return multiple values in C# with Tuples. They are very handy.
- mattmanser 13y agoFor the sake of the sanity of your future co-workers, I really hope you are kidding.
- MichaelGG 13y agoFor extremely low values of "easily". C#'s lack of pattern matching and tuple syntax makes dealing with tuples in C# a complete pain in the ass. It's ugly, annoying, code, in general. P.S. You can do this in pretty much any language. Just define pair, triplet, etc.
- mwsherman 13y agoI 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 are not going to write code to address all 50 possibilities from the start. Instead, we are going de facto to wait and see what fails in the real world, and address them as we discover them, because we value our time. We make an economic distinction between a 1-in-1,000 problem, and a 1-in-1,000,000,000 problem. Is this un-robust? Yes. Are we allowing exceptions to be a control-flow catch-all? Yes. Would a Go-like approach of simply returning error conditions reduce bugs? Probably. But it’s important to recognize programmers’ ‘revealed preference’ for exceptions.
- crazygringo 13y ago50 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 errors from memory allocation failing, or even possibly failure to write bytes to disk, depending on the situation (e.g., if those fail, you've got bigger problems to worry about than your error handling -- and it's not like exceptions are probably helping you to recover anyways). But for stuff like network communication, writing to databases, etc., you had definitely better be addressing all error possibilities from the start, because these things fail all the time.
- mattmanser 13y agoNonsense. 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...
- stan_rogers 13y agoThe discussion around that time was interesting (though distributed around the blogosphere); Ned Batchelder had recently argued the opposite (and updated the argument specifically to take Joel's article into account)[0][1]. It was at about that same time that Damien Katz was becoming firmly convinced that Erlang and crash-only behaviour would be the way to go in designing CouchDB[2][3]. (Both Damien and Ned were at Iris/Lotus working on Notes and Domino, and I was a Domino dev at the time.) [0] http://nedbatchelder.com/blog/200310/joel_on_exceptions.html http://nedbatchelder.com/blog/200310/joel_on_exceptions.html [1] http://nedbatchelder.com/text/exceptions-vs-status.html http://nedbatchelder.com/text/exceptions-vs-status.html [2] http://damienkatz.net/2004/08/crash-only-software.html http://damienkatz.net/2004/08/crash-only-software.html [3] http://damienkatz.net/2004/09/crash-only-software-revisited.html http://damienkatz.net/2004/09/crash-only-software-revisited....
- shizamhn 13y agoAs a side note, at least in Objective C, blocks are a great way to return multiple values (including errors) and deal with them immedately.
- mamcx 13y agoThe advantage of exceptions is the ability to pass back the error and make mandatory dealing it (even if ignoring). Exist a way to have both styles, cleanly? I have thinking in how could look a language where objects, like in UNIX pipes, have stdout & stderr, and if stderr is consumed (returning codes) the exception is handled, but if stderr is not consumed, then raise it?
- deleted 13y ago[deleted]
- jeffdavis 13y agoI think there is reasonable consensus about some software engineering best practices: 1. Avoid action-at-a-distance and side-effects that are hard to reason about. 2. Use immutable objects and values rather than references and pointers. 3. Avoid intricate control flow with many branches. 4. Greater isolation of processes/threads (actor model). 5. Use systems and platforms with simple and strong guarantees (e.g. ACID) that are easy to reason about. Special cases and nuances to the underlying platform should be avoided. 6. Use tools with good support for static analysis (e.g. a good type system with a compiler that can understand it). Exceptions seem to violate #1, #3, and #6. These rules only apply to software engineering; that is, the reliability, robustness, long-term maintainability, and total project development cost (including maintenance and support). Of course, there are other considerations, such as: performance, the time to achieve the minimum viable product, how much developers like the tools, how "hackable" it is, or utility for research purposes. These other concerns may be a good reason to violate the above rules.
- deleted 13y ago[deleted]
- losethos 13y agoYou would say that, wouldn't you? There are dumb people. I've seen dumb things. My exceptions, however, are excellent. I'm tried of talking to dishonest people. It gets really old. It's funny -- at osdev, there's a guy named RDOS. All his answers support his position -- it's really pathetic. Just walk away.
- detrino 13y agoForcing a function call to handle an exceptional condition when it doesn't know how doesn't really add anything but bloat to your source code (violating DRY). It's interesting the author mentions goto, because without exceptions goto is often the most reasonable but more error prone way to handle cleanup. C++ solves this using RAII, if you need something to run before the end of the scope, stick it in a destructor. This is very easy and far more composable.
- mtdewcmu 13y ago> This is ugly and annoying but it's better than getting magic unexpected gotos sprinkled throughout your code at unpredictable places. The proper analogy would be not GOTO, but COMEFROM, no? A catch block is basically a COMEFROM. :)
- alexlamsl 13y agohttp://nodejsreactions.tumblr.com/post/59877403633 http://nodejsreactions.tumblr.com/post/59877403633
- rwallace 13y agoMost of the discussion about exceptions tries to think about it in abstract terms, which doesn't work. The point of exceptions is a very concrete one: often the code that runs into an error and the code that handles it are separated by ten levels of function calls. Without exceptions, the logic for detecting and passing on the error has to be duplicated in every function in every one of those levels. Exceptions are arguably the one feature of C++ that isn't just syntax sugar over C, the one feature that makes the language fundamentally more expressive: they reduce a certain kind of code complexity from O(N) to O(1).