picture home | pixelblog | qt_tools

omino code blog

We need code. Lots of code.
David Van Brink // Sat 2006.09.30 11:24 // {code java software architecture}

A Philosophical Snapshot

Introduction

This introduction has been written last.

The intent of this post is to give a snapshot of some coding strategies which I think are good ideas, today. This is of course reflective on the most recent round of development I’ve been involved in. Reading it over, I see that the notions are drawn nearly equally from things we got right, and things that (I believe) we got wrong.

This is my blog and my essay, so I’ll just make these statements as if they are indisputable truths from on high. Sometimes I’ll even try to support them. But I certainly encourage dispute and counterexamples. Comment away!

These are all, broadly, under the heading of API design. After all, what isn’t?

These are also all referring to Java code, though the principles are certainly true in any programming language. Says me.

Offer Power, Not Rules

Make it easy to do the “right” thing. Do not try to make it “impossible to do the wrong thing”. Building your API around “restrictions” rather than “capabilities” invariably results in a cockeyed underlying implementation, infusing anachronistic domain-specifics into inappropriate places. It also can lead towards untestability. For testing you, by definition, need to exercise the functionality “out of context”.

Provisions For The Journey

Consider the entire development flow, not just the final result. (Sometime in the 1970’s Japanese cars all included easily-located jacklift- and tow-points on the frame. American cars still sucked.) For example, if generated code is part of your product, it is an obstacle to quick debugging turnaround times in Eclipse. Consider that as part of the cost. It may be the right solution, still, but is worth considering. Similarly, if your code relies on certain files in certain places, or certain parts which must exist as Jar files, that will have implications to your clients’ interactive development.

Obligations Versus Offerings

  • Obligations are Abstract

Any kind of “plug-in” that your product supports will necessarily have multiple implementations. The thing which unifies the different implementations is that they each satisfy some fixed minimal set of “obligations”.

In Java, a useful way to manage these obligations is by an interface.

If you change the interface, you are changing the obligations. (You can also change the obligations by merely reinterpreting the interface, but let’s gloss over that for now.) Changing the obligations of a plug-in is a serious gesture. Managing that interface carefully is important because any change can have broad effects. A committee of interested parties can be beneficial in restricting unnecessary change.

Once an interface is “out the door”, that is, used by a broad base of implementers, the cost of changing it increases enormously. In general, changing an existing interface (once “out the door”) is impossible, and a better choice is to create a new one, while still supporting the old.

  • Offerings are Concrete

Conversely, a utility library provides a set of features, or “offerings”. Like a plug-in interface, many developers depend on it. However, it is perfectly safe to increase its feature set, as long as the existing features remain unperturbed. Also, in contrast to plug-ins, there will be just one implementation.

In Java, the best way to express offerings is with a concrete class.

The maintainer of a utility library can certainly benefit from client-feedback, but restricting change should not be the primary goal of that feedback. Rather, the client-base should make clear how they are using it and what additional features are desired. The maintainer can then factor that all together to satisfy his clients appropriately.

While it is true that one way to maintain compatibility is to prohibit change of the library, a better way is to ensure that existing features remain operational. This can be done by unit tests. Clients should be free to add unit tests to guarantee that their particular usages remain supported.

If the maintainer inadvertently alters existing behavior such that a client expectation is changed, they can either a) strive to retain the existing behavior or b) negotiate the change with the client.

As with plug-ins, once a utility library is “out the door” the cost of changing it increases significantly. In fact, we can then state clearly that “changing it” is “breaking it”, and one must consider carefully before doing that. (Although occasionally it is still the right thing to do.)

But, unlike “obligations”, it is always safe to add more “offerings”. The next release of the utility library may do more, but never less.

On Plug-Ins and Privacy

This next one is subtle.

A “plug-in” is something that will have multiple or numerous implementations. A “plug-in client” is something that uses a particular kind of plug-in.

A plug-in client obviously must depend on a particular interface for the plug-ins it uses. A plug-in may depend on a certain usage-model or flow. That is, it may expect to be invoked in certain ways. Ideally, this is well-documented, but if not, then the first plug-in client more or less defines it. But, a plug-in must not depend on a particular plug-in client. The classic symptom of this an API like:

public interface IFooPlugIn
{
    public void setX(int x);
    public int getX();
    public FooRecord generateKung();
    /**
     * To invoke sub-plug-ins, we have to give each plug-in a
     * reference to the application.
     */
    public void setApplicationHandle(JoesFirstApplication applicationHandle);
    // Whoops!

}

OK. Right now, you’re all about Joe’s Application. It’s the biggest thing in the world, and the whole company is behind it. Joe’s Application is going to increase shareholder value, and that’s great.

But this API locks you into emulating JoesFirstApplication when you someday (I know you can’t imagine it now) want to write a different application that leverages all those existing plug-ins. Half the value of plug-ins is lost if you’re not decoupled from the client. If there’s some aspect of the client that your plug-in needs, it’s better to create the smallest possible interface which provides it, and pass that instead:

public interface IFooPlugIn
{
    public void setX(int x);
    public int getX();
    public FooRecord generateKung();
    /**
     * Minimal access to discover sub-plug-ins.
     * Clients: your implementation
     * of IPlugInFinder determines the available scope.
     * Plug-in authors: use this to find legally 
     * accessible sub-plug-ins
     */
    public void setPlugInFinder(IPlugInFinder plugInFinder);
}

There. Now we’ve left the door open to create a completely different client that can leverage a body of existing plug-ins. We’re not locked into Joe’s vision forever. You know… just in case JoesFirstApplication isn’t the final manifestation of your product.

Conclusion

I hope, in the future, to look back at this post and think, Good heavens, that’s so basic that you shouldn’t even have to say it! Equally likely, future-dvb will say, What a bunch of misguided nonsense. Only time will tell.

oh, i dont know. what do you think?



(c) 2003-2011 omino.com / contact poly@omino.com