notes-computer-programming-programmingLanguageDesign-prosAndCons-exceptions

toread

http://web.archive.org/web/20120423062420/http://www.research.att.com/~bs/3rd_safe.pdf


" In Go, the established convention is that functions return errors; they don't panic. If a file isn't found, os.Open returns an error; it doesn't panic. If you write to a broken network connection, the net.Conn's Write method returns an error; it doesn't panic. These conditions are expected in those kinds of programs. The operation is likely to fail, and you knew that going in, because the API designer made it clear by including an error result.

On the other hand, there are some operations that are incredibly unlikely to fail, and the context is such that there is no way to signal the failure, no way to proceed. These are what panic is for. The canonical example is that if a program evaluates x[j] but j is out of range, the code panics. An unexpected panic like this is a serious bug in the program and by default kills it. Unfortunately, this makes it difficult to write robust, defensive servers that can, for example, cope with the occasional buggy HTTP request handler while keeping the rest of the server up and running. To address that, we introduced recover, which allows a goroutine to recover from a panic some number of call frames below. However, the cost of a panic is the loss of at least one call frame from the stack. We did this intentionally. To quote from the original mail: "This proposal differs from the usual model of exceptions as a control structure, but that is a deliberate decision. We don't want to encourage the conflation of errors and exceptions that occur in languages such as Java."

The post I mentioned at the start asks "why is an array out of bounds any more cause for panic than a bad format string or a broken connection?" The answer is that there is no in-band way to report the error during the evaluation of x[j], while there is an in-band way to report an error in a bad format string or a broken connection. (The format string decision is interesting by itself but orthogonal to this discussion.) "

" The error returns used by Go are admittedly inconvenient to callers, but they also make the possibility of the error explicit both in the program and in the type system. While simple programs might want to just print an error and exit in all cases, it is common for more sophisticated programs to react differently depending on where the error came from, in which case the try + catch approach is actually more verbose than explicit error results. It is true that your 10-line Python program is probably more verbose in Go. Go's primary target, however, is not 10-line programs. "

" "I personally find it a lot easier to do a line-by line "can this throw a non-hardware error (out of memory, etc.)" inspection than decoding what's effectively a giant if/then error handling jump table."

True. However if you're coming to code that's already been written, you have to do this analysis all over again since the absence of recovery code could mean "Somebody thought about this and decided no recovery code was necessary" or it could mean "Nobody thought about this." Few people add the comment like "it's okay if this throws an exception because xyz" to their code.

Error code paradigm is much wordier - checking error codes everywhere - but it's obvious when somebody forgot to check an error code. And in the cases where somebody wants to ignore an error code, they usually add a little comment "ignoring error code because xyz". "

" Here's a snippet from a book on C# programming, taken from the chapter on how great exceptions are.

    try {
      AccessDatabase accessDb = new AccessDatabase();
      accessDb.GenerateDatabase();
    } catch (Exception e) {
      // Inspect caught exception
    }
    public void GenerateDatabase()
    {
      CreatePhysicalDatabase();
      CreateTables();
      CreateIndexes();
    }
    Notice how much cleaner and more elegant [this] solution is. 

Cleaner, more elegant, and wrong.

Suppose an exception is thrown during CreateIndexes?(). The GenerateDatabase?() function doesn't catch it, so the error is thrown back out to the caller, where it is caught.

But when the exception left GenerateDatabase?(), important information was lost: The state of the database creation. The code that catches the exception doesn't know which step in database creation failed. Does it need to delete the indexes? Does it need to delete the tables? Does it need to delete the physical database? It doesn't know.

So if there is a problem creating CreateIndexes?(), you leak a physical database file and a table forever. (Since these are presumably files on disk, they hang around indefinitely.)

Writing correct code in the exception-throwing model is in a sense harder than in an error-code model, since anything can fail, and you have to be ready for it. In an error-code model, it's obvious when you have to check for errors: When you get an error code. In an exception model, you just have to know that errors can occur anywhere.

In other words, in an error-code model, it is obvious when somebody failed to handle an error: They didn't check the error code. But in an exception-throwing model, it is not obvious from looking at the code whether somebody handled the error, since the error is not explicit.

Consider the following:

Guy AddNewGuy?(string name) { Guy guy = new Guy(name); AddToLeague?(guy); guy.Team = ChooseRandomTeam?(); return guy; }

This function creates a new Guy, adds him to the league, and assigns him to a team randomly. How can this be simpler?

Remember: Every line is a possible error.

What if an exception is thrown by "new Guy(name)"?

    Well, fortunately, we haven't yet started doing anything, so no harm done.What if an exception is thrown by "AddToLeague?(guy)"?
    The "guy" we created will be abandoned, but the GC will clean that up.What if an exception is thrown by "guy.Team = ChooseRandomTeam?()"?
    Uh-oh, now we're in trouble. We already added the guy to the league. If somebody catches this exception, they're going to find a guy in the league who doesn't belong to any team. If there's some code that walks through all the members of the league and uses the guy.Team member, they're going to take a NullReferenceException since guy.Team isn't initialized yet.

When you're writing code, do you think about what the consequences of an exception would be if it were raised by each line of code? You have to do this if you intend to write correct code. "

still seems to me that a proper exception-highlighting IDE could make this just as easy..

" Here's some imaginary error-code-based code. See if you can classify it as "bad" or "not-bad":

BOOL ComputeChecksum?(LPCTSTR pszFile, DWORD* pdwResult) { HANDLE h = CreateFile?(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE hfm = CreateFileMapping?(h, NULL, PAGE_READ, 0, 0, NULL); void *pv = MapViewOfFile?(hfm, FILE_MAP_READ, 0, 0, 0); DWORD dwHeaderSum; CheckSumMappedFile?(pvBase, GetFileSize?(h, NULL), &dwHeaderSum, pdwResult); UnmapViewOfFile?(pv); CloseHandle?(hfm); CloseHandle?(h); return TRUE; }

This code is obviously bad. No error codes are checked. This is the sort of code you might write when in a hurry, meaning to come back to and improve later. And it's easy to spot that this code needs to be improved big time before it's ready for prime time.

Here's another version:

BOOL ComputeChecksum?(LPCTSTR pszFile, DWORD* pdwResult) { BOOL fRc = FALSE; HANDLE h = CreateFile?(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h != INVALID_HANDLE_VALUE) { HANDLE hfm = CreateFileMapping?(h, NULL, PAGE_READ, 0, 0, NULL); if (hfm) { void *pv = MapViewOfFile?(hfm, FILE_MAP_READ, 0, 0, 0); if (pv) { DWORD dwHeaderSum; if (CheckSumMappedFile?(pvBase, GetFileSize?(h, NULL), &dwHeaderSum, pdwResult)) { fRc = TRUE; } UnmapViewOfFile?(pv); } CloseHandle?(hfm); } CloseHandle?(h); } return fRc; }

This code is still wrong, but it clearly looks like it's trying to be right. It is what I call "not-bad".

Now here's some exception-based code you might write in a hurry:

NotifyIcon? CreateNotifyIcon?() { NotifyIcon? icon = new NotifyIcon?(); icon.Text = "Blah blah blah"; icon.Visible = true; icon.Icon = new Icon(GetType?(), "cool.ico"); return icon; }

(This is actual code from a real program in an article about taskbar notification icons, with minor changes in a futile attempt to disguise the source.)

Here's what it might look like after you fix it to be correct in the face of exceptions:

NotifyIcon? CreateNotifyIcon?() { NotifyIcon? icon = new NotifyIcon?(); icon.Text = "Blah blah blah"; icon.Icon = new Icon(GetType?(), "cool.ico"); icon.Visible = true; return icon; }

Subtle, isn't it. "

" My point isn't that exceptions are bad. My point is that exceptions are too hard and I'm not smart enough to handle them. (And neither, it seems, are book authors, even when they are trying to teach you how to program with exceptions!)

(Yes, there are programming models like RAII and transactions, but rarely do you see sample code that uses either.) " -- http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx

hmm this seems more problematic to mee..

i guess the problem is that, say that multiple things within the 'try' block can both throw the same type of exception. Now you can write the exception handler (the 'catch' block), having in mind the first thrower, but forgetting that there was something else in the same code that can throw that type of exception. It's hard for a later reader to distinguish your neglect of the second case from your having thought about it and decided that both of them can be handled the same way. With explicit error code return it is easy to see if you thought about that (did you check the error code in the second case?)

This is a problem because of side-effects; you don't just care what the function returned to you in the end (in both cases, nothing), but you also care how far it got before it threw, because there are side effects.

Jasper's LABELS/footnotes/wrappers idea could help here, if done right. The idea would be, when there is an imperative/ordered sequence of side-effectful commands, rather than allow the coder to say, 'if an exception arises anywhere in here, do this', instead allow them to label each line with a handler. This is better than exceptions because it forces the exception checking to be localized to each function call. It is better than error codes, b/c:

The Go idea of resuming after an exception by dropping the lowest frame in the call stack also seems like a good one.. except that this messes with the usefulness of exception resuming as a non-exceptional control flow technique. Which was their intention. Maybe have Errors and Exceptions.


" Please, please, PLEASE could anyone posting about exceptions in C++ read Bjarne Stroustrup's "Appendix E" note on exception handling? It has been present online for more than 4 years, at the location http://www.research.att.com/~bs/3rd_safe0.html. If someone posts and obviously hasn't read this, they aren't doing their reputations any favours.

And a lot of the people posting on this topic have clearly *not* read this, since they are coming up with some common misconceptions that Bjarne's document addresses:

Misconception #1: Constructors shouldn't throw exceptions. Misconception #2: Exception safety requires rollback semantics. Misconception #3: RAII has anything to do with rollback semantics. Misconception #4: You should always use construct-and-swap when copying objects.

Nope, all wrong. Personally, when I interview C++ programmers, I won't employ anyone in a non-junior position who hasn't at least a passing familiarity with:

All you have to do is print off a document that is *free*, and spend a few days going over it. If you can't or won't do that, I certainly don't want anything to do with your code! If you have read the document, and still prefer error codes to exceptions, that's at least a position I can have some intellectual respect for.

The deterministic invocation times of destructors in C++ makes exception handling easy. You just need to follow a few simple rules, the main ones being:

Now, if you prefer error codes to exceptions, then that's up to you. But please don't write on which you prefer in a public forum without at least having read what the creator of the C++ language has to say on the subject! It won't impress anyone who has done some background reading. "

--

upvote

zzzeek 45 days ago

link

yes I've read that and their reason is "it results in convoluted code" - which is not at all my experience, programming in Java and Python for many years, it's worse in Java for sure due to the heavy emphasis on checked exceptions, but in Python they are a dream. "It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional." also not true in my experience. I really disagree with the notion of hobbling a language just to prevent beginners from writing bad code. Beginners will always write bad code no matter what. I'm not a beginner, and I really don't need to be denied useful tools just because beginners will misuse them - I mean we're talking about improving on C/C++ for chrissakes, in the hands of a beginner those languages are like nuclear weapons. This particular FAQ entry makes it seem very much like the authors have just not seen exceptions used effectively, which I find kind of astonishing.

I watched Bruce Eckel's talk at Pycon, "Rethinking Errors - Learning from Scala and Go" (http://us.pycon.org/2013/schedule/presentation/52/) and I was so ready to be converted. But his arguments were pretty unconvincing.

Can't someone just make this case convincingly?

--

upvote

MetaCosm? 45 days ago

link

I think a lot of people HAVE made the case convincingly, at least well enough for me. I have worked in major C++ shops that ban the use of exceptions (and enforce it).

Just to add another person who regrets exceptions to the big pile, http://250bpm.com/blog:4 (The ZMQ Author)

"Thus, the error handling was of utmost importance. It had to be very explicit and unforgiving.

C++ exceptions just didn't fill the bill. They are great for guaranteeing that program doesn't fail — just wrap the main function in try/catch block and you can handle all the errors in a single place.

However, what's great for avoiding straightforward failures becomes a nightmare when your goal is to guarantee that no undefined behaviour happens. The decoupling between raising of the exception and handling it, that makes avoiding failures so easy in C++, makes it virtually impossible to guarantee that the program never runs info undefined behaviour.

With C, the raising of the error and handling it are tightly couped and reside at the same place in the source code. This makes it easy to understand what happens if error happens..."

upvote

pjmlp 45 days ago

link

That is a problem with the way exceptions work in C++, not with the exceptions as concept.

C++ exception's design suffer from being added to the language in the last years of the language's design and having to support the possibility of being turned off if desired.

This, coupled with the manual resource management of the language is with lead to some of the issues with exceptions in C++.

Not all languages with exception's support suffer from the same problems.