What does "the simplest thing" mean?
Agile advocates often tout the adage do "the simplest thing". Indeed I've heard this phrase used as both justification for and criticism of agile methodologies (XP in particular). However, many agilers and non-agilers alike often have trouble knowing just what is the simplest thing. I often use the phrase within a given context, meaning the simplest thing given a number of healthy practices. Generally these include test driving, loose coupling, use of patterns) and other sometimes nebulous practices. Which practices are important comes mostly from experience, and I often find it hard to articulate just what practices are important.
To help, let's look at doing the simplest thing for a simple example, linking to an external API, in this case an XPath processor. The example comes from a recent project, actually, a lot of my recent rants come from this project. This project linked to an XPath API directly in over 300 places and when we needed to replace the implementation with a faster and less memory intensive processor, we were forced to change all of these lines of code, increasing the chances of new problems and regression.
Simplest Simplest
In order to hook into this API, I simply embed a call to the API directly in my code.
class ElementFinder { void findElement() { Element element = XPathAPI.findSingleElement("..."); } }
I'd say that this is the absolute simplest thing that you can do to link to this API. It's quick and does what is desired, but what are the downsides?
- Tightly coupled to implementation - If I was to use the same code in 300 classes, and then find I needed to change APIs (say as a result of performance testing), I'm stuck with changing 300 lines of code.
- Untestable - I can't test that my code does the right thing as I'm calling directly into the API I want to use. For example I cannot drop a mock in during unit testing.
Uncoupled Simplest
The next step in de-coupling our example is to remove the direct call, and hide it behind an API of our own.
class BetterXPathApi { Element findSingleNode(String xpath) { return XPathAPI.findSingleNode(xpath); } } class ElementFinder { void findElement() { Element element = BetterXPathApi.findSingleElement("..."); } }
The beauty of this is that whenever we want to call the external API we call through our own version, so if we want to changed the backing implementation, we just need to change one line. But what are the downsides?
- Tightly coupled to implementation - Even though we've pulled out the call to the external API we cannot change the call without changing code and I can't swap out implementations easily.
- Untestable - We still cannot test that our code does the right thing as we can't (easily) set a mock in place of our
BetterXPath
code.
Interface Simplest
To make this more adaptable, we can hide our implementation class behind an interface.
interface BetterXPathApi { Element findSingleNode(String xpath); } class XercesXPathApi implements BetterXPathApi { Element findSingleNode(String xpath) { return XPathAPI.findSingleNode(xpath); } } class ElementFinder { void findElement() { BetterXPathApi xpathApi = new XercesXPathApi(); Element element = xpathApi.findSingleElement("..."); } }
This version is a lot better than the last, the implementation is abstracted behind an interface allowing us to swap implementations easily. But what are the downsides?
- Untestable - We still can't drop in a mock in place of our implementation to test that we're doing the right thing.
IoC/Dependancy Injection Simplest
To make this even more adaptable and easily testable, we can allow implementations to be dropped in using IoC or dependancy injection (we'll use the constructor form to make it easier).
interface BetterXPathApi { Element findSingleNode(String xpath); } class XercesXPathApi implements BetterXPathApi { Element findSingleNode(String xpath) { return XPathAPI.findSingleNode(xpath); } } class ElementFinder { BetterXPathApi xpathApi; ElementFinder(BetterXPathApi xpathApi) { this.xpathApi = xpathApi; } void findElement() { Element element = xpathApi.findSingleElement("..."); } }
What we've done is decouple our construction of the implementation with its use, delegating the construction to some other entity. So while this may seem more complex, it allows us to drop in implementations to suit our needs for example during unit testing or at runtime using an IoC container.
So now we've gone from one class to three and increased the lines of code from five to seventeen. But we've loosened the coupling between our code and the external API we're linking against, improved its testability and clarified its intent. So while this may not be the simplest simplest code, it's worthwhile taking a little extra time to build in some things which increase the health of the code.
0 Comments:
Post a Comment
<< Home