Code hijacking

Most good software development practices lead us to modular, testable code, with minimal dependencies. We like orthogonality in our classes and methods, with small APIs, and with clever composition to avoid redundancy. Such code is not only easier to build and reuse, it is easier to understand.

The Java standard toolkit is a good model to study, not because it is perfect, but because it was designed as a toolkit first of all. If you find your personal toolkit developing strange and wonderful patterns unlike anything in the SDK, then you might want to ask why. It is improbable at this late date that your domain is unlike anything ever encountered before.

§    Not invented here?

I've seen some impressively monolithic code over the years. But there is a special kind of pointless complexity that I can only call hijacking. The desired functionality was already available, but someone decided, for no easily defended reason, to add dependencies on their own code.

Sometimes there is suspicion of code written by unfamiliar developers. The code might work already, but it isn't under the direct control of the current team. The safest course is to reinvent it, right? Well, that might be too much work, but there are other ways to make that code your own.

§    The unnecessary fork

I recall two teams working in offices just down the hallway from each other. One had a very handy general-purpose utility that I still have not seen available in any public repository. Many methods returned a value with a method equivalent to Foo getFoo(String key). The key could be checked beforehand, so it threw an exception if the key was not defined.

Another team began using this code and requested a simple enhancement. This team wanted a method that supported a default value, like Foo getFoo(String key, Foo default), and so would never throw an exception. The original authors refused, so the second team copied the code into their own repository and made that simple upgrade. For ten years, those two versions remained in use, and were not reconciled. Bugs were fixed in one, but not in the other.

I later asked the second team why they did not simply define a static utility class of their own that checked the key and handled the default. This practice is common in the SDK, with classes like java.util.Collections and java.util.Arrays. (In this case, static Foo FooUtil.getFoo(FooContainer c, String key, Foo default)). The response surprised me: they had not considered that alternative. Then I had to wonder. Did they spend very much time trying, or was control of the code more important?

One developer compared this practice to the way a dog marks territory.

§    Handholding, or handcuffs?

Then there is the unnecessary wrapper. There may be a standard API already available in your language's toolkit. It is not possible to fork that toolkit, but you can declare that the standard behavior is unsafe for your fellow developers. Define a "convenient" wrapper that manipulates arguments and results in some fashion, then delegate to the standard service for the real work. You're just offering a helping hand, to keep everyone else out of trouble.

There may be countless tutorials and examples on the web for the standard toolkit, but your custom API nevertheless claims to be easier to use. The docs may be sketchy, but you do have one example already prepared.

One particular group began with their own version of logging before Java provided this as a standard service in java.util.logging. They then retrofitted their custom logging to delegate to the standard logger. They ended up with an API of 34 public members, instead of the 48 provided by the standard. So in some sense it was simpler. They also gave up the use of property files and internationalization, and easily lost track of the logging namespace. Considering how much effort Sun put into designing this API, it would be surprising if a mere wrapper could improve it much. Years later, this unique wrapper was still defended as "more familiar" than the one known to Java programmers everywhere.

If a standard package defines an an interface, then you can force all users to extend your partial implementation of it. Make sure your APIs accept no other implementation. Why? "To ensure that clients implement the interfaces the correct way." Seriously, that is the justification I saw for a very complicated decoration of the javax.naming package. As a result, every client was forced to understand the internals of that partial implementation, and prevented from extending any other implementation. The wrappers could have accepted a smaller interface containing only the unimplemented methods, and the custom behavior could have remained private. But the authors somehow overlooked that alternative. I never did discover any actual functionality provided by this wrapper (except for some caching that inevitably needed to be managed elsewhere).

§    Hijacking entire projects

If you really want to get in everyone's face, then try extending or wrapping something that everyone in your company needs to use. Unfortunately, no one will let you customize String or Double, but I have seen examples almost as good, such as StringValue, and DoubleValue. Make sure your APIs only support custom objects as arguments, even if you could easily accept standard alternatives. With any luck, they will propagate throughout the system.

Do you just want to make sure everyone compiles against your code? Offer to manage the way a process is started, or the way services are initialized. Force everyone to register with a centralized service, preferably a global singleton. Do not let anyone execute code without this service framework, even for tests. Avoid interfaces so that stubbed services are impossible.

Want to mark your territory even without a compilation dependency? Then declare a non-standard idiom for your language. In java, you might want to change the policy on exceptions and force all clients to catch unchecked exceptions routinely. Create your own special hierarchy of exceptions, even if existing ones could serve the same purpose.

Or declare that standard constructors and factory methods are insufficient. To participate in your framework, clients must allow their objects to be instantiated in some very non-standard way, using reflection, rigid constructors, special initializers, and runtime properties in XML. Even when compile-time dependencies remain, you can justify the extra complexity for "flexibility." (No, I did not just imagine such a system.)

§    Taking hostages

So how do you persuade other groups to cooperate? Have a troubled project? Already burned through your schedule without compelling results? Sometimes you can promise some reusable infrastructure, a devkit, or a framework that will "help" other projects with similar problems.

Find some projects with essential deliverables and make them dependent on yours. It is best if a manager orders this dependency on general principles such as "leverage," "to avoid duplicating effort," so that you do not have to defend more technical reasons. If their project was too critical to fail, then now yours is too.

Programmers might ask annoying questions like "What problem does this solve?" Do not invite them to meetings.

Before you know it, you will have a special programming culture all your own.

Bill Harlan, June 2009


Return to parent directory.