When Code Outgrows its Design

Posted by Berin Loritsch Thu, 23 Aug 2007 18:52:00 GMT

I’d like to tell you I get everything right the first time, but then I’d be lying. However, what if I told you I got it wrong on purpose? Would you believe me? That said, the fundamental premise of YAGNI (You Ain’t Gonna Need It) is that you do the simplest thing that can work, and refactor later. The principle behind continuous design is that you put in enough structure to work, and refactor and modify as you go on. As you learn how your application’s structure is growing, you find places where you’ll need to help it out a bit.

One such example is the Controller base class in the Java ActionPack code. I started out with a base structure that I need an instance of a controller, populated with the current request and response objects—similar to how JSPs work. Then I started adding some helper methods to make working with controllers easier. Today, I have a mixture of an object factory (and its supporting structure) and helper methods and attributes. The Controller code is getting pretty big, and it may be hard to draw the line between support and factory related code.

So what do you do? The answer is obvious, you refactor. Now is the time to separate the factory code from the Controller base class. According to the GoF Design Patterns book, the proper way to do a factory is to have a factory class whose whole responsibility is to create instances of a different class. In short one objects makes a whole bunch of another object. So why didn’t I start there? In the beginning the Controller class was primarily a factory itself. We really didn’t need to separate things. YAGNI. Now that the code has grown, and we’ve added a lot more support, the time has come to use the factory pattern as specified in the GoF book.

Designing for Change

It should be obvious, but one of the chief principles of object oriented programming is to hide the details of how to do things, and you just tell objects to do a job. You only make available what you intend users of the object to use. That way we can hide changes to the algorithm without breaking compatibility. For example, now that I’ve identified a weak point in the Java ActionPack code, I need to be free to fix it without breaking every web application using the software. Part of the reason for having the Dispatcher and Controller classes in the same package is to provide package access to the factory methods on the Controller class. That means I can safely make the change because that part of the API was never made available to the average user.

Both public and protected methods are usually exposed to the user in some way. Either they are helper methods such as “redirectToAction”, or they are meant to be accessed from anywhere. Package protected (a Java unique feature) and private methods are typically where the guts of things live. They are hidden APIs that can be adjusted as you wish without fear of affecting users.