Injecting Those Dependencies 171

Posted by Brett Schuchert Mon, 22 Mar 2010 21:00:00 GMT

Last week I was teaching a class with a good group of C++ developers in Chicago. They were up on C++, current standardization efforts, virtual machines, performance analysis and tuning. At one point I mentioned a metric for virtual method dispatch on the JVM and one of the students used my numbers to work backwards to determine the number of instructions on different processors.

I was teaching our Working Effectively with Legacy Code class. Michael Feathers usually teaches this class, but he was busy (probably working with legacy code somewhere, which, ironically, is why he wrote the book – to solve the problem once and for all and to stop working with legacy code).

In an early project we have some problem code (the class was in Java, but we discussed C++ quite a bit):
    public void addEvent(Event event) {
        event.added();
        events.add(event);
        mailService.sendMail("jacques@spg1.com", "Event Notification", event
                .toString());
        display.showEvent(event);
    }
The problem with this code, however, comes from what happens in the constructor:
    public Scheduler(String owner, SchedulerDisplay display) {
        this.owner = owner;

        mailService = MailService.getInstance();
        this.display = new SchedulerDisplay();
    }

The display object is associated with a real device (OK, not in our simulation, but you get the point). We need to fix that dependency.

So we traditionally talk about a few ways to address that:
  1. Create an interface and then have a concrete and test-double implementation
  2. Create a test subclass
Those are a few typical ways to handle this problem, but given that the students also worked in C++ and were very concerned with performance (and dynamic binding), so we discussed more alternatives: 1. Link seam – build the system using the original SchedulerDispaly header file but link in a different version of the class. 2. Template parameter (compiler seam) – introduce a template parameter and compile in the dependency rather than link it in.
#pragma once

template<class D> class Scheduler
{
public:
  Scheduler(D &display);
  ~Scheduler();
  void handleEvent();

private:
  D &display;
};
So here are 4 ways to remove the dependency (working bottom up in a sense):
  1. Link seam: link in a different version of SchedulerDisplay
  2. Compiler seam: use a template parameter
  3. Dynamic seam: create a test subclass that removes external dependency
  4. Dynamic seam: extract an interface from existing concrete class, and make Scheduler depend on the new interface.

For C++, the first option might be a good option if it allows a very quick build/test cycle. Imagine using a class that brings in several unwanted dependencies. By making a test version replacement and linking to it, you might be able to get up and running quickly. On the other hand, you have to have a custom build target, which is really outside of the language.

The second option is good for C++ and maybe C#, but not Java. Java’s implementation of generics is so bad, that it requires an interface for this particular case, so you end up with the final option anyway. Using templates puts the seam into the language rather than the build system. It will affect clients because either:
  1. They will have to provide a template parameter
  2. Or, by providing a default value for the template parameter, clients will be aware of a class they were not previously aware of. Still, a viable option, but it will increase build time at least a little.

The third option may not work in any language without changing the original problem code. Imagine a no argument constructor that performs some static initialization. Without changing the constructor, you cannot guarantee that this approach will work. In any case, the problem as presented requires a change since the constructor was doing the initialization of the display; it did not allow for dependency injection.

The final option requires a bit more change, but is pretty flexible if you can afford the virtual method overhead. In Java this isn’t much of an issue (for a number of reasons, mostly related to effective JIT optimization). The cost is a bit more of a problem in C++ because introducing a first virtual method is a big deal. Personally, I don’t work on embedded systems, so I can generally ignore that problem. But this is not something to simply ignore, context does matter.

So which option is the best?

None of the above.

As a developer, you should probably be aware of all of these options (or maybe not the template option of you’re strictly a Java developer). Then, when you are faced with a legacy coding problem, you’ll have more tools from which to choose.