Thursday, 20 May 2010

WPF newbie and ContentPresenter solve the case of the disappearing content

This cost me a bit of time (and 9'59" of our UX Designer's time) a few weeks back. My aim was very simple: write a WPF ControlTemplate that I could apply to certain controls and decorate their existing content. "Easy", thought WPF newbie, "I'll use a ContentPresenter!"

My understanding of ContentPresenter at the time was that it dumps whatever is in the control's content into that section of the template. So if I have something like this:

<Button Template="{StaticResource FancyTemplate}">Hello</Button>
<Button Template="{StaticResource FancyTemplate}">World</Button>

With this template in the resource dictionary (except preferably something pretty):

<ControlTemplate x:Key="FancyTemplate">            
    <Grid Background="{StaticResource FancyBackground}" Margin="10">
        <ContentPresenter />
    </Grid>            
</ControlTemplate>

I expected to see something like this:

But got this:

That's right -- nothing. Nada. Zilch. No content. Not so much as a vague hint of the ghastly template I so thoughtlessly dumped into this post.

So it turns out that ContentPresenter needs a little more information to be able to do its work. The easiest way to fix this was to set the TargetType on the template so our ContentPresenter knows what "content" is:

<ControlTemplate x:Key="FancyTemplate" TargetType="Button">            
    <Grid Background="{StaticResource FancyBackground}" Margin="10">
        <ContentPresenter />
    </Grid>            
</ControlTemplate>

The other option is to explicitly specify the property from which the ContentPresenter will get the content, using TemplateBinding:

<ControlTemplate x:Key="FancyTemplate">            
    <Grid Background="{StaticResource FancyBackground}" Margin="10">
        <ContentPresenter Content="{TemplateBinding Property=ContentControl.Content}" ;/>
    </Grid>            
</ControlTemplate>

Both of these options work fine. Hope this saves someone a bit of time.

Friday, 7 May 2010

On design and missing the forest for the trees

Looking at discreet units of code is an awesome way of limiting complexity to the immediate problem at hand, and of optimising your design for solving that problem at that level of abstraction. That's one of the reasons TDD with unit tests is so useful. However focusing purely on the details of the design can mean missing the bigger picture.

One thing I noticed while watching Rob Eisenberg's excellent MIX 2010 presentation, "Build your own MVVM framework", was that Rob removed some fairly fundamental areas of duplication I hadn't even considered. Things like using conventions for automatically data-binding view controls to properties and commands on their view models, rather than repeatedly wiring-up property changed events and duplicating view model property names in XAML bindings.

My team have just finished our first WPF application, and we focussed on test driving out all our view models and making sure the micro-level design was ok and fairly duplication free. This near-sightedness meant we never really considered the duplication occurring at a macro level, and so we missed a great opportunity to drastically simplify our code. (To be clear, I started out the project and established the initial conventions before the remainder of the team got to work on it, so missing this was my fault, not the team's.)

In addition to helping us stay DRY, I'm also fairly certain having more focus on the macro-level translates into the more effective application of design patterns at a micro-level. It's a case of thinking globally, acting locally. Design patterns are all about context, so the more context you give yourself the better chance you have of choosing a good shape for the unit in which you are working.

That's not to say that micro-level design isn't extremely important. After all, abstracting a complex system into small, manageable pieces is the whole reason OO exists. It's just a reminder that the larger context in which you're working is also important.

Thursday, 6 May 2010

Friends don't let friends test-drive while refactoring

I've generally tended to think about refactoring purely in the context of Test Driven Development. You write a failing test, make it pass, then refactor to tidy up. The main stimulus for the refactoring has been feedback from the tests.

It was only very recently I realised that I should probably think about refactoring separately from TDD. Although it is obviously an essential part of the TDD process, it is also a powerful technique in its own right. As I discovered, treating them interchangeably can lead to trouble.

Refactoring into a test-driven hole

I had a class that had grown too big. I wasn't sure of the right design to split up this class, so my first approach was to fall back on writing tests. I adjusted the tests for the class in question and drove out some new collaborators. This broke a whole lot of my acceptance tests, but I figured that was ok because my unit tests were passing. I continued driving myself deeper and deeper into a mess. My unit tests were giving me feedback as to the design I was driving out, but because I had lost the feedback of my acceptance tests I had no idea how far I had strayed from a correct, functioning implementation. I knew that I had lots of broken tests, and no matter how much I drove out the green bar remained painfully elusive.

A few hours later I took stock. The design direction seemed to be workable although unreasonably complex. I was fairly confident it would only take a little longer to polish off the final pieces, and I had reduced the failing acceptance tests from 30 to about 19, but the whole thing just didn't seem right. For one, I hadn't been able to check in with tests failing so this was going to be a huge change. For another, I'd been driving out all this testable design without the strong link the acceptance tests provide to the actual result I was trying to achieve.

I had pretty much turned from a developer into an abstraction factory. Time to revert. (Actually, git checkout -b garbage and commit to the new branch, just in case... :))

Refactoring without thinking about TDD

The second time I tried this I was determined to take little steps. I had no unit tests to guide these steps, just the SOLID principles and the feedback from my existing test suite, which mainly showed my class was too big and was going to grow bigger with future changes (apparently gravity applies to code, too :)).

So I started extracting small bits of behaviour and pushing them down into collaborators (which my existing class new'ed up, violating DI but keeping my code functional). My unit tests now became integration tests (or at least covered a bigger unit), but most importantly I still had my acceptance tests telling me the software still worked. I found it very useful to keep testability in mind for all new classes I was creating, but the design I was refactoring toward was just based on isolating some of the responsibilities my class had accrued.

Once I had pushed down the messiest stuff into a few new classes at varying levels of abstraction, being careful to keep the tests green all the time, I went and back-filled some unit tests for the original class and its interaction with its new collaborators (replacing the poor man's DI with real DI). This left the new stuff uncovered by unit tests, but still safe due to the integration tests. I could then go back to my standard TDD approach for the final push from the new class down to a finished implementation.

The style of TDD I use helps me to decompose problems and abstract and encapsulate data and behaviour in a way that appeals to my limited sense of aesthetics. This meant that before I started the refactoring I was relatively happy with how the design broke the problem down. Switching to purely refactoring mode meant I could keep the same basic problem decomposition and just tinker with the implementation. I didn't really need TDD to drive the change; it was more a case of rephrasing the existing problem breakdown.

Lessons learned

I (re-)learned a few important lessons from this experience. The first was that small steps are absolutely essential whenever you're not 100% confident with what you're doing. Whenever you've taken a big step away from the green bar, it's time to revert and try smaller steps. (A big step from one green to another is fine if you can manage it.)

The second is that I really need to separate my test-driving from refactoring. I'm either wearing my test-driven design hat, or my refactoring hat. Once I've passed my failing test and can see the green test runner bar of happiness, then I really need to stop thinking about tests and start driving using the refactoring process: small, non-breaking, non-behaviour-altering changes.

Come to think of it, refactoring probably shouldn't involve changing tests at all (unless I've made the mistake of having my tests overly-specify an implementation). As refactoring doesn't change behaviour, and our tests are covering the behaviour or result of that behaviour, then our tests shouldn't need to change. Once we switch to altering tests as a part of a big change we're really in the realms of redesign rather than refactoring. Of course, once we've finished refactoring and our tests are still green, we then have some confidence our code is correct and can resume the test-driven cycle and change our tests however we like.

When I asked a question about this on Twitter, Jak Charlton summarised it perfectly: "You can change tests or implementations, but not both at the same time". (That's right, it takes me 1,000 words to say what most people can say with 140 characters. I'd appreciate any verbosity-cures donated to the comment box below. Thanks. :))