I recently took part in a code dojo, using the scoring for bowling kata, where we were interested in intention revealing and expressive code. The rules set up were typical, TDD, rotating pairs, no verbal explanation to new pairs, no talking to your current pair. Same old rules, same old results, no surprises. It seems to me that if you want different code to come out of the activity you may need to frame the activity differently.
The same old results of TDD as practiced in the typical code dojo is not a good thing. Write a test and implement a solution to it as quickly as possible. Satisfies each test with the least number of characters possible and in the shortest time possible.
Short and quick lead to producing the easiest solution to come to mind, or perhaps simply the first solution that comes to mind, and basically the most primitive solution. By primitive I mean that the solution is implemented as directly as possible in the primitives of the language.
But it's TDD, the goal is to write the simplest solution. Not to be simple minded about your solution. This depends on defining simple of course, but in my experience that's a measure that should be applied to the result code, not the process that generates it. TDD is not about doing the simplest work that results in something that satisfies the test, it's about doing the hard work of delivering something which is simple and satisfies the test.
Simple becomes a set of principles, some with potential metric support, some more like rules of thumb. Simple implementations for me are those which can't be used wrongly. Simple implementations make explicit the important things, not merely the static things that are easy in whatever language you're using but also the processes and dynamic properties of the system even if you have to twist the language to make them explicit. Simple often means hiding and wrapping primitives so that they don't interact and that the myriad things they might do but shouldn't are cut off.
Simple very often means qualities that are not captured in tests, simple is the judgement of a professional saying that the passing test is just the beginning, diligent professional conduct requires me to attend to these other values.
What I found was that we could have written better code faster in one of two ways, (the code I wanted had a layer abstracting frames, and chaining them together as the game plays). If I'd thought long and hard I could have presented the tests in an order that made the abstraction an immediate and obvious benefit. Or, trusting our professional intuitions and being principled, we could have implemented the highest interface in terms that corresponded to the description of the problem, that DDD approach would have automatically led to a frames based abstraction because that's how the domain experts talk about the problem. Or I could have trusted my little rules of thumb: wrap your primitives, abstract away the language. Perhaps if I'd been able to talk to my partner we'd've been able to establish that quality foundation, but once people started rotating the pressure was on to just follow suit on poor beginnings.
I don't think I've ever written code where an implementation that jumped from the highest level interface straight into code using raw language primitives was the best result. When working with code like that, grief and suffering are typical.
TDD is a process that helps me write good professional code by freeing me up from tracking whether my code is functionally correct, instead I can give more energy to all the other professional values that need to be embodied in my code. But this is my heresy, TDD says nothing about the quality of the implementation, but a poor quality implementation is a terrible cost a company.
The problem with that common type of TDD kata was that it punishes us for attending to code quality and architectural concerns (remember those other things in XP: coding standards and the system metaphor, they acknowledged that TDD needs balance).
So how do you get good, expressive, professional, intention revealing code? No guarantees, but you need at least to be able to identify good from bad, you need the boldness to say no to the bad, you need to be allowed to implement better than the poorest conceivable solution.
TDD is a game, it rewards getting stuff done quickly and crudely, it also rewards getting things that function. People who want the process to deliver quality are sadly disappointed, people who like to tick things off and generally score points can use it to churn out piles of muck. People who feel that quality matters often end up ignoring it when forced to practice with rules mavens and game players. What's really need is counter balancing games that rewards the other qualities of good code, qualities that have great business value but may not be easily measured like maintainability or job satisfaction. Such games may look like leader boards showing the latest model code for the team to strive to excede, perhaps as an ongoing vote or outcome from retrospectives. It may be a checklist of points that need to be judged as part of code review. TDD doesn't stand alone.