Sunday, January 29, 2006

I Don't Get Spring

Preface: Please note that this entry is well over a year old. I've since addressed all of my concerns with a new framework named Guice. Try it out. You'll find a direct comparison between Guice and Spring here.

By "Spring" I mean the Spring Framework, specifically the dependency injection container. First off, I mean no offense to Rod et al (you're all good people), but frankly I haven't been able to get my head around your framework. Even worse, I've noticed what I consider to be a dangerous and blind increase in the rate of Spring adoption. I've yet to read a critical article or book on Spring. It seems like everyone loves Spring except me; am I missing something? Maybe Spring adoption is a knee-jerk reaction to J2EE. "J2EE is bad, and the Spring guys say their stuff is better, so Spring must be good." It doesn't work that way.

Second, I'm talking about Spring specifically, not dependency injection. I love dependency injection, and I follow the patterns every day. Good riddance to service locators!

I can't tell you how many times I've had a developer tell me, "I used Spring on my last project and it worked really well," without being able to articulate what exactly they like about it or how it actually helped them. So far as I can tell, they like setter injection because it makes their code more flexible and testable, but that doesn't necessitate Spring. I guess for those who don't have the "dependency injection" mindset ingrained, using a framework that encourages (forces?) you to do the right thing helps. This encouragement doesn't outweigh the laundry list of negative impacts Spring has on your code though.

The Spring folks openly snub their noses at J2EE, but from what I can tell Spring isn't exactly lightweight and simple either. The Javadocs are unnecessarily overwhelming. Does all of this really belong in a user API? At least J2EE cleanly separates API from implementation. Spring advocates tout that Spring doesn't "touch" your code, i.e. you don't have to implement any Spring-specific interfaces (with the exception of life-cycle interfaces, etc.). News flash: the XML configuration is my code, and from what I can see it often ends up constituting a lot of code, all of which is Spring-specific.

Why would I want to express all of my dependencies in XML? Do I use Spring for all of my objects or just the coarse-grained ones? From what I can tell, neither the Spring documentation nor any Spring articles give any solid advice in this area. Am I supposed to use Spring to create all of my objects? Do we not like Java? I want to validate this stuff at compile time, load time at the latest, not while I'm running my code. Does Spring support that?

Obviously I want to load some dependencies like JDBC driver implementations dynamically (i.e. without requiring compile time checking), but these make up a small fraction of the dependencies in my system. What about the rest, the majority? I'm using a strongly typed language; why would I want to throw all of this perfectly good type information away? If I wanted that, I'd use Ruby. Doesn't the Spring configuration start to look suspiciously like writing Java code in XML? Is Spring for developers who aren't comfortable with Java? I'm not convinced adding a layer of XML makes things any simpler.

Back to dependencies on Spring APIs; don't I have to call the container to create my first object (assuming the rest of the Spring-managed objects in the graph are injected)? I want some way to check that the object I'm requesting is the right type at compile time. I don't want to cast. Ever. Do I really even need a container? In Spring you look up objects using a unique ID. I assume most developers define a String constant for the ID in their Java code. That doesn't help me keep the ID value in my java code in sync with the value in my XML. Why do I need both this ID and a container? Why use two objects when one will do? Should we really group all of this information together into a container? Do Java packages and classes not suffice?

It bothers me that I have to reference Spring implementation classes in my XML configuration. I don't care about the plumbing. In Spring's defense, I've heard they have so more concise, domain-specific XML coming in 2.0, but I haven't seen it yet. Why didn't this come up sooner?

What's with all the inheritance? And the permutation upon permutation of long winded class names? Not my style.

Where's the JDK 1.5 generics support? I know you have a lot clients running 1.4 and even 1.3, but that's what branching is for. Generics have opened the doors to all sorts of new possibilities for frameworks like this. They're my favorite new JDK feature. Embrace them.

Have you ever looked at how much work Spring does every time you create an object? I want fewer instanceofs at runtime and more Class.isAssignableFrom()s at load time. Not that the internal implementation matters that much to end users, but it works as a litmus test for the quality of the rest of the framework. A good refactoring to the bean creation pipeline would result in easier to follow and more performant code and would enable reuse without resorting to so much inheritance.

What's with auto-wiring? Does anyone actually use that, or is just another notch in the feature headboard?

How does Spring handle scopes? I heard it will finally support HTTP request and session scopes in version 2.0. I was surprised to here that it didn't support this already. What have Spring users been doing this whole time? Does the new version enable me to define my own scopes? For example, what if I want a "conversation" scope like Seam supposedly supports?

Let's not get into the MVC or AOP frameworks. [Un]fortunately I'm not using a dependency injection container at all at the moment. Don't think PicoContainer is getting off easily. It has most of the same problems, but I think Aslak and Jon have moved on to Ruby pastures. Are there any other frameworks that don't have these same problems?

Fortunately, simply adopting dependency injection design patterns gets you 90% of the way, further if you don't need encouragement to do the right thing. That's the approach I recommend. I certainly don't see myself adopting Spring anytime soon. I'm better off without it. If you do, go in with your eyes wide open. Be skeptical, critical. Just because someone has a popular Open Source framework, they have slick marketing, and they're supported by a big vendor (IBM pushed Struts on me for a number of years after all), it doesn't necessarily mean they know what's best for you or even that they know better than you.

Update: A coworker sent me a link to a Chinese translation of this entry.

Wednesday, January 25, 2006

The Today Show on Preloaded iPods

The Today Show ran a terribly uninformed piece on "preloaded iPods," i.e. iPods bought from a 3rd party already filled with music and movies. The show speculated that such iPods might be illegal, but they failed to differentiate between 3rd parties that preload iPods with content the end user does not own and third parties that preload an iPod and send the original CDs and DVDs along with it. The first is illegal (you can't buy one copy of a song or movie and resell it to as many people as you like) while fair use protects the latter case; who cares whether you or a 3rd party copies the content to your iPod so long as you own it? Matt Laurer was open about his ignorance on the subject, but if they're going to take the time to report on it, they must get the facts straight. At this critical time for copyright, it's The Today Show's responsibility to educate the public so they can better understand and respect copyright and more importantly so we can protect our liberties from the record and movie industry lobbies. If the industry had its way, we wouldn't be able to copy anything to our iPods or download movies, and only a small number of people who understand the issues with a much smaller budget are fighting them. The public needs to know what's at stake.

Tuesday, January 24, 2006

Denny Crane's Patio Chairs

At the end of every Boston Legal episode, Alan and Denny escape to the office balcony to smoke cigars and sit in these really cool outdoor armchairs. I Googled for at least an hour and couldn't find anywhere to buy them. I contemplated asking on Google Answers, but before I could Krista found them. Yes, she's a better Googler than me.

Wednesday, January 18, 2006

Apple Benchmarks

After hearing Apple's claims that the new PowerBooks are "4 times faster," a number of people are quick to point out how slow the PowerPC processor has been all along despite Steve's claims otherwise. Not true. It's true that Steve's past G4 benchmarks were a little contrived. In an attempt to prove hz didn't matter, he tended to favor graphical applications optimized for the Altivec engine. Not a good test of real world performance. But this doesn't mean the new Intel processors blow the doors off the G5. Far from it. The "4 times faster" claim refers only to the long in the tooth laptop line where Apple replaced a single dated G4 processor with the equivalent of two Intel processors with faster clock speeds in addition to other improvements. Of course it's 4 times faster. Hz for hz, the G5 and Intel processors are roughly the same with one exception: the Intel runs cooler so you can use it in a laptop. So don't go throwing out your G5 desktop just yet.

Sunday, January 15, 2006

Virtual PC...

...is actually usable on my quad G5. Fast even. Everything else continues to run at full speed, too. Go figure. Unfortunately, it's not fast enough for the standalone Google Video Player. Guess I'll have to wait for the OS X port.

Lawrence Lessig on Google Book Search

Lawrence Lessig spoke at Google about copyright last summer. I think he has one of the most entertaining and unique speaking styles I've ever seen. His slides are a continuous animation synchronized with his words. Very creative. He has streaming video of his Google Book Search talk up on his site. Highly recommended. If you like that, check out his books, too.

Delicious Library

Delicious Library can quickly catalog all of your books and DVDs by scanning the barcodes using your iSight. It automatically downloads all the information (director, cover art, etc.). It can suggest similar items. You can automatically buy and sell your stuff on Amazon (not sure how useful the buy feature is). If you sell books or DVDs on Amazon, this is a must have. You can also keep track of who borrowed your items. I don't think it's worth $40 to me, but if it supported other services besides Amazon and had some blogging features, I might be game.

Heard of Overheard in New York?

Always good for a laugh.
Warning: don't go if you're easily offended. Okay, don't go if you're capable of being offended.

Friday, January 13, 2006

So Long, George

George Takei (you might know him as Sulu) just finished up hosting Howard Stern's first week on Sirius. It was absolutely hilarious. George fit perfectly. He took the show to a whole new level. Hopefully they'll bring him on full time.

Help! I'm stuck in this blog!

48% of crazybob.org Readers Use Firefox

43% use IE. Only 6% have 800x600 resolution; the rest use 1024x768 and above. 10% still use dialup. 95% have Java enabled; I wish I knew the exact versions. Interesting demographics brought to you by Google Analytics. These are just the people who actually visit my site. Real geeks use RSS.

Unit Testing Serialization Evolution, Take 2

Tom Hawtin suggested that I override writeClassDescriptor() instead. It's a good thing because in doing so I realized overridding writeUTF() as I did in my first try doesn't intercept field types correctly. Enjoy.
  public static <S> S serializeAndDeserialize(Object o,
      Class<S> spoofedType) throws IOException {
    ByteArrayOutputStream bout =
        new ByteArrayOutputStream();
    ObjectOutputStream oout =
        new SpoofingObjectOutputStream(bout, o.getClass(),
            spoofedType);
    oout.writeObject(o);
    oout.flush();
    oout.close();
    ByteArrayInputStream bin =
        new ByteArrayInputStream(bout.toByteArray());
    ObjectInputStream oin = new ObjectInputStream(bin);
    try {
      return spoofedType.cast(oin.readObject());
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  static class SpoofingObjectOutputStream
      extends ObjectOutputStream {

    String oldName;
    String newName;

    public SpoofingObjectOutputStream(OutputStream out,
        Class oldClass, Class newClass) throws IOException {
      super(out);
      this.oldName = oldClass.getName();
      this.newName = newClass.getName();
    }

    @Override protected void writeClassDescriptor(
        ObjectStreamClass descriptor) throws IOException {
      Class clazz = descriptor.forClass();

      boolean externalizable =
          Externalizable.class.isAssignableFrom(clazz);
      boolean serializable =
          Serializable.class.isAssignableFrom(clazz);
      boolean hasWriteObjectData =
          hasWriteObjectMethod(clazz);
      boolean isEnum = Enum.class.isAssignableFrom(clazz);

      writeUTF(replace(descriptor.getName()));
      writeLong(descriptor.getSerialVersionUID());
      byte flags = 0;
      if (externalizable) {
        flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
        flags |= ObjectStreamConstants.SC_BLOCK_DATA;
      } else if (serializable) {
        flags |= ObjectStreamConstants.SC_SERIALIZABLE;
      }
      if (hasWriteObjectData) {
        flags |= ObjectStreamConstants.SC_WRITE_METHOD;
      }
      if (isEnum) {
        flags |= ObjectStreamConstants.SC_ENUM;
      }
      writeByte(flags);

      ObjectStreamField[] fields = descriptor.getFields();
      writeShort(fields.length);
      for (ObjectStreamField field : fields) {
        writeByte(field.getTypeCode());
        writeUTF(field.getName());
        if (!field.isPrimitive()) {
          writeObject(replace(field.getTypeString()));
        }
      }
    }

    String replace(String className) {
      if (className.equals(newName)) {
        throw new RuntimeException(
            "Found instance of " + className + "."
                + " Expected instance of " + oldName + ".");
      }
      return className.equals(oldName) ? newName : className;
    }

    boolean hasWriteObjectMethod(Class clazz) {
      try {
        Method method =
            clazz.getDeclaredMethod("writeObject",
                ObjectOutputStream.class);
        int modifiers = method.getModifiers();
        return method.getReturnType() == Void.TYPE
            && !Modifier.isStatic(modifiers)
            && Modifier.isPrivate(modifiers);
      } catch (NoSuchMethodException e) {
        return false;
      }
    }
  }

Thursday, January 12, 2006

ObjectStreamClass.forClass()

Not ObjectStreamClass.getClass() (if you want the class it describes, not ObjectStreamClass's Class object). That bug took a little while to track down. I hate debugging serialization logic.

ObjectOutputStream.writeUTF() vs. writeObject()

writeUTF() will write the entire string to the stream every time while writeObject() will write the entire string once and then a reference which takes up significantly less space on subsequent invocations.

Tuesday, January 10, 2006

Bluetooth Headphones, Part II

After a day of use, my new bluetooth headphones hurt the tops of my ears. I wish the plastic rims had some padding. Anybody have an idea of how to rig something up without looking silly? Padded tape or something? They're kind of like thick eyeglass earpieces. I can flip them around with the rim underneath my chin (speaking of looking silly), but they don't fit quite right. Also, why do they make you hold down the power button for 3 seconds to turn them on or off? I hate that.

Google Earth for OS X

If Apple upgrades the Powermac line...

I will be very upset because I bought a quad G5 right before Christmas. Update: We're in the clear. While we're on the subject, does Rosetta go both ways?

Full Screen Google Video on OS X Hack

I wanted to watch Waterborne on Google Video full screen on my widescreen LCD. Google Video supports a full screen mode. You click the button in the lower right hand corner of the player and it opens a new maximized browser window. Firefox doesn't support a full screen or kiosk mode on OS X, so you can still see the browser title bar, the window border, the Apple menu, and the player controls. Very distracting. To top it off, Waterborne is letterboxed not widescreen; it has black bars on the top and bottom so the video still doesn't take up all the available space. Luckily I found a solution. If you go to the "Universal Access" preference pane in "System Preferences", you can enable zooming. OS X makes it easy to smoothly zoom in on an arbitrary part of the screen. I had a little trouble scrolling to the right (I set it to scroll "only when the pointer reaches an edge"), but it wasn't a show stopper. Actually, it worked like a charm. If you hadn't seen me do it, you'd never know the difference.

Sunday, January 08, 2006

Bluetooth Headphones

I despise headphone wires. They catch on my coat in airports. I'm constantly rolling and unrolling. They get in knots anyway. The hassle actually deters me from breaking out my headphones on planes or buses at all; I'd almost rather be bored or carry a magazine. The wires tangle on my keyboard at work. I inevitably forget I'm wearing headphones and try to walk away from my computer. That's why when I saw Motorola's new bluetooth stereo headphones at Radio Shack, I simply had to have them. I also picked up the bluetooth stereo gateway so I can watch TV late at night without disturbing Krista or the baby (Radio Shack had a package deal: $200 for both). The headphones can pair with both your phone and music device at the same time. If you get a call, your music pauses, and the headphones turn into a handsfree headset. Great styling, very sleek, leather pads, very comfortable. 14 hrs. of music listening or 17 hrs. of talk time. 500 hrs. standby time. They charge in 2 hrs. with a standard USB charger. They come with a nice velvet bag to store them in. I tried to pair them with my Mac. It could only recognize them as a headset. The awful sound worried me until I plugged my stereo gateway into the computer and paired with that instead. Crystal clear even a couple rooms away. I guess it will have to do until Apple adds support for the stereo bluetooth profile (Hello, Apple?). Even Dagny digs 'em. She was jamming to Metallica this afternoon:

Port Forwarding

Someone should write a library that can automatically configure popular routers to forward ports. The user would enter their router login information, and the library would configure the router (Linksys, D-Link, Netgear, Airport, etc.) via the web-based admin interface. Actually, router manufactures should standardize on this (i.e. some sort of common REST interface). This could make P2P clients like Bittorrent a lot more accessible. Update: I guess this is UPnP territory.

Saturday, January 07, 2006

Herding Cats

I always liked this commercial. Via Google Video.

Thursday, January 05, 2006

Servlet 2.5 Specification

Forget annotations. Where are generics? Doesn't it bother anyone else that in this day and age we're putting values into various nontyped heterogeneous maps?

Unit Testing Serialization Evolution

Say for example I use session replication in my web application. If I deploy some new code to one server in the cluster to flush out any remaining bugs before a full deployment, I want the old servers to fail over to the new server. And if I decide to take that new server down and implement some more changes, I want its session state to fail over to one of the old servers that are still running. This means the serialized state of objects on the session needs to be compatible in both directions. Here's another example. I'm a JVM vendor. I need to make sure that my implementation of HashMap can deserialize state from an old implementation or another vendor's implementation and vice versa. How do you test this? Cross your fingers? Serialization code can get pretty complex when you start considering compatibility. If you only need backward compatability, it's easy. Check out the old code, write some serialized state to a file, and then write tests that deserialize it using the new code. But what do you do when you need compatability in both directions? The following method spoofs the class name when serializing an object enabling you to deserialize it as a different type:
  public static <S> S serializeAndDeserialize(Object o, 
      Class<S> spoofedType)
      throws IOException {
    final String oldName = o.getClass().getName();
    final String newName = spoofedType.getName();
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    ObjectOutputStream oout = new ObjectOutputStream(bout) {
      public void writeUTF(String s) throws IOException {
        super.writeUTF(s == oldName ? newName : s);
      }
    };
    oout.writeObject(o);
    ByteArrayInputStream bin = new ByteArrayInputStream(
      bout.toByteArray());
    ObjectInputStream oin = new ObjectInputStream(bin);
    try {
      return spoofedType.cast(oin.readObject());
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }
If our class name is Foo, we can copy the old version into our test directory and rename it OldFoo. Now we can create tests that create an OldFoo and serialize and deserialize it using our new method into a Foo and vice versa. Now we can evolve classes with both ease and confidence.

The Atlassian Developer Blog

Tracking down JUnit tests that don't clean up after themselves, editing wikis from Excel, props to Google Search, etc. Good stuff. If you've been living in a cave, Atlassian has two excellent products: Jira, a bug tracking system, and Confluence, an enterprise wiki, both of which use WebWork. Via Mike and Charles.

New Lego Mindstorms!

Via David. Bluetooth and OS X support. What more could you want? The release date is just in time for Dagny's 1st birthday.

Parent Hacks

My new favorite blog. I especially appreciate the Bibster recommendation.