I normally try to avoid anything that sounds like Java-bashing - every language has faults, and often the real accusation should be aimed the people using languages well past their sell-by date for things they were not designed for. But trying to decipher some of the examples in Patterns of Enterprise Application Architecture makes me want to scream. He does explain that he chose Java and C# for accessibility, but it definitely doesn't help clarity, like this extract from page 224 (Compound Key):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class Key... {
    
    private Object[] fields;
    
    public boolean equals(Object obj) {
        // this following line one until Markdown supports offsets in lists!
        if (!(obj instanceof Key)) return false; 
        Key otherKey = (Key)obj;
        if (this.fields.length != otherKey.fields.length) return false;
        for (int i = 0; i < fields.length; i++)
            if(!this.fields[i].equals(otherKey.fields[i])) return false;
        return true;
    }
    
}

The constructor is omitted in the first part of exanple, as it contains some business logic that muddies this issue (it is added in with the next snippet in the book). Anyway, let's go through the above code line by line, and ask, is all this really needed in a more dynamic language?

  1. check we're comparing to an object of the same class - do not want
  2. cast the comparison object - haha, don't be silly, do not want
  3. compare lengths of the fields member variable (ignoring obvious OO design objections) - do not want
  4. manually iterate over the two arrays - do not want
  5. manually compare each element of the arrays - do not want; also return false if they don't match - ok, I guess this is useful
  6. return false if they do - and this is probably handy too

Now here's the Ruby implementation:

1
2
3
4
5
6
7
8
9

class Key
  attr_reader :fields
  protected :fields
  
  def ==(other)
    other.fields == fields
  end
end

No type-checking, casting, array-length-inspection, iteration, or array-poking. All we are doing is delegating the definition of == to a class member. In fact, it's so simple, it does not need to be written down. Because of the verbose nature of the example code, you can spend as much time figuring out trivial implementation details as understanding the subtleties of each pattern. Not to mention the time wasted when you get so angry you write a whole post about how reading Java code is such a waste of time! Let's hope I don't blog about that time-sink, or I may well implode.

Not only that, but consider the implications for behaviour-driven development: the Ruby version needs a specification for the behaviour when a Key is created with:

  • an == set of fields to a reference Key
  • a not == set of fields to a reference Key

The Java version needs to cover all combinations of cases where:

  • the reference Key is a Key
  • the reference Key is not even a Key at all
  • the reference Key has the same of fields (to catch out-of-bounds exceptions)
  • the reference Key has a different number of fields (to catch out-of-bounds exceptions)
  • all fields are the same
  • not all fields are the same

There are three (unnecessary) implicitly-nested if statements (although, in each case, one branch is always a return), which translates to 8 possibly testing scenarios. Possibly, some of these will collapse, but looking at the code makes me shiver.

Now, this shouldn't be taken as criticism of Martin Fowler's decision to use Java and C# for the code examples. That was presumably made to ensure the book would sell enough copies to justify writing it. But that doesn't change the fact that Java and C# are terrible pedagogical languages. When the day comes when "Python and Ruby give them a run for their money" (p100), I hope he rewrites the code for a second edition using something that doesn't make array comparisons harder than learning ancient Greek.

2 Responses to “Six lines of (Java) code that do (almost) nothing”

  1. Jim Says:

    If he had used Arrays.equals(), It could have been:

    class Key... {

    private Object[] fields;
    
    public boolean equals(Object obj) {
        if (!(obj instanceof Key)) return false; 
        Key otherKey = (Key)obj;
        return Arrays.equals(fields, otherKey.fields.length);
    }
    

    }

    Which is then simply the strongly typed version of your implementation. It's simply unfamiliarity with the language.

  2. Ash Says:

    Jim, Thanks for pointing this out. Your version does simplify matters a lot. (Although the tests must still handle the case when you pass a non-key object; perhaps this check could be removed from equals() until it becomes apparent that real code may pass an invalid object). Maybe some of the other examples could be cleaned by someone with more familiarity with Java APIs. I still maintain, though, that the only time Java should be used for teaching purposes is when the purpose is to teach Java. It's just too verbose to express simple concepts.

Leave a Reply