Software Development

Legacy Code to Testable Code # 12 : Instance Constructors

So we talked about static constructors and how to go around them. How about instance constructors? Are they innocent or guilty of the same crimes as their static brothers?

I think Java got the terminology right (although not the implementation). What we call a “Constructor” is different than an Initializer. A “constructor” instantiates the object, and an “Initializer” initializes it. These are two operations.

However, in most languages, we do both together. We see a constructor as a place in the code to initialize the object. There’s nothing wrong with that.

Almost.

Sometimes the code in the instance constructor does things that can render the object un-testable. In order to understand why, we need to understand object construction.

Hiding behind a simple “new”, are implicit operations: allocating memory for the object, including building the vtable and the rest of the methods . Only after that, the code in the constructor is called.

Of course, you cannot mock an object until it was constructed. After all you need a reference to the instance handle before you cam mock any of its methods.

Some power-tools can help you choose if to call the constructor code, or just do the allocation. It may be a good enough solution for the problem.

For example:

public class Cache
{
    public Cache()
    {
        InitializeData();
    }
}

Imagine our Cache constructor causing so much problems, we can’t just create an instance without the proper environment. That is a problem to any mocking tool that uses inheritance (or just manual mocking), because the base constructor will be called before it reaches the mocked constructor. We cannot change the behavior of InitializeData whether it’s public or not.

Power tools help if they can override the constructor altogether. However, even they cannot help us in a case like this:

public class Cache
{
    public Cache()
    {
        if (FromDB())
            InitializeDataFromDB();
        else
            InitializeDataFromFile();
    }
}

The FromDB method is an instance method as well. Remember, before the object was created, we can’t access any of its method table. So we can’t mock any method, including FromDB. if it’s called by the constructor.

That means we can’t choose whether to initialize from the database or a file. Once the constructor is invoked, it will run all the way with the actual code.

We can either roll with the constructor as is, or not call it at all. If the constructor is not called, we suffer the consequences of an object that was not initialized at all.

There’s an obvious solution for that: Separate construction from initialization. Like with the static constructor issue, introduce an explicit Initialize method:

public class Cache
{
    public Cache()
    {
    }
    public Initialize()
    {
        if (fromDB())
            InitializeDataFromDB();
        else
            InitializeDataFromFile();
    }
}

This way, we removed the obstacles from the constructor, and after the object creation, we can mock any of the methods. The cost is that the consumer of the object now needs to call the Initialize method.

There’s another slight OO design issue with this solution. An object managing itself, should take care of its initialization, and not depend on its caller to do that. If we used TDD, we’d probably not create a separate method, because we would see the separate initialization call as redundant. We break the design principle, in favor of testability.

The things we have to deal with in software.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button