Saturday, October 30, 2004

Premature design

We just hired a contractor to help out with my project until we can hire someone permanently. I'm having trouble keeping him focused on the coding needs of the present. He wants to look forward, design for the future, and revisit existing design decisions that are well beyond the scope of the project. I admire the breadth of his thinking, but I find the lack of his faith in YAGNI disturbing.

Any suggestions?

Saturday, October 23, 2004

I hate C++

I hate C++, no, really. I've been a C++ fan since I started programming in 1991 and learned about it from a friend, Brian McDonnell, who worked on CLIPS for NASA and was a big OOP fan. It was the first language I learned (in tandem with "C") by mistaking the ARM for a more complete textbook ("annotated") rather than the compiler-writer's guide that it was. And that led to the dragon book as my third computer science text. A rough way to start, but very fun, and Larry Wall is spot on about the value of hubris.

Enough about me.

What happened to the joy of C++? And wasn't the moo book a gas? Clearly I enjoy the stuff. But now I hate C++. The explanation is easy: tools.

After a solid year of Java at its finest, returning to C++ and no good unit test libraries (cppunit being about it), no mock objects, no IDEA or decent Eclipse support, no ant or maven, no good coverage tools, no good code analysis, no good dependency analysis on and on and on. Tools make the programmer. And C++ has dog food for tools compared to the wealth of open-source projects for Java. Now that I'm working on a C++ project, my development efforts are at least doubled for the same work, and I continually feel that I am leaving imporant parts of best practices out of the picture. Nor does it look like that's about to change anytime.

And that is why I hate C++. Goodbye, dear friend.

Sunday, October 17, 2004

Frisson

It was a small fright and not a slight pleasure to see the resuls of scattered conversations with my pairmate Gregor Hohpe make an appearance in such an interesting post. It is satisfying to see prominent ThoughtWorkers like Gregor and Dragos (my first pairmate at ThoughtWorks) getting broader exposure for their interesting ideas. ThoughtWorks is like grad school for good development ideas.

Thursday, October 14, 2004

Installing XP

Having moved on from ThoughtWorks for personal reasons, I find myself in an interesting position. My new employer SimDesk is a mixture of old-style top-down development practice and new-style agile practices struggling to get out. One of my top tasks is helping good triumph over bad in that struggle. What's highest on my task list?

Get CruiseControl running
Without continuous integration builds, it is very hard to track just where the source code base for a project stands. My boss is very excited by this, and now I'm just waiting for an official machine to run on.
Daily stand ups
Daily stand ups keep everyone in the loop, help developers get a larger picture of things and are a great leveler. Not happening yet, but I hope to get some buy in this week.
User story notecards
This is a major point against old-style waterfall. User story notecards are a very visible difference and lead to using development as part of the design process, and to short release cycles. Best news yet: my boss and his boss—who is acting on behalf of the actual customer—were fine with this. I have notecards stuck up on my wall now! And with XP-style estimates.

None of this would have been possible without the environment of ThoughtWorks. The place is like grad-school for best practices; a year there is worth four years at most other places. Now if only they had a Houston office.

UPDATE: I left out mention of the great resource, Extreme Programming Installed by Jeffries et al. And there is a bonus: a group here is alread using an internal Wiki which I immediately latched onto for posting story cards and working out design problems, plus some XP envangelizing. Plus I a co-lead agreed to start daily morning stand up meetings next week. I also find that a small satellite group in Austin is using Scrum, but I don't know that much about it. All in all a good environment to build upon.

Monday, October 11, 2004

CruiseControl and version problems

It took me several days to track down the source of my troubles, but it turns out that the latest versions of CruiseControl, Tomcat and the JDK5 do not play together. To get a working CC build results page, I need these versions with Microsoft:

  • Windows XP SP2
  • CruiseControl 2.1.6
  • Jakarta Tomcat 5.0.27
  • Sun JDK 1.4.2_05

Possibly JDK 1.4.2 works with Tomcat 5.0.28, but I did not try that combination. I spent enough time struggling with it that once I had a working combination, I stuck with it.

Next up: — installing on production which for us is a Linux Mandrake 9 box. No one here is foolish enough to use a Microsoft server for production.

Saturday, October 09, 2004

Templates vs. Generics

Bruce Eckels has yet another brilliant article on Java generics. I've been trying generics out quite a bit and I continue to be disappointed. I would have been much happier if Sun had adopted the more ambitious generics projects wholesale such as Rice's Projet NextGen. Given the state of things, I would jump whole-heartedly into Nice if only it had a decent editor such as IntelliJ IDEA. At last I finally understand first-hand why Microsoft had such a grip on the C++ market with VisualStudio which way back when must have seemed pretty slick to many Windows programmers.

Wednesday, October 06, 2004

Logging into NT with Java

While researching JAAS I scratch-coded this interesting bit:

final String name = "Bob the Builder";
final LoginContext context = new LoginContext(name, null, null, getNTConfiguration(name));

context.login();
context.logout();

Of course, the secret is in getNTConfiguration:

static Configuration getNTConfiguration(final String name) {
    final Map options
            = new HashMap() {
        {
            put("debug", "true");
            put("debugNative", "true");
        }
    };

    final AppConfigurationEntry[] appConfigurationEntry
            = new AppConfigurationEntry[]{
        new AppConfigurationEntry(NT_LOGIN_MODULE_NAME, REQUIRED, options),
    };

    final Map entries
            = new HashMap() {
        {
            put(name, appConfigurationEntry);
        }
    };

    return new Configuration() {
        public AppConfigurationEntry[] getAppConfigurationEntry(final String name) {
            return entries.get(name);
        }

        public void refresh() { }
    };
}

And the super-secret is the value of NT_LOGIN_MODULE_NAME: "com.sun.security.auth.module.NTLoginModule".

The output when I run using all the debug options is:

An attempt was made to reference a token that does not exist.
		[NTLoginModule] succeeded importing info: 
			user name = boxley
			user SID = S-1-5-21-123456789-839522115-1060284298-38670
			user domain = MYDOMAIN
			user domain SID = S-1-5-21-123456789-839522115-1060284298
			user primary group = S-1-5-21-123456789-839522115-1060284298-513
			user group = S-1-1-0
			user group = S-1-5-32-544
			user group = S-1-5-32-545
			user group = S-1-5-4
			user group = S-1-5-11
			user group = S-1-5-5-0-77027
			user group = S-1-2-0
			impersonation token = 7120
		[NTLoginModule] completed logout processing
getting access token
  [getToken] OpenThreadToken error [1008]:   [getToken] got user access token
getting user info
  [getUser] Got TokenUser info
  [getUser] userName: boxley, domainName = MYDOMAIN
  [getUser] userSid: S-1-5-21-123456789-839522115-1060284298-38670
  [getUser] domainSid: S-1-5-21-123456789-839522115-1060284298
getting primary group
  [getPrimaryGroup] Got TokenPrimaryGroup info
  [getPrimaryGroup] primaryGroup: S-1-5-21-123456789-839522115-1060284298-513
getting supplementary groups
  [getGroups] Got TokenGroups info
  [getGroups] group 0: S-1-5-21-123456789-839522115-1060284298-513
  [getGroups] group 1: S-1-1-0
  [getGroups] group 2: S-1-5-32-544
  [getGroups] group 3: S-1-5-32-545
  [getGroups] group 4: S-1-5-4
  [getGroups] group 5: S-1-5-11
  [getGroups] group 6: S-1-5-5-0-77027
  [getGroups] group 7: S-1-2-0
getting impersonation token
  [getImpersonationToken] token = 7120

Friday, October 01, 2004

Upside-down inheritance

Here is a classic, persisted object in Java:

public class Foo extends Persisted {
    // fields

    // constructors

    // getters, setters
}

Pretty dull. What's wrong with that?

Now here's a typical query-by-example (QBE) method in some finder class (assuming something suitable like Hibernate or iBatis, and a supporting framework):

public Foo findFooByExample(Foo foo) {
    return (Foo) findByExample(FOO_TABLE, foo);
}

And here's a typical data transfer object (DTO) for passing around the found Foo to some other layer of the program:

public class FooData {
    // same fields as Foo

    // same constructors as Foo

    // same getters, setters as Foo
}

And then there's methods which take a Foo and need testing:

public void doBar(Foo foo) {
    // Does Bar look at anything in Persisted, or just Foo?
    // The test code needs to mock the persisted methods, if so.  Rats.
}

Oh, wait. Hrm. Lots of code duplication, lots of overhead to make changes, extra things to test. This is not looking so good. Why is that?

The inheritance is upside-down!

Once you disabuse yourself of the preconception that domain/database objects (DO) extend a persistence base class, the solution is trivial:

public class Foo {
    // fields

    // constructors

    // getters, setters
}

public class FooPersisted extends Foo implements Persisted {
    // fields, getters, setters for persistence
}

Now the QBE example uses Foo for input, and FooPersisted for output; FooData goes away completely; and the doBar method explictly requires either a Foo or a FooPersisted, making it clear if it does or does not fiddle with persistence.

And a bonus: it is almost always less work to implement the two or three fields and getter/setters which persistence uses rather than the larger number of fields which the DO uses. And as they are the same few fields everywhere, you can automate the process using code generation or annotations.

(Aside: Or course, it would be even easier still if Java only supported mixins. Then you just defined FooPersisted as:

public class FooPersisted extends Foo, Persisted {
    // empty -- no further code needed
}

No new keywords; no confusion—super always refers to the first class mentioned in the extends list.

But that is a different post.)

Safer collections

In my post on IndexMap I mentioned in passing SafeHashMap. What is that?

The JDK is very useful but has some warts. One of the worst is this inconsistency: if you try to index into a list with a non-existent index (i.e., beyond the end of the list) the collections throw IndexOutOfBoundsException. But what happens when you try to fetch from a map with a non-existent key? The JDK map implementations silent insert a null value into the map for you and return it. This leads to the following anti-idiom:

Value getValue(Map map, Key key) {
    return (Value) map.get(key);
}

See the problem? Now all code dealing with map anywhere in the program needs to test for null values and decide how to handle them, or else be happy with NullPointerException:

for (Iterator it = map.values().iterator(); it.hasNext(); ) {
    Value value = (Value) it.next();

    if (null == value)
        handleNullValue();
    else
        doTheRealWorkWhichIsTheWholePoint(value);
}

Try coding that 10 times real fast.

What is the solution? Force the correct idiom in the first code fragment:

Value getValue(Map map, Key key) {
    if (!map.containsKey(key))
        map.put(key, createMissingValue(key));

    return (Value) map.get(key);
}

And to enforce this idiom, extend a concrete class such as HashMap with a safe wrapper, hence SafeHashMap, and forbid missing keys or null inserts:

public Object get(Object key) {
    if (null == key) throw new NullPointerException();
    if (!containsKey(key)) throw new IllegalArgumentException();

    return super.get(key);
}

public Object put(Object key, Object value) {
    if (null == key) throw new NullPointerException();
    if (null == value) throw new NullPointerException();

    return super.put(key, value);
}