Don’t be lazy

I’ve seen far too many bugs lately involving lazy initialization.

In theory lazy initialization might seem a good idea. Why do extra work up front which might not be needed?

The problem is that it is simply too easy to introduce bugs when state is not completely loaded after construction. For example, a project I recently worked on made extensive use of init() methods to lazily load state on demand. The constructor would not do the initialization. Instead, each getter method would check whether initialization was complete, and if not kick off an init() process before returning data.

The reasons for this were fairly convincing. The loading of the state required a call to a database, which was slow. But the problem was that this lazy initialization pattern soon spread throughout the codebase like a rash. Soon it was rare to find a class which was guaranteed to be ready for use after construction.

Still other classes used lazy loading as a speculative performance optimization. Table models received events which invalidated their internal state. Instead of recalculating immediately, they propagated events to their listeners, delaying their own recalculation. The models would recalculate only when a client class called a method to ask for data, and forced a the model to check its state and recalculate. Again, it sounded good – why take on the overhead of immediate recalculation if your class has no active listeners?

The original coders left. The code base gradually expanded to more than a million lines of code. New programmers arrived. Predictable problems emerged.

In many cases, the new programmers would simply fail to realise that an object was not ready to use until its init method had been called. Init methods had become a part of some key abstractions – although in the vast majority of cases subclasses did not actually require any special initialization. So most objects ended up with an init method, just to cater for the few places where it was really necessary. Confusion reigned as to when and where initialization should take place.

Interfaces changed and classes needed to be modified. It was all too easy to forget to check the initialization status when adding methods to existing classes. Strange errors occurred in the table models. The data had changed some time before, but since the recalculations were delayed it was hard to work out from the logs what had actually triggered the problem. Sometimes the original coders had made mistakes and forgotten to check the initialization state for some methods. In other subclasses had overridden methods and forgotten to include the check. Problems could emerge quite unexpectedly, such as when the ordering of client class method calls changed. This often caused strange data corruption or null pointer exceptions at runtime, which brought down components.

The basic OO tenet that an object should be ready for use after construction really does make a lot of sense – try very hard to avoid lazy initialization wherever possible. It may seem clever, but it probably isn’t. If it seems that lazy initialization is the only way, reconsider every alternative design. Simplicity is key, and lazy initialization or lazy recalculation is rarely, if ever, simple. You’ll likely save yourself some headaches in the short term, and in the long term you’ll save even more for those who inherit the code once you have moved on.


You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

AddThis Social Bookmark Button

Leave a Reply