The importance of keeping the metaphor pure

Kent Beck and the XP guys have a lot to say about the ’the system metaphor’. In XP the metaphor replaces what most other methodologies call ’the architecture’. It’s a single, coherent, view of the system. It gives you a single story from which to choose the names of things in the system. It helps you communicate about the system in a consistent language that even your users can understand.

Refactoring code to keep true to the metaphor is as important as refactoring to remove bad smells.

Sometimes you’ll find that as requirements change or your understanding of the problem improves the names that you’ve already chosen for things aren’t quite as accurate as they could to be. During the first few iterations of the system a method called StartPlaying() may have been spot on. As later requirements are implemented you may discover that the work that’s being done in StartPlaying() isn’t really what happens when the user starts playing; it’s actually what happens when they sit down at the gaming table.

Taking the time to fix up the names so that they’re true to the newly refined metaphor is vitally important. Failure to do so leaves you with code that says one thing but means another. You’ll begin to find that the metaphor is broken, it isn’t as easy to communicate any more; you have to keep qualifying concepts that should stand on their own. As soon as you find that you’re qualifying concepts and taking time to explain the metaphor you need to refactor to restore the value of the metaphor.

Say we were developing a gaming system. The first few user stories might be aimed at getting the initial framework in place. The game play itself wouldn’t be developed until later iterations. In the initial stories a user plays a game by joining a table. The code is developed according to the metaphor and names are chosen. The iteration is complete and we move on. A user should be able to select which available seat they wish to sit at when joining a table. We add seats to the table and allow the user to select where they want to sit when they join the table. Now we begin to add the actual game play and find that in this story a game can only start when two or more players are sitting at a table. We start to add code and find that our metaphor is broken. We have used ‘playing’ to describe what the user was doing in the earlier stories. We now discover that the user wasn’t playing at all, merely sitting. The user doesn’t play until a game starts whilst they’re sitting. We need to adjust our names to match the reality of the refined metaphor. Failure to do so will leave us with code in which a user can select a seat to play at a table and when the game starts they play…

See, it’s getting hard to communicate about the code already… After a quick bit of refactoring to adjust the concepts expressed in the code (perhaps renaming half a dozen methods and a few variables) we have a system where the user can select a seat to sit at and when the game starts they play. The metaphor works again, we can communicate clearly and we don’t have to qualify our concepts.

The important thing to realise is that this isn’t wrong. It’s not bad. Sure it would have been nice to have chosen the correct names when we implemented the original stories, but we didn’t. Perhaps we didn’t understand the requirements fully at that point, or perhaps the change is due to emergent requirements that may never have come out if we hadn’t delivered our initial iterations. It wasn’t a mistake. It was just the result of using a simple design that addressed the user stories that we were implementing and no more. When the time came to implement more stories and we found that we were stretching the metaphor we refactored to bring the concepts that we were expressing in the code back in line with the newly enlarged problem space.

The mistake would be to leave the code slightly out of step with the metaphor. You would waste extra cycles qualifying your communication with other developers and users. The code would be harder to navigate, because concepts in the problem space no longer mapped cleanly to solutions in the code. And most importantly the code will have started to decay.