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.
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.
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.
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).
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.)
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.