Tuesday, May 4, 2010

Test Driven Development and Code Quality

I've been digging into some hairy code the last couple of weeks. It's new code, so there's a lot of testing around it, but it's complex, partly of necessity because it is a hard part of our business, but it seems harder than it needs to be. One of the things I've noticed is that no matter how good or bad it is the tests are better.

Tests are an extra layer sketching out a single scenario over the production code, so they are both more abstract and more specific. Any given test isn't trying to generalise all the scenarios and behaviours of the code being tested, in that sense they are more specific. Also they are at least an extra layer of abstraction over the production code.

It may also be important that tests normally form a topmost layer with no distorting pressure from their caller. By way of contrast consider a web application, the code has a very concrete front end and back end, typically the HTTP interface and a database. Wedged between those things is our poor beleaguered layer of abstracted business logic, all too easily squeezed and distorted by both upstream and downstream pressures.

Production code, the actual program, must encompass all the possible uses, in that sense it must be generalised. I hold that generalisation is next to optimisation as a source of complexity and confusion in code. Any developer looking at a generalised piece of code must assume that it supposed to be able to do all the things that it does: being more specific reduces the things the developer has to take into account when working; needless or accidental generalisation forces the developer to think about things that are irrelevant or unnecessary.

It seems the pressures on production code drive it to be concrete and general, while clarity seems to be served by being abstract and specific, there are two axes here and I think we get them mixed up at times.

And we have a model of abstract and specific code sitting right beside the production code: the tests! But it seems that the quality of those tests isn't feeding back into the production code in a way that makes the production code easier to understand or maintain. The good practice of testing is helping to produce correct code, but not necessarily sustainable code.

But we may have some pieces of the puzzle, some seeds of solutions:

  • Wrap general collections so that there is clearly documented set of operations that hopefully communicate the business meaning of interacting the with structure. Isolate and limit misleading generalisation.

  • Perhaps we need principles such that a test should never use domain vocabulary that isn't also in the code. If the test wants to interact with something, then the name should be coming from the code, if the name is not there then the code is missing an abstraction.

  • Similarly, if the test is creating a composite object, then perhaps that represents an abstraction that is missing from the code. And if the test is wrapping an action or a series of steps in a named function then perhaps that represents something missing from the code.

  • Make your implementation match the way you talk about the business: if your intuition or knowledge of the domain tells you something and the code contradicts that expectation, unless the expectation is actually wrong, then the implementation is bad, because being arbitrarily different from expectation requires additional intellectual effort to work with. (That last one is an insight from Domain Driven Design, emphasising ubiquitous language and consistent with models expressed in code.)

No comments:

Post a Comment