There have been complaints — valid complaints — about difficulties dealing with our stack, and debugging errors in the system. The response I would offer is that we need to debug the debuggabilty of these systems. We could try to change architecture and keep our code more monolithic so everything shows up in a single stack trace, but I see that as a dangerous kind of overreaction; a panicky response where we overestimate the problems we know about and underestimate the problems we don’t know about.
If it is difficult to deal with the stack, then we should fix those problems. If it’s too hard to debug, that is itself a bug.
Here are some general things I think we could do to improve our system:
- More ubiquitous use of logging (with logging).
- Aggregate log viewers, so we can see all the messages that are generated by any part of the system. Because there are sometimes side effects in other parts of our stack outside of the application you are interacting with (especially with the introduction of Cabochon) we need see everything that is going on.
- Soft failures, well logged. Also more warnings. Everyone is tired of all the warnings that come out of Zope, but that’s just because it’s not our code that is causing the warnings.
- More assertions in code. Exceptions should be raised as soon as possible. Type assertions are okay, especially for things like strings and numbers where there aren’t generally other types you could usefully use to replace them. Generally immutable objects without substantial behavior, like strings, integers, in some contexts lists, tuples, and dicts, do not benefit from polymorphism and sometimes potential polymorphism is just a bug waiting to happen. (We shouldn’t put in checks everywhere, but if a bug occurs we should put in a check even if the actual bug is elsewhere in the system.)
- Error messages should be helpful and include sufficient information to understand the error. Bad or unhelpful error messages are a bug. So if you ever get an exception from some code like “assert x is None”, you should change it to “assert x is None, (’x should be None (not %r)’ % x)”.
- Centralize error reporting, so that errors in any part of the system are consistently presented.
- Up-front checking of configuration parameters for general sanity. Any configuration error that we make is something we should check for. It doesn’t matter how stupid or misinformed the configuration mistake seems, if someone made a mistake we should add a check for that mistake.
- Greater use of code analysis tools (like PyChecker, pyflakes, jslint) and putting together the scripts to make this checking easy (perhaps part of the fassembler builds). These can’t find lots of bugs, but they will usually find some bugs. And of course every valid bug found is one less bug.
- Make more use of documentation tools, and document the core parts of our system that people need to understand. Apydia looks promising, though Epydoc would be fine too (and it’s older and more mature).
- When bugs are hard to debug, make a ticket for it. For example, does fassembler fail in a weird way? Even if you figure out why and fix it (e.g., some weird system dependency), ticket it up anyway. It probably will always fail, but it should fail in an unweird way.
- When you are debugging code, try to put in debugging code that you can leave in. For instance, use well-structured logging messages instead of print statements or pdb.
- Make sure all objects have useful __repr__ methods. Include a little extra code in __repr__ to show the interesting structure of the object, so that the data doesn’t become overwhelming (e.g., sometimes leaving out attribute values if the value is None).
- If you encounter an error like an attribute being set to the wrong kind of value, consider replacing it with a property with assertions. Automated testing is nice, but runtime checks with simple tests that exercise functionality are better. Runtime checks happen during simple exercise tests, and also in actual production code, and potentially during other tests that you might not have realized exercise that code.
- When writing new code avoid being needlessly permissive. For instance, if you are retrieving a value from a dictionary you can use a_dict[key] or a_dict.get(key). If you aren’t sure which one to use, use a_dict[key] (that’s the more attractive syntax for a reason). Of course if there’s actually a reason to use a_dict.get(key) then use that.
- If you ever put in a hack, always always always put in a comment describing why it’s there and what its purpose is. Later people should know that there’s something fishy there, so they can think harder while reading that particular code.
If we make these improvements in the stack and adhere to these rules while writing and maintaining code I think we’ll all have a much more fulfilling development experience.

I think these are all great suggestions for improving the quality of our code. Thanks for posting this checklist.
Comment by rmarianski on February 6, 2008 at 10:55 pm
Yeah, Ian, I just want to second rmarianski’s comment above. This is a really excellent and productive list for us to all pay attention to. I especially love the parenthetical “that’s why a_dict[key] is the more attractive syntax for a reason” — it’s great that you’re encouraging us to really pay attention to the language’s choices and learn to work with the grain of it to write better code. I dunno, that’s just really exciting to me, and I really hope you’ll continue pointing out things like this.
Comment by ejucovy on March 31, 2008 at 10:25 am