Dismiss Notice
Join Physics Forums Today!
The friendliest, high quality science and math community on the planet! Everyone who loves science is here!

I hate error handling

  1. Nov 6, 2006 #1

    0rthodontist

    User Avatar
    Science Advisor

    Error handling obfuscates code. For someone reading your code, what your program does when there is an error is usually unimportant--at first they care only what it does when it works properly, and maybe later they'll come back to the errors, but understanding your error handling is a very low priority for someone reading your program. Furthermore it's ugly. Throwing a bunch of trys and catches into a neat, clean block of code completely ruins it, as well as probably tripling its length.

    So what work has been done on separating error handling from code?
     
  2. jcsd
  3. Nov 6, 2006 #2

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    There's not a whole lot that can be done theoretically.

    Some errors need to be detected and handled within the bowels of an otherwise very clean algorithm -- and the desire to maintain code locality means that that error-handling code needs to be inserted right there in the algorithm. You would not want it to be hidden in some separate file.

    On the other hand, it's best to handle errors at the highest level possible. If you can allow exceptions to propagate out of entire blocks of code, you should.

    There are only a couple of ways to really "separate" error handling code. Validation of arguments is a good way to clean things up -- put all your type- and bounds-checking up at the start of each block. The rest of your code can then tacitly assume no errors due to invalid inputs are possible. Also, contractual assertions can also be grouped together at the bottom of blocks of code. These contractual assertions pretty much just make sure the block above them did what it was intended to do.

    Both of these techniques require a very good design skills, though; few people outside of academia really write their code with such foresight.

    You might be interested in some of the "cleanroom" software engineering principles, which can be used to mathematically prove that code does what it's intended to do, and thus eliminates many of the possibilities of exceptions. They require a significant investment of labor, however.

    - Warren
     
    Last edited: Nov 6, 2006
  4. Nov 6, 2006 #3

    NoTime

    User Avatar
    Science Advisor
    Homework Helper

    Yes, I think what you said is a good overview.

    If would be my experience that very few people fresh out of school address the problem at all, let alone "with foresight"
     
  5. Nov 6, 2006 #4

    Hurkyl

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    You can always look at it as a presentation issue. Use an editor that lets you fold away things that you don't want to look at.

    (Admittedly, I've been unhappy with what folding I've seen available, but I haven't explored them much. Maybe you should make a project to invent a good editor with useful folding ability. :smile:)
     
  6. Nov 6, 2006 #5

    -Job-

    User Avatar
    Science Advisor

    You know, you can have multiple catches per try. :)
    I actually like try/catch statements, they're better than checking for error conditions with if statements, for example.
     
  7. Nov 7, 2006 #6

    0rthodontist

    User Avatar
    Science Advisor

    Yes, I was thinking along the lines of an editor feature. But some programmers prefer to work with plain text in a plain text editor--me not among them--so I was also wondering if anyone's done research on, maybe, applying an exception-handling "schema" to code that doesn't have error checking.

    I once talked to someone who had the opinion that error handling should not be used as program control--that it should be used for only the things you can't detect otherwise, because of the overhead of throwing and catching an exception. At the time I disagreed, on the grounds that I'd rather write the "mainstream" of my code, then toss in some easy error handling for the cases where it doesn't work. I still believe, in regard to both exceptions and "unusual cases," that code should be written as if it worked and then patched for the cases where it doesn't work. But I think that the patch should be external--that someone should be able to read only the mainstream of your code, which gives them the gist of what it does, and only afterwards read what happens in special cases.

    Also, thinking about this just now, separating special cases from "mainstream" code might help a programmer write the program, too. By separating special cases into a different section, it might enable the programmer to review the special cases as an entity all their own and decide whether any error is still unchecked more easily.

    I think that a clue towards making something like that happen is finding ways to restrict what "special-case" or exception code should be able to do.
     
    Last edited: Nov 7, 2006
  8. Nov 7, 2006 #7

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    This is a very bad idea. You generally want to promote code locality -- you want everything involved in a single task to be in the same place, where it can all be read together.

    Besides, there are so many distinct ways that a complex piece of code can fail that separating the error handling in the fashion you desire would make it extremely difficult for a reader to piece it all together.

    What you want -- a program without any kind of interwoven exception handling -- is a pipe dream. There are ways to hide or minimize the error handling, using editor features or design techniques like contracts. There are zero-defect techniques that can be used to formally prove that a piece of code has no exceptions which need to be handled -- but you want to be able to look at an arbitrary piece of complex code, written without the burden of formal verification -- without any of the "clutter" of error handling.

    I think this is a pipe dream, because what truly separates a good programmer from an excellent programmer is his/her awareness of the failure modes of each line of code written. A programmer who intends to write a piece of code as if nothing can go wrong, and only later go in and "insert" error handling as an afterthought, is a poor programmer.

    - Warren
     
  9. Nov 7, 2006 #8

    0rthodontist

    User Avatar
    Science Advisor

    Nonsense--planning the main idea of your code first before writing the special cases is just another incarnation of top down design. It is a good idea to focus on the big picture before worrying about the details. This goes when you're writing code as well as when you're reading it. Why not reflect that as a language feature?

    I agree that repeatedly referring to an outside section of code for exception handling is a bad idea. The challenge would be to make the syntax for an exception handling schema simple and clear enough so that you can read it once and don't often need to refer back to it, but powerful enough so that it can do the things it needs to do.

    I don't believe that failure modes of a particular line of code is a high priority. It's certainly important, but it's secondary. Understanding how the code actually works, when it does work, is the essential step 1 for making or reading it. Surely you are not proposing that it is more important, on a first reading, to understand the 1% of the time when flawed code fails than the 99% when it works? Even if your goal is to fix the flawed code, do you really want to look at the failures before you understand how the program's even intended to run?

    There is another advantage to separating exceptions and special cases from the mainstream code: doing so promotes the concept of exceptions and special cases. It creates a clear, abstract distinction between exceptions and special cases, and the main code. A change to code can then be looked at as either a "special case" alteration, or a "mainstream" alteration. Special cases tend to be local and trivial, and easier to change without affecting mainstream code. Mainstream code, being the actual design of the application, is much harder to change, and the programmer should be aware of which he is dealing with at a given time. Separating special cases introduces that abstraction.
     
  10. Nov 7, 2006 #9

    Hurkyl

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    I posit that writing code is considerably more important than reading code.
     
  11. Nov 7, 2006 #10

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    I mean no offense, Orthodontist, but you're a student, right? It seems every student goes through a phase where they think they can design a better language...

    - Warren
     
  12. Nov 7, 2006 #11
    I don't understand where your issue with readability in error-handling applies. In the function, raising the error will be very clean, and actually provide insight in to the limitations/restraints/and conditions of the procedure, hence more readable code.

    On the other hand, in the implementation where some obfuscation may occur, as an error excepting handle appears for every function call... this all occurs to the extent of the programmer's needs. If it ever becomes obfuscated beyond the point of readability, either it's complicated enough to disregard the possibility of language-related syntactical anomalies, or the programmer who's implementing the functions is being redundant or over-protective of the performance of his code.

    All in all, I think it's silly to be pestering about this.
     
    Last edited: Nov 7, 2006
  13. Nov 8, 2006 #12

    -Job-

    User Avatar
    Science Advisor

    I agree with Sane on this.
     
  14. Nov 8, 2006 #13

    0rthodontist

    User Avatar
    Science Advisor

    It[color="#black"]`[/color]is[color="#black"]`[/color]a`true[color="#black"]`[/color]fact[color="#black"]`[/color]that[color="#black"]`[/color]most[color="#black"]`[/color]programming[color="#black"]`[/color]work[color="#black"]`[/color]is[color="#black"]`[/color]code[color="#black"]`[/color]maintenance,[color="#black"]`[/color]not[color="#black"]`[/color]creation.

    I[color="#black"]`[/color]don't[color="#black"]`[/color]think[color="#black"]`[/color]it's[color="#black"]`[/color]a[color="#black"]`[/color]very[color="#black"]`[/color]common[color="#black"]`[/color]interest.[color="#black"]`[/color][color="#black"]`[/color]Anyway[color="#black"]`[/color]there[color="#black"]`[/color]are[color="#black"]`[/color]a[color="#black"]`[/color]lot[color="#black"]`[/color]of[color="#black"]`[/color]languages,[color="#black"]`[/color]and[color="#black"]`[/color]a[color="#black"]`[/color]lot[color="#black"]`[/color]of[color="#black"]`[/color]fairly[color="#black"]`[/color]new[color="#black"]`[/color]languages,[color="#black"]`[/color]so[color="#black"]`[/color]it's[color="#black"]`[/color]not[color="#black"]`[/color]an[color="#black"]`[/color]unrealistic[color="#black"]`[/color]goal.

    Consider[color="#black"]`[/color]the[color="#black"]`[/color]following[color="#black"]`[/color]function,[color="#black"]`[/color]not[color="#black"]`[/color]written[color="#black"]`[/color]by[color="#black"]`[/color]me:
    Code (Text):

            def[color="#black"]`[/color]wget_link(self,url,flag,parent_url="",ignore_pr=False):
                    """
                    Download[color="#black"]`[/color]url[color="#black"]`[/color]and[color="#black"]`[/color]all[color="#black"]`[/color]requisite[color="#black"]`[/color]pages
                    First[color="#black"]`[/color]Create[color="#black"]`[/color]new[color="#black"]`[/color]dir[color="#black"]`[/color]or[color="#black"]`[/color]use[color="#black"]`[/color]old[color="#black"]`[/color]parent[color="#black"]`[/color]dir
                    """
                    dir=md5dir(self.subdir,url,parent_url)
                    if[color="#black"]`[/color]dir==None:
                            print[color="#black"]`[/color]'md5dir[color="#black"]`[/color]came[color="#black"]`[/color]back[color="#black"]`[/color]with[color="#black"]`[/color]none!'
                            sys.exit()
                    #print[color="#black"]`[/color]"\twgetting[color="#black"]`[/color]%s"[color="#black"]`[/color]%[color="#black"]`[/color]url

                    #[color="#black"]`[/color]Retrieval[color="#black"]`[/color]of[color="#black"]`[/color]images[color="#black"]`[/color]from[color="#black"]`[/color]yahoo[color="#black"]`[/color]doesn't[color="#black"]`[/color]use[color="#black"]`[/color]the[color="#black"]`[/color]printer[color="#black"]`[/color]ready[color="#black"]`[/color]flag,[color="#black"]`[/color]html[color="#black"]`[/color]does
                    pr=self.printer_ready
                    if[color="#black"]`[/color]ignore_pr:
                            pr=''
                    wget_command[color="#black"]`[/color]=[color="#black"]`[/color]('/usr/bin/wget[color="#black"]`[/color][color="#black"]`[/color]-E[color="#black"]`[/color]-nv[color="#black"]`[/color]-H[color="#black"]`[/color]-k[color="#black"]`[/color]-p[color="#black"]`[/color]%s[color="#black"]`[/color]--no-host-directories[color="#black"]`[/color]--no-directories[color="#black"]`[/color]-P%s[color="#black"]`[/color]\"%s%s\"'[color="#black"]`[/color]%[color="#black"]`[/color](flag,dir,url,pr))
                    (status,output)=commands.getstatusoutput(wget_command)
                    if[color="#black"]`[/color](status>0):
                            self.pr[color="#black"]`[/color]("Wget[color="#black"]`[/color]failed![color="#black"]`[/color]\n\t%s"[color="#black"]`[/color]%[color="#black"]`[/color]output)
                            return()
                    #[color="#black"]`[/color]see[color="#black"]`[/color]what[color="#black"]`[/color]wget[color="#black"]`[/color]renamed[color="#black"]`[/color]the[color="#black"]`[/color]file[color="#black"]`[/color]as
                    try:
                            link=output.split('[color="#black"]`[/color]URL:')[1]
                            link=link.split('"')[1]
                            return(link)
                    except:
                            self.pr[color="#black"]`[/color]("WGET[color="#black"]`[/color]ERROR:\ncommand:\n%s\nURL:%s\n--\n%s\n--\nCould[color="#black"]`[/color]not[color="#black"]`[/color]determine[color="#black"]`[/color]new[color="#black"]`[/color]wget[color="#black"]`[/color]link:[color="#black"]`[/color]%s"[color="#black"]`[/color]%[color="#black"]`[/color](wget_command,url,output,sys.exc_info()[0]))
                            return('')
     
    Consider[color="#black"]`[/color]its[color="#black"]`[/color]equivalent[color="#black"]`[/color]with[color="#black"]`[/color]no[color="#black"]`[/color]error[color="#black"]`[/color]checking:
    Code (Text):

            def[color="#black"]`[/color]wget_link(self,url,flag,parent_url="",ignore_pr=False):
                    """
                    Download[color="#black"]`[/color]url[color="#black"]`[/color]and[color="#black"]`[/color]all[color="#black"]`[/color]requisite[color="#black"]`[/color]pages
                    First[color="#black"]`[/color]Create[color="#black"]`[/color]new[color="#black"]`[/color]dir[color="#black"]`[/color]or[color="#black"]`[/color]use[color="#black"]`[/color]old[color="#black"]`[/color]parent[color="#black"]`[/color]dir
                    """
                    dir=md5dir(self.subdir,url,parent_url)

                    #[color="#black"]`[/color]Retrieval[color="#black"]`[/color]of[color="#black"]`[/color]images[color="#black"]`[/color]from[color="#black"]`[/color]yahoo[color="#black"]`[/color]doesn't[color="#black"]`[/color]use[color="#black"]`[/color]the[color="#black"]`[/color]printer[color="#black"]`[/color]ready[color="#black"]`[/color]flag,[color="#black"]`[/color]html[color="#black"]`[/color]does
                    pr=self.printer_ready
                    wget_command[color="#black"]`[/color]=[color="#black"]`[/color]('/usr/bin/wget[color="#black"]`[/color][color="#black"]`[/color]-E[color="#black"]`[/color]-nv[color="#black"]`[/color]-H[color="#black"]`[/color]-k[color="#black"]`[/color]-p[color="#black"]`[/color]%s[color="#black"]`[/color]--no-host-directories[color="#black"]`[/color]--no-directories[color="#black"]`[/color]-P%s[color="#black"]`[/color]\"%s%s\"'[color="#black"]`[/color]%[color="#black"]`[/color](flag,dir,url,pr))
                    (status,output)=commands.getstatusoutput(wget_command)
                    #[color="#black"]`[/color]see[color="#black"]`[/color]what[color="#black"]`[/color]wget[color="#black"]`[/color]renamed[color="#black"]`[/color]the[color="#black"]`[/color]file[color="#black"]`[/color]as
                    link=output.split('[color="#black"]`[/color]URL:')[1]
                    link=link.split('"')[1]
                    return(link)
     
    It's[color="#black"]`[/color]clearer.[color="#black"]`[/color][color="#black"]`[/color]Actually[color="#black"]`[/color]the[color="#black"]`[/color]essence[color="#black"]`[/color]of[color="#black"]`[/color]this[color="#black"]`[/color]function[color="#black"]`[/color]could[color="#black"]`[/color]be[color="#black"]`[/color]2[color="#black"]`[/color]lines[color="#black"]`[/color]without[color="#black"]`[/color]error[color="#black"]`[/color]checking[color="#black"]`[/color]and[color="#black"]`[/color]if[color="#black"]`[/color]you[color="#black"]`[/color]ask[color="#black"]`[/color]the[color="#black"]`[/color]caller[color="#black"]`[/color]to[color="#black"]`[/color]make[color="#black"]`[/color]the[color="#black"]`[/color]directory,[color="#black"]`[/color]namely[color="#black"]`[/color]just[color="#black"]`[/color]a[color="#black"]`[/color]wget[color="#black"]`[/color]and[color="#black"]`[/color]a[color="#black"]`[/color]return[color="#black"]`[/color]of[color="#black"]`[/color]the[color="#black"]`[/color]URL,[color="#black"]`[/color]vastly[color="#black"]`[/color]reducing[color="#black"]`[/color]the[color="#black"]`[/color]time[color="#black"]`[/color]needed[color="#black"]`[/color]to[color="#black"]`[/color]figure[color="#black"]`[/color]out[color="#black"]`[/color]what[color="#black"]`[/color]the[color="#black"]`[/color]hell[color="#black"]`[/color]is[color="#black"]`[/color]going[color="#black"]`[/color]on.[color="#black"]`[/color][color="#black"]`[/color]Also[color="#black"]`[/color]you[color="#black"]`[/color]really[color="#black"]`[/color]only[color="#black"]`[/color]need[color="#black"]`[/color]2[color="#black"]`[/color]arguments,[color="#black"]`[/color]dir[color="#black"]`[/color]and[color="#black"]`[/color]url,[color="#black"]`[/color]not[color="#black"]`[/color]4.
     
    Last edited: Nov 8, 2006
  15. Nov 8, 2006 #14

    0rthodontist

    User Avatar
    Science Advisor

    Why can't I edit?

    Edit: I mean, why can't I post any Python code?
    Edit: I can if I replace the spaces with black-colored `'s...
     
    Last edited: Nov 8, 2006
  16. Nov 8, 2006 #15
    Now yet again, you are splitting hairs ...

    Focusing on that Python code you posted, that is a poor example to prove a point. The error handling is not filtering what kind of error it handles. Using the except command without any statement following it will direct any error to the excepted code block.

    If the code were written properly, it would be clear and concise as to what error it's excepting and handling. This is definitely more readable. To think otherwise is either ignorant, or just plain silly.
     
  17. Nov 8, 2006 #16

    0rthodontist

    User Avatar
    Science Advisor

    It would be a bit clearer if the type of the error were included, but that's not the main problem.

    Consider this function:
    Code (Text):

        def retrieve (self):
            try:
                mod=self.current.modified
            except:
                mod=''

            self.new = feedparser.parse(self.url,modified=mod)
            try:
                if self.new.status == 304:
                    self.pr ('\t304: no updates')
                    return()
            except:
                self.pr('No status variable... no internet access?')
                self.new.status=304
                return()
            if self.new.bozo>0:
                self.pr('Failed to retrieve (proper) rss feed:\n\t%s' %
                        (self.new.bozo_exception))

            if self.new.feed.has_key('lastbuilddate'):
                try:
                    self.new.lbd = time.strptime(self.new.feed['lastbuilddate'],"%a, %d %b %Y %H:%M:%S %Z")
                    self.pr('LBD of new feed %s' % (time.mktime(self.new.lbd)))
                except:
                    pass
                else:
                    try:
                        if time.mktime(self.current.lbd) == time.mktime(self.new.lbd):
                            self.pr ('\tLBD unchanged %s==%s' % (time.mktime(self.current.lbd),time.mktime(self.new.lbd)))
                            self.new.status=304
                            return()
                    except:
                        self.pr ('\tNo LBD key in current: %s' % sys.exc_info()[0])
                       
            #self.pr ('\tstatus: %d ' % self.new.status)
            self.pr ('\tEntries retrieved: %d' % len(self.new.entries))
            # number the entries for renaming the link in the updates subroutine
            ind=0
            for x in self.new.entries:
                x.index=ind
                ind+=1
     
    Without error handling (and removing some extraneous code), this is one line (whited out so you can read the function as written first):
    return feedparser.parse(self.url,modified=mod)
    Is it easier to understand if you see that one line, or see all the error handling?
     
    Last edited: Nov 8, 2006
  18. Nov 8, 2006 #17

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    So... why doesn't the author just put that "one-liner" in his comments above the code? Then you know exactly what the "gist" of the block is -- that's what this is all about, right? -- and the error-handling code stays put, interwoven with the code it protects, as it should be.

    And, besides, the code you pasted does NOT just download a page -- it checks server status codes, timestamps, and does other things. I agree it's poorly written code, but even without error-handling, it won't be just one line. You're being dishonest.

    - Warren
     
    Last edited: Nov 8, 2006
  19. Nov 8, 2006 #18
    In my opinion ...

    The error handling is very important. It'll tell whoever's reading your code what crap not to throw its way. If you really feel like achieving a sense of readability, I'll tell you what...

    At the top of the function, include a """ multiline comment """ that contains the code without error handling. Otherwise, your intentions in compromising length of code for a secure algorithm is unjustified.

    Edit : Wow. A pleasant surprise: chroot and I both suggested the exact same thing. Comment yer "readable" code if it makes such a difference.
     
  20. Nov 8, 2006 #19

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    Bingo. That's two of us, who have independently said the same thing.

    - Warren
     
  21. Nov 8, 2006 #20

    0rthodontist

    User Avatar
    Science Advisor

    No, it really is. There's no need to check the last build date because if there wasn't a 304, it will always be different. Also the index code doesn't actually do anything since the index already contains those values (not that you need them anyway since you can just iterate through the feeds). All the rest is just error handling--necessary, but obfuscating. Well, there's also the "mod" variable, but still the only reason you had to make that is because you didn't know if the current feed had a modified field, so its purpose is still entirely error handling.
     
    Last edited: Nov 8, 2006
  22. Nov 8, 2006 #21

    turbo

    User Avatar
    Gold Member

    I wrote custom specialty software for small businesses back in the day when a 286 was a potent machine and 386's were servers. I wrote code in modules that had specific functions, and commented them heavily, so that when I went to my library of modules, I didn't have to parse the code every time to remember what functions they performed. That way, when a customer had a complex problem, I could just scan my modules with a text editor and decide which ones to use, and which ones might be usable with minor modification. Error-handling was built into the modules and was commented.

    Showing my age, I wrote these applications in Ashton-Tate dBase and as soon as FoxBase came out with a compiler, I compiled them to run under FoxRun. Pretty primitive, but it worked great. In this system, error -handling had to accompany the relevant code.
     
  23. Nov 8, 2006 #22
    It's necessary, I'm happy you acknowledged that.

    Your nested indentations of exceptions do lead to obfuscation, however it doesn't have to. The obfuscation lends itself to being the result of your programming. You don't state which errors to except, and you can turn the excepted blocks in to methods in your class. That way you can label each resulting event of an error, respectively, with a function name. Additionally, you've limited your obfuscation to simple nesting.

    It's quite simple really, but only worth it if you do really care that much (which, by the size of this thread, illustrates just that).
     
  24. Nov 8, 2006 #23

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    You must check the date, because some servers might be broken or non-compliant. Such servers might not return a 304, even when the dates are the same. The 304 status code is not even required by the HTTP/1.1 RFC -- it is simply suggested.

    Do you really trust a server you didn't write, running on a computer you don't control? Do you really not feel that this is a justified thing to check?

    It sounds to me like your gripe is really that people don't write (or comment) their code cleanly enough to make it easy for a novice reader to immediately understand what is being done in a block of code. The solution to this problem is better commenting and better conventions. The solution is absolutely not to split the error handling off to some other file where it can be "hidden." Error handling is an integral and necessary part of writing a decent program -- it is not just some dirty, menial task that has to be grudingly done after having written the "pristine" one-line version.

    - Warren
     
  25. Nov 8, 2006 #24

    0rthodontist

    User Avatar
    Science Advisor

    Why should a programmer have to remember to put shorter, exception-free code in a comment? It would feel like busywork, and nobody's going to do that. And nobody does. If it's useful, and if you think it should be in a comment then we agree that it is useful, it has to be a language feature.

    It is not absolutely necessary to check the dates, because the worst that will happen is that the server (the one that's running this code) will spend an extra couple seconds writing stuff to disk that it already has. It is a slight efficiency optimization, not an issue of correctness (and remember that this is written in Python, which is very slow anyway). Now that I look at it again, the whole thing with making the "lbd" is actually unnecessary since you could have just compared self.current.lastbuilddate with self.new.lastbuilddate.

    I certainly object to calling me a "novice" programmer. I'm not a novice programmer. Also, I certainly never suggested that the error handling should be in "some other file." It should be separate, but near.
     
  26. Nov 8, 2006 #25

    0rthodontist

    User Avatar
    Science Advisor

    The examples of obfuscation are not my code.
     
Share this great discussion with others via Reddit, Google+, Twitter, or Facebook