Pass Around Ginormous Context Objects

DZone 's Guide to

Pass Around Ginormous Context Objects

· Java Zone ·
Free Resource

This question was submitted by one of the readers, and it shows a common questions people ask when they are coming up to speed when trying to write testable code….

About “Pass[ing] around ginormous context objects” (http://misko.hevery.com/2008/07/24/how-to-write-3v1l-untestable-code/) – this situation comes up for me when [I] trying to get configuration settings into my application. I want objects deeper in the system to have some configurable values which come from outside the system, and I don’t want everyone along the way to know about what those configurable values are. How do you deal with this case? I’m thinking about a collection of console utilities which take settings like number of retries for different actions, timeouts to allow, logging levels, and other things. Some settings will be of interest to many components and others only to one, and it seems like if I decompose them then whomever handles the construction will have to know a lot about what settings classes know about and don’t know about.

// I would hate the class constructing the
// FileCopier to know it is interested in
// all of these!
FileCopier copier = new FileCopier(logger,
           copierSettings, generalTimeouts);

// I end up doing this:
FileCopier copier = new FileCopier(configuration);
// and inside..
FileCopier(Configuration configuration) {
  X = configuration.GetSetting(“X”, DefaultValue);

Lets look into each of these in turn:

  1. “I would hate the class constructing the FileCopier to know it is interested in all of these!” Why is this a problem? as discussed in ‘Breaking the Law of Demeter is Like Looking for a Needle in the Haystack‘ When you ask for things explicitly it makes it easy to pass in test-doubles for these dependencies and hence it makes the code testable. Not only is the code testable it is easy to understand. If I am new to the code-base I look at the constructor and I see that logger, copierSettings and timeouts are things which I may be interested in. On the other hand passing in a Context object it is hard for me to know which parts from the Context object the class-under-test is interested in. It also makes testing harder. Now I have to create Mock Context and override all of the getLogger(), getCoppierSettings() and so on for these methods to return the real test-doubles of interest. But How do I know what needs to be mocked out? I need to read the code, I can’t just look at the constructor and know. But there is more: Suppose I want to reuse FileCopier class in a different project. In that case I need to reuse all of the compile-time dependant classes as well. In this case it is the Context. But context is a kitchen sink and it knows about everything in your application which means I have to resuse those objects as well. So the code with Context objects is not reusable/re-compilable.
  2. The place where our mental model diverged is in this sentence: “I don’t want everyone along the way to know about what those configurable values are.” The fear is that in order to call the constructor the caller will have to know about the parameters, and its caller will have to know and so on. We talked about this in ‘Dependency Injection Myth: Reference Passing‘. However, we are making one more mistake. We are mixing object creation (and look up) with application logic, see: ‘How to Think About the “new” Operator with Respect to Unit Testing‘. The basic mistake people make at this point is that they assume that the object construction graph is same as object instantiation graph. This assumption is mostly true only if you sprinkle new-operators with your application logic. If the new operator is removed from application logic than your application has two phases. Phase (1) wire the objects together in order to instantiate the application. In Phase (1) all we do is call the new operators and pass in the right dependencies. We do not call any methods! In phase (2) we execute the application, here we stay away from the new-operators and call methods so that the objects can collaborate. Once the application is constructed we no longer need to pass the configuration information around. We simply execute methods on FileCopier.
  3. Loging is a Singleton and we have discussed Singletons here, here and here. The root problem is that Singleton (the design pattern) is the global state and global state sucks, from testability, maintainability, and few other -bilities. So should we pass in the Logger through the constructor. Well that depends… If you want to assert that under some conditions a specific log message is produced than you have no choice but to pass it in. On the other hand, if you just want logging, and don’t want to test that it works correctly, than you can do Logger.getLogger(Class.class). This does not make Singletons OK, but it is benign-kind-of-evil. The reason is that with Logger the information only flows one way: Into the Logger. Your application does not read data from the Logger and more importantly does not behave any differently depending on if the Logger is turned-on or turned-off (It behaves different only in the sense that things don’t get printed, but not in the sense that your application now computes a different answer.) Most Singletons do not fall into this rare category where we push data only in, and hence most Singletons are a nightmare to test.

NOTE: when I say Singleton I mean the design anti-pattern with a global instance field as in private static Singleton instance = new Singleton(); This is what makes the Singleton a global. To be contrasted with singleton (lower case ’s’) as in a single instance of something without a global instance field.

From http://misko.hevery.com


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}