Archive for the ‘Design’ Category

Getting Chop-Happy with Axeman

Friday, March 16th, 2007

An important but oft-overlooked principle of software design is the aggressive culling of unused features. The best software products are slim and lean, with exactly the features its users need and few that they don’t. This like weeding and pruning in your garden: without it, you’ll eventually be overrun.

The types of apps that I most commonly work on are internal applications used by perhaps a few hundred users in a single organization, or across several organizations. You’d think that with such a narrow audience, it would be easy to get information about what’s being used and what isn’t. Not as easy as it seems, though, because the users are not good at analyzing their own use. If you ask them whether a particular report is used, for example, they’ll make vauge noises like “Oh yeah I clicked on that once” or “Huh I hadn’t seen that before, but I’ll definitely use it now that I know about it.” In most cases this stuff is not true, but it’s very hard to tell.

Historically my approach to this has been to cut a feature I think is unused and wait for someone to complain. This works well enough because my intuition is right 90% of the time; but the 10% it is wrong, I can end up with cranky users. (In doing experimental cutting like this, I usually remove the link a few days prior to deleting the connected code. That makes it a cinch to put it back in when necessary.)

A burst of inspiration hit me the other evening. We don’t need to ask the users: the application should be able to track this! To this end, I’ve created Axeman. This is a tiny Rails plugin that tracks usage in a SQLite database, and displays a simple report with the results constrained by time. Screenshot:

In the left column is a traffic report, comparable to a web log analyzer like the venerable AWStats. The Axeman report is way simpler and doesn’t have any fancy graphs, though, so this isn’t too exciting. Besides, you can install AWStats or whatever to get this info about your Rails app. Where it gets more interesting is the right column.

Here we see controller actions that have not been accessed during the selected time period. These are determined by analyzing the source of your app/controllers directory, and cross-referencing it against the usage data.

As an aside, I think it’s interesting that this is only possible because of the structured and convention-based nature of modern application frameworks. Axeman is a very simple example, but I am hoping that as time progresses, we will see more self-aware / self-introspecting application components.

What does it mean when actions appear in the righthand column? Let’s look at the example shown in the screenshot. This is a tiny app and I didn’t expect there to be many dark corners, but as it turns out there’s quite a bit of dead code. First, there’s a bunch of account signup stuff which is unused - this was created by the generator for the login engine. It’s not used, so axe it.

Next, we see that categories and authors both have index actions which are never accessed. Looking at the code I see that these are just redirects to the list action. However, the list action seems to be linked directly, since that one appears in the lefthand column. No need for them then: the axe claims two more victims.

Books has a few unused actions. sort_order is a vestigial remnant of an ajax feature which is no longer used; it goes under the axe. destroy is a working method, but not linked anywhere; most likely created as part of CRUD, but then whoever did the UI didn’t feel that it was needed. We could link it, I suppose, but why bother? If the app has gone this long without anyone complaining that they can’t delete books, then there seems little need to maintain the code that implements the feature. Chop, chop. Last, it seems that there is some confusion about new, edit, and new_and_edit methods on the Books controller. Looking at the code I see that new_and_edit is called by both new and edit, but is never accessed directly by the user’s browser. Therefore it should be made private (Axeman ignores private and protected controller methods). With all of these changes, the Books controller is quite a bit cleaner.

Also on the executioner’s block should be methods with low hit counts, that is, ones that appear at the very bottom of the lefthand column. This requires more knowledge of the user story for each page than does completely unaccessed pages. For example, you could have a page which displays some tax information which is only accessed once a year by one person in the organization. Therefore a low hit count should be expected, and the page should not face the axe. But most other kinds of pages should probably be removed if they haven’t been accessed frequently. The default time period is 3 months, which I think is about the timespan in which you’d expect something to be accessed at least a few dozen times. If it’s only got one or two clicks, chances are good this was just someone who hit the wrong link, or perhaps was just curious. Truly useful pages will have hundreds or thousands of views, depending on the size of your user base.

What about the idea that a page does offer useful features, but people don’t know about it? If you think that this is why it’s unused, then you need to find a better place to link it, or a better way to educate your users. The bottom line is that it doesn’t matter how theoretically useful a page is: if no one is using it, then it is not actually useful.

And keep in mind that this tool (and in fact, the entire concept of aggressive feature culling) is most effective not as a one-time event, but as a habit over time. A page which might have been extremely popular last year could fall into disuse when another page is added which provides similar but slightly improved functionality.

This plugin was the result of just an hour or two of hacking, but I’ve already been surprised at how useful it has been in my production apps. New ideas are suggesting themselves as I use it, including watching for unused partials, showing changes over time in a visual fashion, or even trying to look for unused model methods. For this last item, it’s been my experience that over time, model start to bristle with methods, many of which are remnants of historic functionality and no longer used, though this will be by far the hardest one to implement.

Another feature that I’ll try to add soon is a logfile anaylzer which scans production.log in a manner similar to how AWStats processes Apache’s access.log. This will allow the importation of historic data, and will also make Axeman more suitable for use on high-traffic, public-facing sites, where hitting an external SQLite database on each pageview may not be acceptable.

A subtle but powerful point that is driven home by the usefulness of this plugin is just how much design is an evolutionary process, not a one-time occurrence. Of course I know this, as do most of us, but I’m finding that Axeman makes it tangible. Here’s a piece of code which exists for no other reason than to help the application’s design change over time. The only other component I can think of that really acknowledges this is migrations, but these are more at the underlying technical level, rather than at the level of user-facing features.

Behavior-Driven Development

Friday, March 2nd, 2007

Behavior-Driven Development (BDD) has been hanging around, tugging gently at my brain for a few months now. Like most interesting ideas, it’s only a small evolutionary step from its predecessor, TDD. But I think it’s a subtly powerful concept, one that I’m starting to pay more and more attention to in my work. The introduction sums up the evolutionary shift at a personal level:

1. The developer starts writing unit tests around their code using a test framework like JUnit.
2. As the body of tests increases the developer begins to enjoy a strongly increased sense of confidence in their work.
3. At some point the developer has the insight (or are shown) that writing the tests before writing the code, helps them focus on only writing the code that they need.
4. The developer also notices that when they return to some code that they haven’t seen for a while, the tests serve to document how the code works.
5. A point of revelation occurs when the developer realises that writing tests in this way helps them to “discover” the API to their code. TDD has now become a design process.
6. Expertise in TDD begins to dawn at the point where the developer realizes that TDD is not about testing, it is about defining behaviour.
7. Behaviour is about the interactions between components of the system and so the use of mocking is a fundamental to advanced TDD.

Word.

Go Ahead, Do The Big Rewrite

Thursday, January 11th, 2007

Many experienced developers caution against the Big Rewrite. Perhaps the most famous of these is Joel’s adamant proclamation that the Mozilla team’s decision to rework Netscape from the ground up was “the single worst mistake” that a software team could make.

Although I agree with Chad’s arguments and to a lesser degree with Joel’s, I can’t help but to notice: what Joel called a huge mistake turned into Firefox, which is the best thing that ever happened to the web, maybe even the best thing that’s ever happened to software in general. Some “mistake.”

Ok, ok. So most of the time the big rewrite is a bad idea. Mozilla got lucky, or tried really hard, or it doesn’t count if you’re an open source project, or something.

Except that I’ve done several big rewrites of commercial applications in my software career, and every time it has gone very smoothly. In all cases the end users have been pleased, and I and the other developers find ourselves saying “We should have done this ages ago!”

In fact, Bitscribe is currently finishing up one of these right now. The application being rewritten is one of those sprawling enterprise apps with countless nooks and crannies of functionality. Written in a blend of PHP, Perl, PL/PGSQL stored procedures, and C++ (GTK 1.0, baby!), it dates back to right around the turn of the milenium. So not utterly ancient, but plenty has changed in the software world since then. The rewritten application is 100% Rails.

Everyone involved with the project just couldn’t be more pleased, from what I’ve seen. It’s been a lot of hard work, sure, but the benefits are massive. Developers are energized and excited to be working with the latest tools, and throwing out years of accumulated developer debt. (The legacy app had been maintained by a variety of different companies over the years, so you can imagine what the code looked like.) The client’s staff is loving the shiny ajaxified interfaces. And the client’s management (you know, the people who sign the checks) are seeing a whole new world of software unfold before them, and seem very aware of the efficiency improvements that will come with it.

There was a benefit that we didn’t anticipate, and it’s turned out to be one of the most useful. In the process of rewriting we’re not just porting code; we’re rethinking the design. The technical design of course, but also application design. It turns out that the wisdom the clients had gained from years of production use of the legacy application pointed to many insights on a streamlined design. We’re able to provide the majority of the functionality of the original app with a fraction of the complexity.

And not just code complexity, but user interface complexity. When all is said and done, the client’s training costs for new staff will be greatly reduced; there’s much less likelihood of entry error; and the application matches their business process more closely. Et cetera.

So I’ve never had a rewrite go badly. But maybe that’s because I’m inherently cautious and skeptical about rewrites. I lean toward waiting a bit too long, rather than doing it a bit too soon. So then by the time we go to do it, it’s really overdue.

Raganwald says “And you’ll need to be 100% sure your team has the horsepower to get the job done and is going to use a process that can handle the load.” Maybe that’s why it’s gone well for us; Bitscribe has massive developer horsepower, and massive focus on process. And even for us, there’s been lots of strain on the team doing the rewrite.

I’ll admit that there’s a unique kind of pressure that you get in this situation. It’s not like the ordinary pressure to meet software deadlines. I think it’s that sense that you can’t turn back. You don’t want to fix bugs or add features to the legacy code, but the client needs those changes so that they can do business. So then you have to press on ahead and try to get the new version workable as quickly as you can. And as Chad points out in his essay, there are seemingly endless little bits of functionality in the original app that need to be provided in the new system. It’s not a kind of pressure I’d be willing to bear most of the time, but it can be worth it when an app is really due for a bottom-up overhaul.

And let’s face it, you’ve gotta rewrite sometime - no software system can run forever, and the cost of running even a marginally outdated app can be tremendous. I guess the trick is just picking the right time to do it - and then having a healthy fear of just how big a job it really is. The moment you think it’s going to be easy, that’s the moment you’re digging your own grave. Be scared of what you’re facing and you may just have a chance.

Is Test-Driven Development Overrated?

Saturday, October 21st, 2006

The (Misguided?) Faith in Unit Tests stands as a rare counterpoint to the flurry of pro-TDDers (one of whom is me). The author makes some great points; but they have more to do with incorrect applications of TDD than flaws in the approach itself.

Let’s separate the technical design of software applications into two layers: high-level, which is an outline of all the working parts and how they fit together; and low-level, which is a description of the details of each part. A high-level design of a shopping cart, for example, might be something like: “Products represent items the user can buy, and belong to one Category. Cart is a container for CartItems, each of which specifies a quantity and is linked to a Product.” Design decisions you would make at this level are things like: can a Product can belong to more than one Category? Or: is multicurrency implemented through its own class, or just a currency_type enumerated field alongside any price field on an object?

The low-level design is a description of the interface to each object. For example, how does one add items to the cart: cart.addItem(item, or cart.getItems().add(item), or item.addToCart(cart)? Do you copy the price of the Product onto the CartItem when it is added to the cart, to prevent updates to the Product from affecting the customer’s checkout price? Does the shipping calculator connect to the shipping carrier’s API every time it needs to calculate a cart’s shipping price, or does it cache a local table of weights and shipping values to avoid slowdown from the network cost of the transaction? Most of these decisions affect either the internal implementation of the objects, or the APIs they export.

Tests, and unit tests in particular, help somewhat with low-level design, but not at all with high-level design. The latter is informed by the needs of the application. High-level design defines the mapping between software and the real world. Tests have a limited scope: their whole universe is the application, and anything beyond the boundaries of the code cannot be addressed. (It should be noted that business logic tests are one attempt to bridge this gap; we use this for some of our apps, but it isn’t appropriate in all situations.)

Furthermore, TDD is not so much a way to help create your design as a way to enforce it. Yes, writing the unit test first is a good way to discipline yourself to create a clean, straightforward, and orthogonal API for any given class, and I make this very point repeatedly in my screencast on test-driven development. But this is only half the story, maybe less. TDD’s real benefit over the long term is documenting and enforcing the interface and functionality of an object. A unit test says: “This is how the object is supposed to work, and I can prove it: just run me.”

So if someone new is working on the project, they can find out how multicurrency support is handled or what you have to do to add an item to a shopping cart by looking at the tests. Better yet, if they change an object’s implementation in a way that is inconsistent with the low-level design - for example, tinkering with the pricing code for products without properly handling multicurrency - this will break the tests, thus forcing the developer to confront the fact that they have a misperception about the design.

Where is the right place for the high-level design to live? For this I don’t think we have any solution other than traditional documentation. To some degree a good UI communicates many aspects of the design: for example, a breadcrumb trail shows the relationship between the primary objects (e.g. Store > Category > Product). But there’s usually still plenty of behind-the-scenes design that can’t be communicated this way. For that, some text descriptions entered into a wiki, along with some diagrams showing class or table relationships, is still your best bet.

People never liked technical documentation because the bulk of its maintenance is related to low-level design. If you’re documenting that this object has these ten methods and this first method is named this and takes these three arguments, each which have this type - well, that’s going to get out of date quickly. It’s a pain to maintain which means it won’t be up to date, and when it’s not up to date no one uses it and thus it becomes worthless. Unit tests free you from this painful cycle. (If you’re making a complex, publicly-used support library, reference documentation generated from the real code with something like RDoc should complement the tests.)

Cut out documentation of low-level design, though, and you’re left with much less documentation to write. I suspect most good technical designs - and by “good” I mean “free of excessive complexity” - could be summarized in a few paragraphs and a diagram or two, backed up by a larger document to cover domain-specific knowledge for the data tracked by the app.

Just-In-Time Design

Thursday, May 25th, 2006

Software is traditionally created with the same basic approach that one would take doing any other kind of engineering - say, building a bridge. The architect lays down a detailed blueprint; during construction, the design changes very little. Let’s call this up-front design.

Despite decades of building software in this fashion, no one has been able to get it to work as well as well for software as it does for bridges. Software is different because it’s soft. You can, and usually do, make massive changes to the design over the lifetime of a project. In most cases version 4.0 of a product has almost nothing in common with 1.0. It would be like if halfway through building the bridge, you decided to make it a boat instead.

This would be insane for a bridge, but - counter-intuitively - it’s actually possible with software. More than possible, in fact: it works better. This is what extreme programming proponents call evolutionary design. It’s what I like to call like to call it “just in time design” or “iterative design”. First, the bullet points.

  • Take small bites
  • Do the simplest possible thing that will work
  • Degrade gracefully
  • Release early, release often
  • Don’t be afraid to change the design mid-stream
  • Throw in unplanned icing when it’s easy to do so
  • Drive new features based on feedback, not speculation
  • Keep the momentum going

Let’s look at each in turn.
(more…)