Defensive programming is like defensive driving: Anticipate everything that might go wrong. If a function is passed an Id to a database table, do not assume that it is a valid Id, or an integer, or even that it has a value.
What is most important in defensive programming is to communicate clear and precise error messages when things are not as they aught be.
Here are some examples error messages:
Less then ideal error messages:
AttributeError: 'NoneType' object has no attribute 'last_name' _mysql_exceptions.OperationalError: (1054, "Unknown column 'Jerry' in 'where clause'") IndexError: list index out of range KeyError: 'Jerry'
Better ones:
BookError: Book not found: id = Jerry AuthorError: Author not found: id = 506 getCustomers command: Expected 3 parameters, only 2 given. FoomWebsiteError: Unable to read from http://foom.com: HTTP 500
These better error messages are not hard to do if we think about it ahead of time. Here are some examples:
class BookError(Exception): pass
class Book(object):
def get(self, id):
results = self.db.query(select * from books where id = ?, id)
if not results:
raise Bookerror('Book not found: id = %s' % id
return results[0]
Another example:
URL = 'foom.com'
class FoomWebsiteError(Exception): pass
class FoomWebsite(object):
def scrapePage(path, params):
website = Website(URL)
page = website.go(path, params)
if website.error:
raise WebsiteError('Unable to read from %s: %s'
% (URL, website.error)
lines = page.split('<p>')
name = lines[3]
return page
It is okay to have bugs if they are easy to find and easy to fix. Applying a little defensive programming to everything we write make debugging a breeze, and helps everyone using the system.