Beautiful Code [184]
12.4. Conclusions and Lessons Learned
Designing software to be used by other developers is a challenge. It has to be easy and straightforward to use because developers are just as impatient as everyone else, but it can't be so dumbed-down that it loses functionality. Ideally, a code library must be immediately usable by naïve developers, easily customized by more sophisticated developers, and readily extensible by experts.
I think Bio::Graphics hits this sweet spot. Developers new to BioPerl can get started right away by writing simple scripts that use familiar BioPerl objects such as Bio::SeqFeature:: Generic. Intermediate developers can customize the library's output by writing callbacks, while the most sophisticated developers can extend the library with custom glyphs.
Bio::Graphics also illustrates the power of standard interfaces. Because it was designed to render any object that follows BioPerl's Bio::SeqFeatureI interface, it will work hand-in-hand with any of BioPerl's sequence data access modules. Bio::Graphics can generate diagrams of handcoded sequence features as easily as it can display features read from a flat file, retrieved from a database query, or generated by a web service and transmitted across the network.
The module also has a few warts, and if I had to reimplement it now, I would have done several things differently. A major issue is the way that subglyphs are generated. In the current implementation, if you assign a glyph to a feature and the feature has subfeatures, the subglyphs will all be of the same type as the top-level glyph.
This has two drawbacks. First, one must use subclassing to create composite glyphs in which the subglyphs reuse code from a previously defined class and the parent glyph is something new. Second, glyph methods always have to be aware of which level of features they are currently rendering. For example, to create a glyph in which the top level is represented as a dotted octagon and the subfeatures are represented as rectangles, the draw_component() routine must be sure to call the glyph's level() method to find out the current nesting level and then draw the appropriate shape. If I were to do it again, I would provide an API to select the right glyph to use at each level of nesting.
Another annoyance is the box model. Glyphs are allowed to allocate additional space around themselves in order to draw decorations such as arrows, highlights, or labels. They do this by overriding methods called pad_left(), pad_right(), and so on.
This works fine until you define a new glyph class that inherits from the old one, and you need to adjust the padding for additional decoration. The derived class must be careful to find out how much padding its parent requests (by calling the inherited pad method) and then add its own padding needs to this value. This can get tricky. If I were to do it over, I would simply keep track of where the glyph draws in its draw_component() routine, and increase its bounding rectangle as needed.
Unfortunately, implementing either of these fixes will change the glyph API in pretty fundamental ways and would require someone, most likely myself, to rewrite all 60+ existing glyph classes in order not to break them. So for the time being, I will accept that the module will always be Pretty Good but will never achieve Perfection. And this is the last, and maybe the best, lesson learned.
The Design of the Gene Sorte > The User Interface of the Gene Sorter
13. The Design of the Gene Sorte
Jim Kent
This chapter is about a moderate-sized program i wrote called the gene sorter. The size of the Gene Sorter code is larger than the projects described in most of the other chapters, about 20,000 lines in all. Though there are some smaller pieces of the Gene Sorter that are quite nice, for me the real beauty is how easy it is to read, understand, and extend the program as a whole. In this chapter, I'll present an overview of what the Gene Sorter does, highlight some of the more important parts of the code, and then discuss the issues