Learning Curves

Sat down to clean up some dojo widgets for a project today.  Basically these are "explorer" like collections of sets-of-things, with the desire to allow for filtering the set, viewing a detailed-list view, you know, standard stuff.

So I figured I would work on the detail-list stuff.  It's a simple grid control, dojo has a grid control, therefor I'll be done in minutes.

Except my grid drew the first line of the grid on top of the grid headers.  Hmm, well, what's causing that.  Let's look under the hood.  Hmm, rather complex-seeming under there, lots of surrounding divs around absolute-sized tables and a lot of rather exotic looking CSS, none of which appears to be obviously responsible for the over-draw in Firebug.

Which is where the learning curve hits the road.  From a simple API that you use to drop a widget into the system, you're now looking at an extremely complex/messy implementation that tries to implement the widget.  The encapsulation has allowed for all these neat features, but it's also allowed the widget to become rather involved in structure.  Once you open up the black box, you're committed to understanding the whole thing, even if you just need to tweak some trivial little aspect or find some tiny little bug.

We saw the same thing with Zope 2, back in the day.  Incredibly simple to work with, right up until the point where you ran into the incredibly complex and opaque machinery that made it so easy.  The pattern just keeps seeming to come up, software gets generalized and generalized until it reaches a point where those who are gurus of the core can do amazingly powerful things, while those outside can't really be anything more than consumers.  (Yes, I realize the Dojo example here is relatively trivial).

The thing is, faced with a complex system, coders who want to do something simple will often just short-circuit and say "I'll just code my own"... at which point they code something that does what they need today.  Over the years they look at that and say "I should use the standard widget to get that little piece it has", but that's such a lot of investment just for the little piece.  So their non-standard piece grows that feature too, likely less elegantly, but it works... and so on.

So what is it that you do to make your libraries or frameworks avoid this?  One thing that comes to mind is the idea of consistency; if your system is constructing widgets, construct them out of lower-level widgets, rather than writing low-level code for each widget.  That way someone that understands your external API can look in and see what's going on easily (it also means you become a consumer of your own APIs and are more likely to find your API issues).

Also; build your implementation as a way of communicating with the people who will be debugging it.  Make it possible for your implementation to switch to a debugging mode or the like where it can show you what's going on (and why).  People will have to debug the software, try to make it possible to read it.

Maybe; avoid registrations, plugins and abstractions.  I know, who am I to say anything, PyOpenGL has plugins that are registered for abstract roles.  I'm not saying don't use them at all, I'm just saying that if you want to be *easily* understood you likely need to keep the number of them to a minimum and you need to document them out the wazoo.

There's probably something in here about customizability and ease thereof.  Probably also something about avoiding too much customizability.  Or maybe something about how to go about making customization systems that people really want to use them.

Comments

  1. Daniel Greenfeld

    Daniel Greenfeld on 02/22/2009 10:21 a.m. #

    Well written! I very much agree!

  2. j_king

    j_king on 02/25/2009 11:47 a.m. #

    Abstractions are good!

    They separate the language of our solutions from the details of their implementation.

    At some level in our program it doesn't make sense to be talking about memory addressing, data types, and other low-level implementation details.

    The problem is a "leaky abstraction." In order for abstractions to work our assumptions about their results must be consistent. I don't care about how they implement the solution to get my result, just that I get what I expect.

    I think dynamic languages by their nature make it difficult to provide abstractions free from side-effects. In my experience, a lot of that mess under the hood is a lot of complex machinery for checking that the result returned is actually what the caller would expect. This kind of abstraction doesn't generally scale well in terms of complexity.

Comments are closed.

Pingbacks

Pingbacks are closed.