With code like this, reasoning about it is really hard. The particular area I struggle with is capturing all of the second order dependencies; assumptions about the dependencies that aren't immediately appaernt. For example, the Law of Demeter (
a.b.c
) is violated, then I need to capture that knowledge somehow that a
must return a non-null b
. Once I've got this information, it's a lot easier to act upon it. (perhaps I just introduce c
on the interface of a
and then I've broken a dependency relationship).In medicine, a technetium scan is a technique where a radioactive isotype is injected into a patient, and this trace allows you to visualize what's happening internally. We can use mock objects to simulate this technique with code. A mock object provides a simulated object that you can inspect to see how it is used. It is the radioactive isotope object, injected in and observed.
I found it super useful to capture hidden behaviour about a system. As a simple example, let's take a hideous class like this.
class HorribleMess { HorribleMess(Foo foo, Bar bar, Baz baz) {} DoHorribleThings() { var xyz = foo.getXYZ(); bar(xyz.abc(), xyz.def()); } }
There's a slightly hidden dependency here. HorribleMess depends not only on
Foo
, but on the type returned by Foo.getXYZ()
. Not only that, but it also depends on the results of methods invoked on those objects. In this example, this is dead simple to see, but in a real legacy code base finding this information is a real challenge.This is where I think mock objects can help. The way I've found useful is to create a unit test class and just try to instantiate the object passing in mocks wherever possible. If you can construct the object immediately, great, there's no dependencies on constructors and you can start to explore how the methods work. By using strict mocks you can force yourself to spell out the dependencies in the test (the test will fail unless you explicitly set the response of the mock object).
The pattern my tests often end up with is a series of documentation about the dependencies of the class. This is similar in spirit to the effect sketching advocated in Working Effectively with Legacy Code, but with strict mocks it has the advantage of being more difficult to make a mistake. It also serves as a living record of hidden dependencies for a particular class.
class HorribleMessTest { // 1st order dependencies MockFoo mockFoo; MockBar mockBar; MockBaz mockBaz; // 2nd order dependencies MockXyz mockXyz; void Test() { // 1st order mockFoo.When(mockFoo.getXYZ()).Return(mockXyz); // 2nd order // setup on mockXyz new HorribleMess(mockFoo, mockBar, mockBar); } }
In legacy code, I often find that there's three or more levels of dependencies. Once these are explicitly spelt out you have a trace of a particular execution path through the code and you can start to feel slightly more confident about changing it.
The spelt out dependencies often immediately suggest the refactoring needed to make the solution cleaner. I've found remove the middle-man to be a great first step in eliminating the multiple dependencies.
I do have some concerns whether this'll continue to be a useful technique in the future. Perhaps this will create too much baggage in the code base (in terms of tests needing to be kept up to date). Time will tell!