Tuesday, March 18, 2003

Optimization vs. Readability

Coming from a CIS background, I've never had the same appreciation for low level optimization as co-workers with CS backgrounds. My philosophy has always been: readability is always more important than optimization, unless it's been proven that the specific situation is a bottleneck.

This opinion has never been popular, but it was dismissed almost completely in the early days of Java. Java was always sooo slow, that any optimization helped. Method calls were minimized; string manipulation was reserved for StringBuffers and character arrays; and anything that could be done in native code, was.

At the time, we did what we needed to do. Today, however, many programmers haven't accepted that those days are behind us. Not to say people should start writing slop; but more than ever before, we can trust the VM to do its job.

Consider this code:

StringBuffer buf = new StringBuffer();
buf.append("select table1.columnA, table2.columnB, table3.columnC from ")
.append("someTable table1, someOtherTable table2, table3 where ")
.append("table1.key = table2.key and table2.key = table3.key "
.append("and table1.key=?");
con.prepareStatement(buf.toString);

It could look like this, without any performance penalty:

String sql =
"select table1.columnA, table2.columnB, table3.columnC from " +
"someTable table1, someOtherTable table2, table3 where "
"table1.key = table2.key and table2.key = table3.key "
"and table1.key=?";
con.prepareStatement(sql);

Not only is the direct use of strings easier to read, it's use is optimized in many IDEs (IDEA automatically places ["+"] in a string when you press enter in a string. Ctrl-J will concatenate strings from separate rows.).

Many programmers (especially those who suffered through the early version of the Java VM), still cache objects unnecessarily, rather than relying on garbage collection. Many implement all their Swing listeners in a single class and perform their own event delegation to save on class creation. My personal favorite is parsing Strings as character arrays, which of course renders the code completely unreadable while (according to an article in Oct 2002 Java Pro - "How Hot is HotSpot?") actually slows the code down in a modern VM. You read that right! In some cases, the code that was optimized for older VMs is now slower than more readable code.

One more thing to consider when optimizing that code: What costs your company more? people or machines? If your optimizing a problem that will improve the client's perception of the product, then go right ahead. If your making the code less readable because "it's theoretically faster this way", make sure the theory is true, and the change is worth it's time in readability.

Labels:

Monday, March 17, 2003

99 Bottles of Beer

I read an article on Slashdot about a contest which would have programmers write an interesting program printing the lyrics to "99 bottles of beer on the wall". This inspired me to play with the Java Speech API. I wrote a quick program to recite the lyrics. It's really nothing more than a glorified Hello World program, but it let me play with the Speech API:

public class BottlesOfBeer {
public static void main(String[] args) {
Voice voice = null;
Class voiceClass;
try {
voiceClass = Class.forName("com.sun.speech.freetts.en.us.CMUDiphoneVoice");
voice = (Voice) voiceClass.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (voice instanceof MbrolaVoice) {
try {
(new MbrolaVoiceValidator((MbrolaVoice) voice)).validate();
} catch (ValidationException ve) {
System.err.println(ve.getMessage());
throw new IllegalStateException("Problem starting MBROLA voice");
}
}
voice.setLexicon(new CMULexicon());
voice.setAudioPlayer(new JavaClipAudioPlayer());
voice.load();
for (int i = 99; i > 0; i--) {
String verse = i + getBottle(i) + " of beer on the wall. " +
i + getBottle(i) + " of beer. " +
"Take one down. Pass it arround. " +
(i - 1) + getBottle(i - 1) + "of beer on the wall.";
voice.speak(verse);
}
voice.speak("Thank you everyone. You've been a great audience. " +
"I'll be here all week!");
}
public static String getBottle(int numberOfBottles) {
return numberOfBottles != 1 ? " bottles" : " bottle";
}
}

For those of you who don't know, the Java Speech API doesn't have a reference implementation. It only defines the api. I used an open source implementation called FreeTTS.

By sharing this code, I've disqualified my self from the contest, but since the code is judged on compactness, obfuscation and originality, I don't think I had much of a shot anyway.

By the way... when did obfuscation become a cherished trait in a programmer?

Labels:

Monday, March 3, 2003

Issues programming on my Mac

I've been programming from my new mac recently, and ran into a few issues. I thought I would post them, not only to share with others, but in hopes that people may share their workarounds to some of these issues:

Creating an Application Bundle

Packaging my application was much easier than I originally thought. There really isn't an excuse not to provide an application bundle for a java application if you intend to support the Macintosh platform. I got really good use out of a book called Mac OS X for Java� Geeks. I'll probably post a detailed review when I finish it.

I initially used an application called MRJAppBuilder, (which looks very slick and easy to use), but it's output was missing two very important lines of code: the classpath and the main class. It's not that MRJAppBuilder didn't know what they were... it just failed to write the files out correctly. Very strange. Thankfully, the book had very detailed instructions for editing these files by hand.

I ended up copying all the resource bundle files into my source tree (for version control), and made an ant task to assemble them as an application bundle at deployment time.

Creating a Deployment File

I wanted deployment of my application bundle to be very Mac-like. I didn't want the user to see a hint of my Windows background in the download and installation process.

My first instinct was to create a disk image. As it turns out, creating a disk image from the command line (or ant) requires a bit more interaction than I would have liked. You must do a du on the source files to determine the sector count, then bump that up by a few percent for metadata, then create the image, format it, etc, etc. This would have required a hand full of support scripts and a lot of hair pulling. Someone should just put together an ant task for this mess.

My second attempt was to tar and gzip the files. It almost worked fine. Application bundles for Java contain a stub file (written in C) that executes your Java program for you. By default tar removes permissions from the files it archives (including the executable permissions). Ant's task has a option for specifying permissions, but I didn't have much luck getting it working.

I finally decided it wasn't worth the trouble. I used zip. It works great.

Refresh issues

For some reason, when I run the application bundle, I sometimes have issues with the toolbar not refreshing correctly. I haven't tried debugging it yet, but I suspect my property settings in the application bundle. Idea seems to have a similar problem in their message window (sometimes words don't redraw correctly).

Labels:

Windows Keys in Swing

Java has always centered its identity around it cross platform support. Although they have never deviated from this focus, there has always been a wandering direction as to how to implement this cross platform support on such a wide variety of machines. AWT took the approach of the lowest common denominator. Swing choose to emulate a UI that would behave the same on all platforms. At first, Sun even refused to add support for the right mouse button; after all, if most Java programs require a two button mouse to work, is the language really cross platform?

I believe any language that is totally cross platform, would have to be distilled to the point of uselessness. As critics point out, not every aspect of Java is cross platform. Java programmers can write some very nice, cross-platform code; at the same time, they are not normally excluded from tapping into the platforms strengths:

  • A J2EE project doesn't have the same set of constraints as a J2ME project.
  • A Unix program can have a tree structure
  • A Windows program can have right mouse button support

What about the windows keyboard?

For a Java program to be as usable as it's native counterparts, it should have the ability to respond to all the same key events. I understand that Sun has a delicate balance of functionality and compatibility to maintain. I didn't expect the Swing API to fire a right-mouse-click event on the currently focused component when the context menu button was pressed on my Microsoft keyboard (although that would have been really cool). I did expect, Sun to allow me to trap the key event on my own.

It turns out, that Sun (in some apparent fear of developers like myself), decided to strip out any key events it didn't recognize and replace them with a keycode of zero. I can't tell a windows key from a cash register key without dropping down to another language.

JDK 1.4.2 is due out in a couple months. Features for 1.5 are already being leaked. If you agree with me on this, vote for feature request 4352104 at Sun's Bug Parade so we can get the fix as soon as possible.

Labels: