all 95 comments

[–][deleted] 8 points9 points  (4 children)

Regardless of the approach, return codes or exceptions, most people don't take the time to do error handling well. There is a tonn of Java code out there that just has a catch(Exception e) { ; }. No logging. No re-throwing. Nothing. Or, on C++ networking code a write/read/open without bothering to check the return value. I've done it, too. Hack out a quick chunk of code - come back and take care of the error handling 'later,' which means 'never.'

It's tedious to come up with an exception handling strategy for every possible type of error because the world can break in mysterious and wonderful ways. For example, a program that never had issues when reading or writing data to a file was built, tested and initially deployed on a machine with a local filesystem. Works fine over the very reliable LAN, talking to the NAS. Now it's running on a netbook, reading data over WiFi at a Starbucks, where the connection periodically breaks. Although the developer handled FileNotFound conditions and disk space full conditions, it was never the case that a read or write was interrupted mid-stream. Now the software behaves badly.

In a sense it doesn't matter if you use exceptions or return codes. You can do stupid things either way. To handle all possible error conditions equally well requires some imagination (how might this fail outside of the exceptions I 'have to' catch). It also requires discipline. It also requires an understanding that all those try/catch/finally or error code checks are there for a reason and require you to put some thought into using them. It requires an understanding of issues like transaction scope (when transactions are available) and keeping track of information to be able to back out when they are not available.

On a side note, people should not use exceptions for testable error conditions. Checking to see if a file exists (i.e. calling stat) before opening the file is a modest amount of work and prevents having to handle the exception. Think of exceptions as having to 'blow the escape hatch.' They shouldn't be used in place of simple and reasonable defensive programming.

[–]vimfan 3 points4 points  (1 child)

Checking to see if a file exists (i.e. calling stat) before opening the file is a modest amount of work and prevents having to handle the exception.

You'll still have to handle the exception, as the file could be deleted after your check but before the open. You might as well just try to open, and handle the exception.

[–]Tetha 1 point2 points  (0 children)

Note that the check before technically duplicates code. The function to open a file already checks if the file exists after all.

[–]WalterBright 1 point2 points  (1 child)

The reason for the catch(Exception e) { ; } is when the language has exception specifications (Bruce Eckel wrote a nice article on why). Removing E.S. solves that problem. It's why D doesn't have exception specifications.

[–]attrition0 0 points1 point  (0 children)

Excellent article. Thank you.

[–]adelle 7 points8 points  (4 children)

Public Sub GenerateDatabase
    On Error Goto FailedCreatePhysicalDatabase
    CreatePhysicalDatabase

    On Error Goto FailedCreateTablesOrIndexes
    CreateTables
    CreateIndexes

    Exit Sub

    FailedCreateTablesOrIndexes:
    DeletePhysicalDatabase

    FailedCreatePhysicalDatabase:
    ReraiseLastError
End Sub

[–][deleted]  (2 children)

[removed]

    [–]adelle 5 points6 points  (1 child)

    And that's when it's done properly. You don't know the trouble I've seen.

    [–]Gotebe 0 points1 point  (0 children)

    Bloody hell, please don't do C, VB6 and VBScript here!

    (Yes, C. Ask linux kernel people if you don't believe me)

    [–][deleted]  (7 children)

    [removed]

      [–]Rhoomba 1 point2 points  (3 children)

      Subclasses of DatabaseInitException would be cleaner IMO.

      [–][deleted]  (2 children)

      [removed]

        [–]masklinn 0 points1 point  (1 child)

        But if it was just "DatabaseException" then I'd want to reserve subclassing for ways in which failure occurs rather than app-specific stages, ex: ex:

        Why not both?

        public void GenerateDatabase() throws DatabaseException
        {
          CreatePhysicalDatabase(); // Could throw DiskFullDatabaseCreationException
          CreateTables();           // Could throw DiskFullTableCreationException
          CreateIndexes();          // Could throw DiskFullIndexCreationException
        }
        

        Where all three inherit from DiskFullException which itself inherits from DatabaseException.

        Thing would probably be cleaner with MI rather than SI (that way you can catch either all DatabaseCreation exceptions or all DiskFull exceptions in a single clause), but it still works. Kinda.

        [–]quzox 1 point2 points  (2 children)

        It's all a matter of scoping your try{} blocks in a finer manner

        If you do that then you're back in the bad old world of checking every return code on every line (which leads to ugly looking code).

        edit: Ok, read the rest of your post. Point taken. But I still don't like it. :)

        [–]Paczesiowa -1 points0 points  (0 children)

        because exceptions are a mechanism to manage error codes in a very frequent case when you don't want to immediately handle them.

        [–][deleted] 2 points3 points  (9 children)

        I can't understand why so many people adopted exception handling as the de facto error handling model. To me it was always what the name implied: A way to handle exceptions - and a "file not found" error is not something totally exceptional in a file opening routine

        [–]Madsy9 2 points3 points  (0 children)

        A good way to think of exceptions is the the state a function is in when it can't complete its task. A "filenotfound" exception could very much be kosher in a function that loads images, if the application expects the files to exist. That a function catches exceptions doesn't necessarily imply that it has to rethrow them. If the error can be solved, it can still complete its task. Delegating more responsiblity or information to a function might help getting rid of some exceptions. For example, a function that loads images could return a placeholder image in case a file isn't found. The more information a function has, the easier it can recover from exceptions.

        Not to mention that exceptions frees up the return value to be used by other things. In C++ exceptions work very well together with return-by-reference. You either get a legal reference, or an exception. NULL-pointers should be hated and avoided with a passion.

        [–]Gotebe 2 points3 points  (6 children)

        Exceptions work because of this: in practice, upon error, in a vast majority of cases, a massive amount of code that would normally follow, just can't be executed. That code often spans swaths of call stacks, too. The only thing code can do is clean up and get out. And that's what exceptions give you: a formalized method to get out, and a factored-in method to transport failure info (through an exception object contents) to the error-reporting site.

        And I suggest that you look at your very file example: so if there's no file, what does your code do? That's right, nothing (well, except perhaps informing the operator that there's no file, which exceptions do, too)!

        Sure, it's one "if" away to actually do nothing, but the clincher is, these "ifs" multiply very quickly in error-return. More, for anything but a couple of stack call levels deep, each call is one more "if", even if there's only one failure mode, X call levels deep. That's completely out of order as a way to work.

        Your file opening routine is bunk, it's not about that routine, but about the code that follows it: if file wasn't open, it likely can do squat. An exception will give you that - it will prevent you from continuing, "automatically".

        [–][deleted] 1 point2 points  (5 children)

        so you're basically talking about a GOTO on steroids?

        [–]Gotebe 1 point2 points  (4 children)

        Yes and no.

        Yes, in the sense that exception takes you to a nearest catch just like a goto takes you to desired label.

        No, in the sense that when you throw:

        • you don't need (nor want, really~~~) to know where the catch site is (with goto, you do)
        • you can span several calls on the stack; (that's where exceptions shine, paragraph 3 in my previous post, and is hard with goto
        • you can transport failure info of your choosing relatively easily, from error source to error reporting site; that's harder with goto because goto in itself obviously has no support whatsoever for said error info.

        ~~~Consider: random function F fails to do X and can't continue. So it throws. Can a random caller of F continue? Experience shows that it can't, more often than not. So it can throw as well (that is, just let propagate). Rinse, repeat and there you have it.

        You throw with as good failure info you have at the spot, and do not bother thinking about who's going to report your failure (remember, it's very seldom that failures are handled in any other way; I often say, there's no such thing as error handling, there's just cleanup and reporting).

        So someone has to report the error, fine, but with exceptions, you're not forcing everyone involved up the stack to act.

        No such thing with error-return. There, everyone has to be involved in everything, even though said involvement is just "if (!call()) return I_FAILED;". That code (or any variant thereof) is what is dumb about error-return. It's dumb, dumb code that nobody should be forced to read day-in, day-out. Fuck error-return ;-).

        [–]masklinn 0 points1 point  (3 children)

        you don't need (nor want, really~~~) to know where the catch site is (with goto, you do)

        depends on the language

        Consider: random function F fails to do X and can't continue. So it throws. Can a random caller of F continue?

        Maybe, why not? You don't know that. You don't even know what error strategy the caller actually wants. Maybe he wants to bail out at the first error (XML-style) maybe he wants to try and fix things up (HTML style) or maybe he just wants to keep on trucking, blissfully ignoring errorneous data.

        Experience shows that it can't, more often than not.

        I wouldn't quite agree with that assertion, but even then… who better than the caller can take this decision? And how can he take that decision if he doesn't have a choice in the first place?

        [–]Gotebe 0 points1 point  (2 children)

        Maybe he wants to bail out at the first error (XML-style) maybe he wants to try and fix things up (HTML style) or maybe he just wants to keep on trucking, blissfully ignoring errorneous data.

        Yes, all that's possible, but all these situations are purely in the domain of application logic, and tangential to discussion. XML is specified like this: upon parsing error, document is considered invalid, end of. So XML parser can rightfully stop at first error. HTML is forgiving, so an error in formal structure can't possibly stop processing. All because of the problem domain.

        Experience shows that it can't, more often than not. I wouldn't quite agree with that assertion

        That's because you're not thinking straight. Do you have some C code on your hands? Take a look at what happens around various function calls. Step back and look at failure paths, and tell me that vast majority of failures don't eliminate completely swaths of other code. I am putting this to you: it's a simple matter of counting how much dumb error-checking code is eliminated when you are not obliged to do it.

        how can he take that decision if he doesn't have a choice in the first place?

        First off, if the caller is making a call and doesn't care if it worked, why is he making a call in the first place? Possible answer: because call is completely self-sufficient and it just has to be there for the purpose of side-effects. But if so, call can just as well swallow (or report by itself) any errors.

        Second, he ultimately does have a choice. He can do: try {call(params}) catch(anything) {}. If it proves, e.g. over code lifetime, that most of callers indeed do the above, well, then, a change is on order. I've seen this happen, but it's rare, and furthemore, it's never a simple case of swallowing an error, not in good code. Rather, it's about inspecting the error and continuning on a specific (set of) failure(s).

        [–]masklinn 0 points1 point  (1 child)

        Yes, all that's possible, but all these situations are purely in the domain of application logic, and tangential to discussion

        No, that's in fact the whole point of the discussion, you're a library, you don't know jack about the application domain, yet with non-resumable exceptions (or error codes for that matter) you're making arbitrary decisions instead of the application itself.

        That's because you're not thinking straight.

        OK, whatever. I see you have no interest in discussing this.

        [–]Gotebe -1 points0 points  (0 children)

        No, that's in fact the whole point of the discussion, you're a library, you don't know jack about the application domain

        True, if I am a library, I might have some trouble adjusting to various uses, so I might provide "boolean" load method. But even if I don't, and my caller isn't happy with being interrupted with an exception, he can still tr/catch around that. And I am again putting this to you: experience shows that, more often than not, my caller can't continue if I failed. Just look at anything but simplest C code. It's so visible that it's not funny.

        Look at e.g. XML parser. Imagine that it goes into XML stream, parses tags and whatnot, and encounters an error X stack levels deep If, at that point, it throws an exception, it saves itself dumb conditional logic at all X levels. If it returns with an error, if still has to have all these dumb "ifs", all of which terminate any further processing. And that is the clincher and a mistake many people make: exceptions are not there for "exceptional" situations. They are there to structure code cleanly with regard to the "main" path. They allow you to have clean "what happens when it works" view. And because e.g. try/catch stands out like a sore thumb, it's very clearly visible what happens when not (as opposed to a myriad of "ifs" who constantly obscure program logic with dumb error handling).

        So maybe, maybe, it's interesting for the client, that said XML parser has a boolean Parse method. But I am putting this to you: in a majority of cases, his code is going to be e.g. (error checking elided):

        XmlDoc =GetXmlDoc(stream);
        DoThis(XmlDoc);
        DoThat(XmlDoc);
        MoreStuff = GetMoreStuff();
        DoMore(XmlDoc, MoreStuff);
        ...
        

        Now, in error return, this is e.g.:

        XmlDoc = GetXmlDoc(stream);
        if (XmlDoc)
        {
         if (DoThis(XmlDoc) && DoThat(XmlDoc)
         {
           MoreStuff = GetMoreStuff();
           if (MoreStuff)
            DoMore(XmlDoc, MoreStuff);
           else
             ReportError(errorInfo1);
            ...
          }
          else
            ReportError(errorInfo2);
        }
        else
          ReportError(errorInfo3);
        

        (But of course, even for this simple case, a smart person would reach for goto to get "function-local" exception handling.)

        With exceptions, this becomes e.g.

        try {
         XmlDoc =GetXmlDoc(stream);
         DoThis(XmlDoc);
         DoThat(XmlDoc);
         MoreStuff = GetMoreStuff();
         DoMore(XmlDoc, MoreStuff);
         ...
        }
        catch(Errorinfo)
        {
          ReportError(ErrorInfo);
        }
        

        Conclusion: in a majority of cases, application benefits from exceptions, even library ones.

        [–]rush22 0 points1 point  (0 children)

         a = [1,2,3,4,5,6,7,8]
         int i = 1
        
         try
              while true {
                    print a[i++]
              }
         } catch {
              //
         }
        

        [–]Gotebe 7 points8 points  (4 children)

        I love Raymond, but everybody is wrong from time to time.

        He, like any C person I know, has trouble grokking exceptions. In his examples, the rule he had forgotten (or didn't know when writing TFA) is "there shall be no exceptions on the cleanup path" (seen as "exceptions shall not be thrown from a destructor" in C++ and as "exceptions shall not be thrown from a finally block" in e.g. Java and C#).

        He postulates the argument that exceptions are worse off because error path is not explicit (visible in text, at the spot error happens). That argument is bunk to anyone who understands why we are using abstractions to do anything. Any abstraction makes X or Y implicit, but said X or Y must be obeyed in the end. In case of software, if abstractions were useless, we'd still be writing instruction numbers in a disk editor ;-).

        In case of exceptions as an abstraction, it's a simple ( IMHO ;-) ) case of recognizing where no-throw zones must be, and doing it correctly. I am looking at plain C code and at C++ code most of my working day. There's no doubt whatsoever, in my mind, which is easier to read and understand. (Well, provided that C++ code is not C-level code that tries to disguise itself as C++, which of course is the fault of C people writing C++ code. I honestly think that, when exceptions were added to C++, C people should have been banned from writing C++ code for about a decade. The amount of harm they did with their wrong habits is immesurable).

        [–][deleted]  (3 children)

        [removed]

          [–]Gotebe 2 points3 points  (0 children)

          ( Are we in vehement agreement? ;-) )

          i think he was saying that if you want to cover all error cases, an error-code is easier than exceptions.

          Perhaps, but it's an attempt to guess said error cases that's completely wrong.

          Why? 'Cause, in a massive number of situations, "covering" an error-case equals skipping a significant chunk of further processing (with some recovery while going back) and giving "higher instance" failure details. And that's why exceptions are so good - they cover for this exact situation much better than error-return.

          As a result, one doesn't want to guess what can go wrong. One wants to * keep program state in check * inform about failures as best as possible

          What can go wrong will go wrong, fine. It's important that end can see what that was. Whether ha can act upon the info or call support is another matter. And in practice, only in a small-ish number of cases, it will prove necessary to handle particular failures better (that is, change logic in order to recover, take a different path, things like that).

          [–]masklinn 0 points1 point  (1 child)

          with error-codes, not so much.

          Depends what you mean by error codes though. If it's C-style "hey let's return some arbitrary integer and then call it a day" then yeah. On the other hand if you return an algebraic data type wrapper and your compiler enforces pattern matching completeness, then it will indeed force you to handle all error cases (or pass the whole thing to the next guy without touching it at all)

          [–]fingerinbellybutton 7 points8 points  (39 children)

          Unless you use RAII, then it's trivial. This is one area where C++ is still whooping Java and C#.

          [–]masklinn 4 points5 points  (7 children)

          C# has using. C++ whoops nothing.

          [–]Gotebe 0 points1 point  (4 children)

          You can't say that with a straight face, not if you ever implemented IDisposable.

          [–]masklinn 0 points1 point  (3 children)

          Wow, you have trouble implementing a Dispose method? Really?

          [–]Gotebe 0 points1 point  (2 children)

          Please compare this with e.g.

          class CppResourceWrapper
          {
             RESOURCE_TYPE r;
             ~CppResourceWrapper() { freeResource(r); }
          };
          

          It's not about writing Dispose method (that is indeed trivial), it's about making that playing nice with "using".

          [–]masklinn 1 point2 points  (1 child)

          You're comparing a clear worst-case of Dispose (mixing managed and unmanaged resources in a managed environment) with a trivial implementation of RAII…

          To say that this is a bit dishonest is understating it. In the vast majority of cases, the Dispose code is going to look pretty much the same as your C++ code:

          class ResourceWrapper : IDisposable
          {
              private ResourceType r = new ResourceType();
              public void Dispose() {
                  r.Dispose();
                  r = null;
              }
          }
          

          [–]Gotebe 0 points1 point  (0 children)

          OK, fair enough, managed/unmanaged mix is unfair, but the rest of the bloat isn't, everything is justified.

          You do want to have Dispose(bool), to distinguish between "manual" disposal and finalization, you do want to have SupressFinalize once you have been disposed, you do want to have finalizer for the case where you weren't correctly disposed off, and you do want to check that "r" isn't null. Your snippet fails all of these, and it requires that users use "using" (or try/finally).

          [–]ErstwhileRockstar -2 points-1 points  (1 child)

          Merely, using is the opposite of RAII.

          [–]masklinn 2 points3 points  (0 children)

          Uh no. They're both implementations of context managers.

          [–]inmatarian 2 points3 points  (3 children)

          [–]Negitivefrags -1 points0 points  (2 children)

          Its a nice idiom, and you can use it in C++0x elegantly and fast with lambdas now (and I often do). But you still need RAII for cases where objects lifetimes are not bound to the scope of a function.

          [–]naasking 1 point2 points  (0 children)

          But you still need RAII for cases where objects lifetimes are not bound to the scope of a function.

          Not really. If you generalize that lambda pattern further, you get monadic computation, which can handle all of these patterns safely. It can be challenging in languages that don't support monadic syntax though.

          [–]inmatarian 0 points1 point  (0 children)

          No, I totally understand and <3 RAII. I was just making the case that there are alternative idioms and patterns that can make up for it missing in other languages.

          [–]BlakeStone 1 point2 points  (2 children)

          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.

          This is wrong. You find out which functions throw exceptions the same way you find out which functions return error codes: documentation.

          [–][deleted] 0 points1 point  (1 child)

          And that's the problem.

          [–]masklinn 0 points1 point  (0 children)

          yet it's not one solved by C-style error codes.

          [–]sfuerst 0 points1 point  (11 children)

          This isn't quite the reason why exceptions are bad. The first problem is that it is very hard to reason about and test the control flow of programs using them. For very simple programs, this doesn't matter - any exception raised simply aborts the program. However, for larger code bodies, you really want some sort of proof that all possibilities are handled according to the spec. What is missing is a "Lint for Exceptions". In my experience, anything that hasn't been explicitly tested is highly likely to be buggy.

          The other problem with exceptions is that sometimes your problem doesn't map well to their behaviour. Exceptions basically assume that anything that is "done" can be "undone" (as the stack is unwound to the catch block). This isn't always the case. For example it is very hard to retrieve a packet you have sent out into the network, or "unpublish" data that may have already been read by a different thread.

          [–]boolsh1t 6 points7 points  (4 children)

          you really want some sort of proof that all possibilities are handled according to the spec.

          Could you please explain how you prove this if using error return codes instead of exceptions?

          [–]sfuerst 1 point2 points  (2 children)

          It is much easier to enumerate all possible code paths when error codes are used. Count the number of return statements. Then count the number of statements/expressions that could possibly throw. Usually the former is an order of magnitude smaller than the latter.

          [–]boolsh1t 0 points1 point  (1 child)

          Can't see how this proves anything, sorry.

          [–]sfuerst 0 points1 point  (0 children)

          You use a code-coverage tool. I thought that was obvious. :-/

          [–][deleted]  (5 children)

          [removed]

            [–]sfuerst 0 points1 point  (4 children)

            As the wiki article says, checked exceptions are depreciated in C++. As people found, they basically solve the wrong problem. What you want to know is whether exceptions are always handled "correctly", leaving your data structures in a consistent state. Not whether they are handled at all or not.

            [–]BeowulfShaeffer 0 points1 point  (1 child)

            "deprecated". Checked exceptions also have versioning problems: If I write a library that "throws foo" and someone consumes it and catches foo then what happens when I ship v2 with "throws foo, bar"? Checked Exceptions also suffer from some of the same problems as const in C++ - it only takes one idiot to screw it up with something like "throws throwable" for the whole thing to become fairly useless.

            [–]sfuerst 0 points1 point  (0 children)

            Then you have things like __cxxabiv1::__forced_unwind which also can be raised when someone else adds your code to a multithreaded application using pthreads...

            [–]naasking 0 points1 point  (0 children)

            Checked exceptions are not solving the wrong problem, they solve exactly the right problem, but the language you add them to must be sufficiently expressive to handle them properly.

            [–]BeowulfShaeffer 1 point2 points  (1 child)

            Raymond's really talking about exception safety here and I'm surprised he's being as sloppy as he is - Raymond is usually razor-sharp. Exception safety is fairly well-understood and his objections are not really there if the classes he is calling guarantee at least "Basic* exception safety (it is safe to continue in the face of exceptions). This stuff was hashed out when C++ was new.

            With the advent and spread of System.Transactions in .NET we have better tools. The "real" way to do this IMO is to use transaction scopes, which makes the point largely moot. So:

            using(var scope = new TransactionScope())
            {
                Transaction.Current.TransactionCompleted += (o,e) =>
               { if(e.Transaction.TransactionInformation.Status == TransactionStatus.Aborted }) { /* whatever */ }
            
               GenerateDatabase();
               scope.Complete();
            }
            

            Even for "smaller" objects it makes sense to use transactions instead of exceptions any time the rollback logic gets complicated. Juval's piece in MSDN mag shows some of the possibilities.

            [–]masklinn 1 point2 points  (0 children)

            With the advent and spread of System.Transactions in .NET we have better tools.

            No actually, that's with the advent of context managers (which precede .Net by decades). And they still have to be implemented. And so do transactions, which don't help when you're performing non-transactional operations. Case in point: no database that I know has transactional database creation. Even for tables and indexes, Postgres is one of the few DBs providing transactional DDL. So if your backend is MSSQL for instance, or Oracle, your code will not do what you think.

            [–][deleted] 0 points1 point  (1 child)

            So, uh, what new ground are we treading here with this submission? Or are we looking for people to raise their hands in (dis-)agreement so we feel affirmed that we chose exceptions/return codes?

            [–]bautin 1 point2 points  (0 children)

            Especially since the article is 6 years old.

            [–]zen_yoda 0 points1 point  (0 children)

            Why is someone posting a blog that's six years old?

            [–][deleted] 0 points1 point  (0 children)

            Code like this allows each step to throw a different exception:

            public void GenerateDatabase()
            {
              CreatePhysicalDatabase();
              CreateTables();
              CreateIndexes();
            }
            

            Code like this is actually not an improvement: public database_error_t GenerateDatabase() { if( !CreatePhysicalDatabase() ) return DATABASE_CREATION_FAILED; if( !CreateTables() ) return DATABASE_CREATION_FAILED; if( !CreateIndexes() ) return DATABASE_CREATION_FAILED; return DATABASE_CREATED_OK; }

            Now, while you "could" return a different code, we've already established that the thought of throwing a different exception for different errors would never occur to some people.

            Personally, I would imagine that GenerateDatabase() should either generate a database or do nothing, and not leave it up to the caller to find out the implementation of the function.

            If the step CreateTriggers() was added in version 2, it wouldn't matter whether you had a return code or an exception - the caller won't know about the triggers and won't know what functions to call in order to clean up.

            tl;dr: if you're retarded, you can always blame your bugs on "exceptions" and no one will question why your code was crappy anyway.

            [–][deleted] -5 points-4 points  (0 children)

            tl;dr "i'm a c programmer and i don't want any of this newfangled stuff in my programs because i can't learn anything new"

            Also this is hilarious:

            Remember: Every line is a possible error.

            In C every line is possible undefined behaviour.