Talking to lots of developers at GUADEC about their designs, I’m
reminded of the hardest thing to get right in software engineering:
when are you doing too much of it?
The “agile development” model is to always do as little as possible,
adding code and design complexity only as needed. I’m a big fan of
this, especially for apps. It breaks down a bit for libraries and
APIs, though; it’s too hard to get anybody to try the API until you
have it fairly far along, and then it becomes too hard to change the
API. A good approach that helps a bit is to always develop an API as
part of writing an app – for example we’ve developed HippoCanvas as
needed while writing Mugshot, Big Board, and Sugar. Compared to a
written-in-a-vacuum API the core turned out very nicely IMO, but one
consequence of as-needed development is that the API has a lot of
gaps. Still, an API founded in as-needed development would often be a
better start for a productized API than a from-scratch design.
Another guideline I use is that the last 5% of your use cases
or corner cases should be addressed with hacks and
workarounds. Otherwise you will double your complexity to cover that
last 5% and make the first 95% suck. The classic Worse is Better
paper says about the same thing, without the made-up percentages.
Typical hacks in this context might include:
- “Just don’t do that then” – declare that while the API could be
misused or something bad could happen in a particular case, the case
is avoidable and people should just avoid it.
- “Convention rather than enforcement” – all of Ruby on Rails is
based on this one – rather than jumping through hoops to “enforce”
something that’s hard to enforce, just don’t.
- “Slippery slope avoidance” – pick some bright line for what to add
vs. what not to add in a particular category and stick to it, even
though each individual addition seems sensible in isolation.
I bet someone could write (or has written) a whole book on
“complexity avoidance patterns.”
I’ve tried different complexity levels in different projects. For
example, libdbus is flexible in various ways and even adds probably
30% more code just to handle rare out-of-memory situations. (The GLib
and GTK+ stack reduce API and code complexity that other C libraries
have by punting the out-of-memory issue.) While Metacity doesn’t even
use GObject to speak of, just structs in a loose object-oriented style.
(I frequently prefer a struct with new/ref/unref methods to GObject in
application code, though not in APIs.)
It’s a useful thing to think about and experiment with.
(This post was originally found at http://log.ometer.com/2007-07.html#18)