Tuesday, February 24, 2009

Wikipedia is the new Yahoo

In the beginning — 1994, early 1995 — Yahoo wasn't a portal, or a search engine. It was a collection of links to “cool stuff,” compiled by a group of grad students. They categorized everything that they found on the web, and the rest of us benefited: we could go to Yahoo, drill down through the categories, and learn about whatever we were interested in at the moment.

The web expanded, of course, until it was no longer possible for human-powered indexing to capture everything. And that's when the search engines took over: Yahoo itself, Altavista, and the current king, Google.

The trouble with search engines is that they aren't very selective — although Google certainly tries. I see this with my own website: there's a single line in my pancake recipe that describes the appropriate time to flip a pancake, and it regularly turns up highly ranked for the search term "pancake flipping." I can only imagine that the people who search for that term want to learn how to flip a pancake, not when. None of them have clicked through to my recipe.

I've recently noticed a change in my own browsing habits: when I want to learn about something, I don't go to Google first. Instead, I turn to Wikipedia. This in itself isn't unusual: lots of people turn to Wikipedia for a summary view of a topic. What is unusual is that, after reading the entry, I start clicking on the footnote links.

This makes sense: these links are selected by the people editing the article, as authoritative references to the subject. They are almost guaranteed to be the best, most relevant links for that topic.

All of which reminds me a lot of 1994, except the index has moved out of the dorm room.

Thursday, February 5, 2009

Mental Models

In Java, an anonymous inner class is permitted to reference the variables and parameters defined by its enclosing method — but only if they're declared as final. For example, the Runnable below can use foo1, but not foo2:

public Runnable prepareOperation(final Foo obj1)
{
    Foo foo2 = // get another instance
    return new Runnable()
    {
        public void run()
        {
            // do something
        }
    };
}

Why the restriction? The JLS simply says that it exists. The Inner Classes Specification (which only appears to exist as part of the JDK 1.1.8 documentation set) requires that “the same value is produced everywhere, so that the name consistently appears to refer to the same variable in all parts of its scope,“ but doesn't give a rationale.

Certainly there's no physical reason for this requirement: the inner class can't hold an actual pointer into the method's stack frame. Indeed, the specification goes on to describe the mechanism that's actually used: the local variable is passed to the inner class constructor, so the inner class holds its own copy. Given that, the local variable could change without affecting the copy held by the class, and vice-versa.

But doing so would break the mental model of Java variables. On the one hand, there's the idea that local variables and member variables are equivalent in every way but scope. On the other, the idea that an inner class has the ability to change the member variables in its enclosing instance, and those changes will be seen by the enclosing instance as well as the inner class.

But allowing the inner class to change its copy of an enclosing method's local variable, without propagating that change to the enclosing method, would subtly break that model. Requiring the local variables to be declared final preserves the model at the cost of a needless bit of syntax. Is it that important?

To answer that, I'll ask a different question: have you ever climbed a staircase where one stair was not quite the same height as the others? Did you notice it? Did you trip?

When approaching a staircase, you unconsciously build a mental model of that staircase, then use that model to place your feet while climbing. I recall reading that most people place their foot within 1/16 of an inch of the expected height. This mental model is so strong, and the consequences potentially so expensive, that the International Residential Code (excerpted at stairways.org) specifies no more than 3/8 inch variance between the shortest and tallest stairs in a flight — about the width of your smallest fingernail.

The mental models that we build for programming are equally strong. They cover everything: the syntax and semantics of the language, the flow of data and code, the order of method parameters, and the One True Brace Style™. When you program in an environment where the models are consistent, you don't have to expend mental energy remembering how something works. When the models are inconsistent, particularly the semantics of the language, it's like placing your foot a half inch above the stair tread.

Sunday, February 1, 2009

TDD Isn't Just QA

I enjoy reading Joel On Software, and have started listening to the podcasts that he does with Jeff Atwood. It's a great distraction from my commute, particularly when Joel says something that's way over Jeff's head and Jeff goes off on a completely different topic. In general I agree with Joel's positions, but in the past few week's he's been slamming test-driven development and programming to interfaces.

In last week's podcast, about 40 minutes in, Joel gives an example of what he would consider excessive testing: a feature that will change the compression level used by the Fog Creek Copilot application. And his argument was that testing that feature would require actually comparing images under the different compression schemes, which was an enormous amount of work to simply verify that you can click a button.

At this point I realized that one of us doesn't understand test-driven development. And I don't think that it's me.

I will give Joel credit for one thing: he gave a good example of how not to write a unit test.

First, because what he's describing is an integration test. And I'll agree, there's usually little return from writing a test to demonstrate that clicking a single button changes the overall flow of a program in a predetermined way. Particularly if your approach to such a test involves validating the output of a library that you do not control.

But why does he need to do an end-to-end test to verify this one button?

I suspect it's because he doesn't partition the program's logic across a series of interfaces. Instead, clicking that button sets off a chain of explicit function calls that ultimately reaches to the other end of a TCP connection. And this is where the discipline of real unit tests comes in.

One of the underlying themes of test-driven development: if it's hard to test, it will be hard to use and maintain. Maybe not today, maybe not in the next revision, but at some point all of those strands of untestable code will wrap themselves into tangled mess. And one way to make code easy to test is to create boundary layers in your application: the two sides of a boundary are easier to test than the whole.

For Joel's example, my first thought is that such a boundary could be created by introducing an object to hold configuration data. On one side of this boundary, the GUI sets values in the configuration object; on the other, the internals use those values to configure the actual libraries. Introducing that single object will improve testability, even without unit tests.

How is the application tested today? I'm guessing that the tester sets a few configuration options, makes a connection, and visually inspects the result. This accomplishes two things: verification that the GUI controls are actually hooked up to something, and verification that the controls have some effect. But why should the former be tied to the latter?

Once you have a configuration object, GUI testing is simply a matter of adding logging statements to that object. You click a button, you should see the appropriate log message. While you could automate those tests, there's no reason to do so. Without the overhead of actually making a connection, any rational number of controls can be manually validated in a matter of minutes.

The other side of the boundary is a different matter: rather than force your tester to manually inspect the results of each configuration change, write automated tests that do it for you. Different compression ratios and color depths can be verified using a single test image, with assertions written in code (does a higher compression ratio result in a smaller output? good, the test passed). There's no need to do low-level validation of the library, unless you don't believe that its authors did enough testing (in which case you have other issues).

But wait, what if that compression code is part of the communications stack?!? OK, introduce another boundary layer, one that separates data processing from data transmission. And this brings me to my biggest complaint with Jeff and Joel: they seem to feel that testing is only important when you're writing an API.

In my opinion, if you're doing object-oriented programming correctly, you are writing an API. Objects interact with each other through their interfaces: you give them certain inputs, and they produce defined outputs. This doesn't mean that every object needs have MyIntf and MyImpl classes. It does mean that you need to define a contract that describes how those inputs and outputs are related.

By writing unit tests — true unit tests, not integration tests in disguise — you force yourself to define that contract. Your goal is not to write tests for their own sake, or even for the sake of future quality assurance, but to tell you when the interface meets your requirements.