Recently I tripped over another stupid mistake – just among us, I’m still waiting for the day when I stop making stupid mistakes and start making the smart ones – in a Java project. This time I spent about 20 minutes in the debugger, watching a paricular method call. One of the arguments in the call was a direct reference to a static final String defined in a class from another package. I had just changed the value and recompiled the class in which it was defined. However, the old value was still being passed in this method call. I stepped through it in the debugger; inspecting the value before the method call showed that the new value was indeed assigned to the variable. However, as soon as I stepped into the method, the old value popped up.
After a while I had a colleague look at the decompiled source, and then it hit us: the -O flag in the java compiler will inline references to static final variables. I not only needed to recompile the class where the variable was defined, I needed to recompile any class that referred to it. You C/C++ types out there are probably screaming about makedepend right now, and you’re right…sorta. Java does not have a popular makedepend-like tool. Thinking briefly about why this is so, I attribute it to the greater complexities of dependencies in Java than #include in C and the relative cheapness of compiling Java classes to bytecode. Couple those with considerations like JVM startup overhead at compile time, and you wind up with the conclusion that it’s cheaper to recompile all the classes all the time than to try to maintain a dependency tree.
Yet another twist on Hoare’s Dictum: “Premature optimization is the root of all evil.” During development, it’s probably best to leave optimization off.
Anyway, that’s not the gripe, just the moral. It’s a good thing we remembered the optimization flag, because I went looking for docs and found…nothing. The javac optimization flag is not in the Sun tool documentation, or any other place I could think to look. That’s the gripe. What else don’t I know about javac?
I disagree with your moral. Your compilation settings during development should be as close as possible to the ones you intend to ship with.
Suppose there is a bug in the optimizer, and it produces the wrong code. (Has happened to me, but only twice.) Wouldn’t you want to discover that before shipping?
Rather, I fault your build environment. But that’s not an indictment of Java (even though I am a C++ snob). This sort of thing happens under even the best of systems. For instance, we have a special set of headers that are included by everything. Dependencies on these headers are always ignored; but you are only allowed to edit them in certain ways, e.g. adding a value to an enum list. This way, your change cannot actually invalidate anyone else’s code. This works great.
…Until you do something like this: Change the header; compile; update your workspace, requiring a merge in the special header, which changes the value of your enum tag; check in. Now you need to manually recompile all of the files you just checked in!
I made this mistake recently. After NOT recompiling, I ran tests again, trying to verify my checkin. Smoke tests failed. I sent an email to the world, saying, “Hang on! I’ve broken something and I’m working on it!!!” One of our super-experienced guys (Fred Brooks might call him the “language lawyer”) guessed what I had done.
I agree that, ideally, my compiler settings would not change between development and deployment, and I was second-guessing my moral even as I wrote it. After all, this is a clear demonstration of the impact of compiler settings! I suppose that either changing the build environment to always compile all classes, or learning to live with little irritations like this one, would be a better approach.
After a little more drilling, I did find the optional depend task for ant, but the situation I described is documented as outside depend’s capabilities, since it analyzes bytecode instead of source. I guess this happens infrequently enough that people just live with it.
Unless you can compile everything in less than 20 seconds, you will eventually get tired of that.
Here’s my proposal… live with the irritations, but document them. I have a text file called approximately “c:\thurp.txt”. Whenever a new developer starts, I offer him a copy.
I agree with Tim, keep the settings constant. Compiling all your classes should be so fast these days that it shouldn’t impact your development much.
FWIW, I think one reason you don’t see a lot of makedepend-type stuff going on in Java is that–for many of us–compilation isn’t the big time suck. In a J2EE universe, you usually spend most of your build time doing EJB compilation and such things, so the compilation time is negligible anyway.
Our project isn’t that big; around 700 class files. I haven’t done a SLOC count lately but we’re probably around 100-150K. Clean compiles take under 2 minutes so and a full distro build from checkout usually takes under 5 minutes. Of course, compiling a couple of source files takes just a few seconds, so comparatively speaking, two minutes is a long time. Fortunately we are EJB-free so we don’t have to worry about that workflow.
As of now I’m not planning any changes in the build environment to address this; the benefit isn’t there to justify the cost. I’m still annoyed at the lack of doco on the optimization flag, though.