Wednesday, November 12, 2008

You want to know what I think? I'll tell you what I think. Here's what I think: Java Java Java is is is too too too damn damn damn verbose verbose verbose. That's what I think. And I'm sticking to it. So there.

You want to know what I think? I'll tell you what I think. Here's what I think: There's There's There's way way way too too too much much much boilerplate boilerplate boilerplate in in in Java Java Java. That's what I think. And I'm sticking to it. So there.

You want to know what I think? I'll tell you what I think. Here's what I think: If If If Charles Charles Charles Dickens Dickens Dickens had had had designed designed designed a a a language language language, it it it would would would be be be a a a lot lot lot like like like Java Java Java. That's what I think. And I'm sticking to it. So there.

I was writing a small helper class to correctly paste together the CGI arguments to an http service. The idea is simple. Rather than simply appending strings in a kludgy way, you'd collect a number of instances of the objects you want to pass (mostly integers, text, and enums) and the code would magically marshal them in to a correctly escaped URI. No big deal.

Two days later...

Sixteen hundred lines of code distributed in twenty-nine files. Is it any wonder that people get carpal tunnel syndrome? And this is mostly `throwaway' code that abstracts only the small amount of functionality I need. I don't even want to think how awful it would be if I had tried to engineer a full-blown class-based utility.

Of course I was asked “Why didn't you just use the mumble class?” A few reasons, but the primary reason was that the suggested class worked like this:

    FooBuilder fooBuilder = new fooBuilder();

    FooPartBuilder fooPartBuilder = new fooPartBuilder();
    fooPartBuilder.addName ("fooPartName");
    fooPartBuilder.addValue ("fooPartValue");

    FooPart fooPart = fooPartBuilder.createPart();
    fooBuilder.addPart (fooPart);
    .... etc. etc. ...

I wanted something like this:

    Foo myFoo = new Foo (new FooPart ("fooPartName", "fooPartValue"),
                         new BellOption (BellOption.Sound.Tinkle),
                         new WhistleOption (), ...)

What's the difference? As I was skimming the code for FooBuilder (and the code for AbstractFoo, FooInterface, AbstractFooBase, etc...) I saw a note that mentioned ‘this isn't thread safe’. Great. Another thing to think about. The problem with the FooBuilder model is that it is just lousy with state. It starts empty and then you smash some state into it. This is stupid. It begins in a state that no one wants it to be in, so the first thing everyone does is smash the state to something more useful.

They way I wanted to do it was to simply construct the object in its final configuration. The object is immutable and stateless. It is guaranteed to be thread safe because there is no way to modify it. The JVM could keep separate copies all over the place for all I care. And because there are no mutators, the code is about 30% smaller. That's 30% fewer javadoc comments that replicate the argument lists.

Another gripe: Having a database that keeps track of where the source code resides is a good idea. Using the file system as a ‘poor man's database’ for that purpose is a bad idea. To give a concrete example, I wanted to say, formally, that ‘every optional CGI argument must provide a marshaling function’. Simple statement. Here goes:

    public abstract class OptionalCgiArgument {
        abstract String marshal() throws MarshalingException;
    }

But it should be in a file all by itself, which means a copyright notice, a package declaration, and, if I'm being pedantic, a Javadoc comment.

    // Copyright (c) 2008

    package com.foo.bar.baz.utility.api.helper;

    /**
     *  Abstract base class for OptionalCgiArguments.
     *  Ensures that all OptionalCgiArguments provide
     *  a marshaling method.
     *
     * @author jmarshall
     */
    public abstract class OptionalCgiArgument {
        abstract String marshal() throws MarshalingException;
    }

Did I forget the unit test?

3 comments:

Светка-конфетка said...

FooBuilder is not thread-safe.... What a big deal! Anyway, you're using it in this single thread. Moreover, I suppose you're talking about something like multi-part request builder. No need to write such a big post. This builder was created like this long ago. If you're working with it too often, you may create any wrapper you like ;-) Relax. Life is short to be too nervous. Also, remember: you can always leave your job and write in Haskell!

rektide said...

Why add the performance penalty for thread safety if a user may or may not use it? Even if you do impose atomicity constraints, a multithreaded application could still execute out of order, so the multithreaded app still requires its own coherency system. So then what does the re-entrance guard save you? Not much.

As for your object configuration gripes, thats exactly what Dependency Injection and Spring in specific try to solve. The whole "use setters/use constructors" debate is moot when you have configuration tooling that can a) do both b) isolate configuration from code and c) reduce complexity. Dependency injection tries to separate out object initialization and gives you means to say "when I say I want a MyFoo object, give me a new Foo object with these parameters set".

You further appear to be arguing that having less methods inherently makes code smaller. An army of small state mutators rarely affects code size very much at all; add thirty to an object and check for yourself. A good IDE will let you mask the ones you dont care about.

As for java's demands that each class get its own file, your tooling should help you here again. In Eclipse I can say "Give me a new class derived from this abstract class" and it will give me my own boilerplate header, and build empty functions for all the abstract functions I must define. I don't see that the header imposes any kind of maintenance burden, so I'm unclear what cost you see to these requirements.

I do agree: 1600 lines and 29 files seems completely outrageous for a CGI param parser. It sounds like you've been bitten by a library and a lack of dependency injection tooling for using that library. I dont- however- see how these are inherently Java problems. Java is what you make of it, and your own myFoo constructor is indeed valid Java; just not Java supported by the library you've opted to use. Sounds like you've had a rough start with Java, hope it gets better fast.

akuhn said...

$ is a valid identifier name, that might ease your pain.

See For example, spartan programming