I am very much a "C" style programmer, so exceptions are not something I use, or see on an everyday basis. Despite this, I've always had a love/hate relationship with exceptions, with my emotions depending on the situation I'm in.
In the past few years, I've used OCaml and Python on occasion to do some quick prototyping (and even some not-too-quick prototyping), and I found the languages' support for exceptions to be quite useful. Being able to call library functions without having to continuously check their return values, and instead be able to rely on the called functions to throw an exception when something bad happens, allows me to write much more quickly. Compare this, for example, to OpenCL in C (because C doesn't have exceptions), which I've been exploring recently. Even the most basic of OpenCL programs, to multiply two vectors, requires 50+ lines of boilerplate code, half of which is "if" statements checking to make sure a cl function didn't return -1. When I was writing my first OpenCL program, I was muttering in annoyance with how much boilerplate cruft I had to spew out and dreaming of exceptions. What I wanted, was for the cl functions to throw an exception on error, which would then allow me to have a single generic catch-all exception handler to dump some error messages and gracefully die.
But we shouldn't forget that exceptions have a place outside of simple prototyping also. Some level of terseness is always appreciated, especially when it allows the programmer to focus on the real task at hand, instead of writing an error check for every function call.
The real magic of exception handlers is that they allow the program to unwind the call stack all in one go, without actually having to jump to each and every return address on the stack as it goes. This is quite powerful, allowing the program to immediately handle the exception when it occurs, and removing the need for the programmer to organize her code to pass the error back up the call stack to where it can be processed.
But as Uncle Ben reminded us, with great power comes great responsibility. The power of exception handlers also gives programmers the ability to screw things up in remarkable ways. Because the exception basically forces a jump to the closest exception handler, all the code in between the exception and the handler won't get executed. This may include important bits like free() and close(), or even just simple things like finishing up printing parts of an output message. While the exception handler is *supposed* to handle all those bits, it's not uncommon for them to be forgotten. A common reason for this, is placing the exception handler very far away from where the exception might be thrown.
Which leads me nicely into my next story. I was working on a bug in some Python code last week, where an exception was thrown somewhere within several thousand lines of code and then handled way at the top of the program. The error message printed was "Exception occurred in <wubwubwub> server," making it quite possibly one of the most useless error messages of all time. I spent a good five minutes raging silently at the programming gods before trying to identify a root cause. It's important to remember that the point of exception handlers is to improve our lives not just in the short term, but also for the long term when code needs to be fixed and maintained.
There are of course many other reasons to love or hate exceptions. But these are the ones that strike a particular chord with me because of my limited experiences. I don't believe exceptions are totally bad, it is merely a powerful tool that is far too often misused, much like "goto" (let's not get ourselves muddled in that argument though...). My personal belief is that the power and terseness of exceptions is sufficient enough that it shouldn't be completely eschewed, but simply used sparingly and with great care. The problem with that idea of course, is that software development sits in the real world, not some lovey-dovey happy-place, where there is always plenty of time to work on projects, everyone is properly trained, all code is properly reviewed, and vampires sparkle in the sun.
I wanted to be able to have an animated progress bar to be displayed in a terminal for my current Python project. There are already various implementations on the Internet of something like what I wanted, but I couldn't find one that was animated. So for the sake of practice, I decided to write my own. In order to be able to have the bar animated, and still allow the program to get other work done, I had to create a seperate thread to manage the status bar.
Code for the bar, (and also sample code to create a demo) are below.
import time import sys import os import threading """ Display an animated statusbar, with progress and percentage ( items-completed/items-total ) displayed below the statusbar. Seperate thread is used to display the spinning "icon." In order to stop the statusbar thread early, calling thread can use join() example output created by StatusBar thread: [===============\--------------] 30/60 50% Written by chi (aka chi42) from 42gems.com-- 11 Sept, 2009 Copyright (C) 2009 chi (from 42gems.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Please see http://www.gnu.org/licenses/ for a copy of the license. """ class StatusBar(threading.Thread): # class variables: # max: number of total items to be completed # pos: number of completed items # inc: amount of items to increment completed 'pos' by # (shared resource) # comp: amount of '=' to display in the progress bar # running: whether or not the statusbar is running def __init__(self, pos=0, max=100): threading.Thread.__init__(self) self.pos = pos self.max = max self.busy_char = '|' self.running = 0 self.inc = 0 self.__getsize() self.comp = int(float(self.pos) / self.max * self.columns) self.inc_lock = threading.Lock() # find number of columns in terminal def __getsize(self): rows, columns = os.popen('stty size', 'r').read().split() if int(columns) > 80: self.columns = 80 - 2 else: self.columns = int(columns) - 2 return # redraw progress bar and all numerial values def __print(self): self.__getsize() sys.stdout.write('\x1b[1G') sys.stdout.write('[' + '=' * self.comp + self.busy_char + \ '-'*(self.columns - self.comp - 1) + ']' ) sys.stdout.write('\n\x1b[0K' + str(self.pos) + \ '/' + str(self.max) + '\t' + \ str( round(float(self.pos) / self.max * 100, 2)) + '%') sys.stdout.write('\x1b[1A\x1b[' + \ str(self.comp + 2) + 'G') sys.stdout.flush() return # run the thread def run(self): global busy_chars, inteval busy_chars = ['|','/','-','\\'] interval = 0.3 self.running = 1 self.__print() while 1: # loop and display the busy spinning icon for c in busy_chars: self.busy_char = c sys.stdout.write(c + '\x1b[1D') sys.stdout.flush() time.sleep(interval) self.inc_lock.acquire() if self.inc: if (self.pos + self.inc) >= self.max: self.inc_lock.release() self.pos = self.max self.comp = self.columns self.busy_char = '' self.__print() sys.stdout.write('\n\n') self.running = 0 return 0 else: self.pos += self.inc self.inc = 0 self.inc_lock.release() self.comp = int(float(self.pos) / self.max \ * self.columns) self.__print() else: self.inc_lock.release() return 1 # increment number of completed items used by calling thread def increment(self): if self.running: self.inc_lock.acquire() self.inc += 1 self.inc_lock.release() return 0 else: return 1
Annnd the demo code:
#!/usr/bin/python import statusbar import time import os print '\n' min, max = 0, 80 inc_sleep = 3 bar = statusbar.StatusBar(min, max) rows, columns = os.popen('stty size', 'r').read().split() print 'columns in screen: ', columns bar.start() while 1: time.sleep(inc_sleep) if bar.increment(): break print 'done!'