Martin Fowler’s Mocks aren’t Stubs article presents a balanced comparison of two different styles of unit testing. According to Martin’s taxonomy I’m the type of chap who prefers “state-based” testing over “interaction-based” testing – that is, I prefer using stubs instead of mocks. And I increasingly encounter a downside of mocks that isn’t mentioned by Martin:
Suppose you’re working with legacy code. There are only a few tests; and the tests you already have are all large and complex, because the objects you’re testing were never designed with de-coupling in mind. You have to make a change to one or more of these objects, and you want to proceed test-first. The approach I’ve seen with increasing frequency (probably due to the easy availability of NMock) is to “mock out” a few of the closely-coupled objects. This allows the TDDer to get on with the work, but does nothing to alleviate the underlying problem of the smelly design.
Here, mocks are being misused, as “smart” stubs. I would much prefer to have seen the TDDer spend a little extra time refactoring the production code so that these tests – and all future tests – became easier and quicker to write. (The many excellent techniques in Mike Feathers’ Working Effectively with Legacy Code will help here.) So when I see mock objects being used as the easy way out of a tight corner, I seek instead to refactor away those nasty design smells.
In Pseudo-OO Design Dadi Ingolfsson talks about that all-too-common phenomenon – a design in which the code and the data are separate.
“This relates to the Microsoft-style of a DAL (Data Access Layer), a BLL (Business Logic Layer) and a UI, where the data that travels through the layers might be, for instance, dumb Customer objects or DataSet objects. The objects in these different layers are very procedural in nature as they usually only operate on data in objects that are sent to them rather than the data being their own. It´s what Fowler called an Anemic Domain Model. This is not OO design even though many programmers seem to think code is OO if it has classes and objects.”
By coincidence, this week I found myself workshopping agile programming with a team whose legacy C++ system is just like this. My approach to gradually improving the situation is to look for instances of the feature envy smell. Bit by bit, as we find each of these smells, we move the code into the objects where it belongs. Bit by bit, encapsulation improves. Bit by bit, the domain model emerges in the code. And very often, as we introduce abstractions to improve testability, bit by bit a hexagonal architecture emerges too.
Why is this important? Why is it bad to separate the code from the data? Because it stops the code changing at the pace of the business. Distributed behaviour and state mean that each piece of business knowledge is also distributed throughout the system. Which means that any new or changed requirement will cost more to implement.
In Approaches to Legacy Code Brian Marick postulates that there are three basic approaches: throw it away and re-start; fix it; or strangle it.
“The term strangler application is due to Martin Fowler. The image is of a vine growing up around a tree, gradually killing it, until eventually the only thing left alive is the vine, roughly in the shape of the original tree.”
He notes that most practical approaches will be one of these kinds, or some hybrid combination of them. And he wonders whether this might be the beginnings of a pattern language.
I certainly agree with Brian that more needs to be written in this area. I’m sure many organisations would benefit from a set of patterns that could help them choose a strategy for dealing with legacy code – and with legacy systems in general. Indeed, I wonder whether this applies more broadly than just to code. Very often I find that the major factor holding back a software team’s productivity is some kind of legacy organisation – structures, processes, attitudes. I very rarely get to scrap and start again, but in future I’ll certainly be paying attention to when and whether I fix or strangle!
I had an experience a few months ago that yet again raised the question of what kind of skills are required to make XP work. I was trying to get a team to adopt TDD, and was having very little success. The reason, it turned out, was that the existing code hadn’t been written TDD. Anyone trying to add a new feature incurred a huge time penalty from trying to get to the point of being able to write simple unit tests.
So I stepped back from TDD and tried to get the team to only produce well-factored code from now on. Another failure, and this time the legacy code was only partly to blame. It turned out that only two of the fifteen people on the team really understood what well-designed object-oriented code looks like. The rest wrote working algorithmic code, and were completely blind to the duplications and strong couplings they had created. For lots of reasons, including timezone differences in some cases, it was impractical to pair with everyone to help them towards an understanding of ‘simple’ code. So I did a few public worked examples via NetMeeting, and still had almost no impact on the code being produced. I guess that refactoring is a design skill, and that it has to be taught, because most programmers don’t learn to appreciate ‘good’ code in university. Continue reading →
A couple of years ago I was working on a project in which a significant portion (say 30%) of the codebase was legacy code. By which I mean that it was written by a chap who had just left the team. He was a ‘clever’ programmer – his code did the job, and the rest of us were afraid to touch it. The coding style (this was a Java project) was intensely algorithmic. It involved a small number of huge classes, each method of which took parameters to configure their behaviour. No-one could touch it because the inter-relationships between the (large) pieces were too opaque, and none of us was brave enough to even begin the process of refactoring towards a more ‘object-oriented’ solution. (One thing that would now ring alarm bells with me: the code failed Mike Feathers’ test, in that it was well-nigh impossible to write unit tests for any piece of the code. In fact, most of the classes couldn’t be instantiated outside of this sub-system’s highly complex nutrient soup.) Continue reading →