A simple rule to help improve convention and maintainability: Camelcasing should be used when dealing with Objects, Everything else should use underscores.
- Class definitions should be CapCase
- Objects should be mixedCase
- Class Methods should be mixedCase
class BookAuthors(object): # <-- Class Definition
def getAuthors(self, book_id): # <-- Class Method
bookAuthors = BookAuthors() # <-- Object
When not dealing with objects we should use underscores and lowercase
- Simple instance variables
- Filenames / Modules
- SQL schema, table and column names
num_books = len(books)
fp_debug = open('/tmp/debug.log', 'a')
sql = 'select book_id, author_id from books.book_authors where book_id = ?'
This varies slightly from the PEP8 Naming conventions. The PEP8 suggest class methods and non-class methods (or functions) be treated the same way, with underscores. However I prefer naming class methods with mixedCase to remind us we’re in the domain of objects.
With regard to sql schemas, table and column names, because column names can become very long, underscores makes them more readable.
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
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
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
URL = 'foom.com'
class FoomWebsiteError(Exception): pass
def scrapePage(path, params):
website = Website(URL)
page = website.go(path, params)
raise WebsiteError('Unable to read from %s: %s'
% (URL, website.error)
lines = page.split('<p>')
name = lines
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.
Whether building a new system, or trying to untangle some untenable rats nest of code — One should have an image of the ideal state in their mind.
If we draw up the best plan we can, thinking in terms of the perfect – regardless of its immediate feasibility – we can then put that on the wall and work towards it.
All changes to code come in two forms: Bug Fixes and Feature Enhancements. There are many decisions to make in each case. By holding up an Ideal, it helps us with those decisions. We choose the path that helps the code converge on some well conceived plan rather than letting it oscillating around, as is often the case.
The ideal plan is rarely realized but that’s not the point. By aiming at one target we help the general direction of all the arrows.
So we must spend a lot of time designing the ideal. To do that we need to create documents such as these:
– Problem Statements
– Use Case Diagrams
– Data Model Entity Relationship Diagrams (ERD),
– Class Diagrams
– Sequence Diagrams
– Wireframes, and
– Mock Reports
It is not enough to simple understand a single aspect of the system and go to work implementing it. We must take the extra time to see how that component fits into the larger whole. The benefits of which creates flexible, easy to use and fun to maintain code.
It was Abe Lincoln who said: Give me six hours to chop down a tree and I will spend the first four sharpening the axe.