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.