Dependencies
By Adrian Sutton
I’ve never understood Java programmers attitudes to dependencies. The Java runtime and libraries was designed specifically to allow cross-platform deployment and increase compatibility. Write once, run anywhere. What confuses me then is this obsession Java developers seem to have with being incompatible or rather, using the absolute bleeding-edge, never released, not available anywhere version of stuff – particularly the JRE. Now certainly Java is unusual in that it is rapidly developing. Not only that but the language, the runtime and the libraries are all tied in together so if you want to live on the bleeding edge with one you have to live on the bleeding edge with them all. Contrast this with the C world where the language is effectively static, the standard library is minimal and evolving relatively slowly and the “runtime” (I’m thinking of the ABI) is fairly static (if platform specific). It’s not often that C programmers have to make a decision to update to the latest version and it rarely impacts deployability if they do. Most of the time if a C program requires a specific version of a library it just statically compiles it. In the Java world however, people are always pining for the latest enhancements. Suddenly generics are a huge deal when everyone’s been getting by without them just fine for years. Suddenly it’s too much effort to work around bugs in the libraries and it’s better to just force users to upgrade. Frankly, that’s simply not good enough. If I develop an application for Windows, I have to decide which versions of Windows I’ll support. I could use APIs that are new to Windows XP and thus tie my application specifically to it, or I could not use those APIs and let any Windows version run it. Better yet, I could design my system in such a way that I can optionally use those APIs if they’re available and fail gracefully otherwise. C programmers are really good at this, most likely because they’ve spent so much time trying to write C code that’s cross platform (it’s possible, but you have to be good at doing this kind of thing). Unlike C, Java actually makes it easy to optionally use APIs because everything is dynamically bound. Consider the class:
public class MyCoolClass {
public MyCoolClass(boolean doStuff) {
if (doStuff) {
com.apple.ewt.Application.registerPreferencesListener(this);
}
}
} What happens if the class com.apple.ewt.Application isn't available? If doStuff is true obviously a ClassNotFoundException would be thrown, but if doStuff is false no exception will be thrown and the missing class will never be noticed. Now clearly, if you change doStuff to something like
System.getProperty("mrj.version") != null
you’ve just implemented an optional dependency that works on Mac and is ignored elsewhere. Better than that though, if you know a little about ClassNotFoundExceptions, you can just catch the exception and carry on with life. In C as far as I’m aware you’d have to jump through hoops or use compile time ifdefs to achieve the same thing. Why then don’t Java developers take advantage of this? The other thing that really annoys me about a lot of Java developers is this concept of certifying a specific JRE version and only running on that. C programs don’t do that, why should it be okay for a Java program to? Again, if I write a standard windows application, my users would expect that it continue to run even if they upgrade to Windows Longhorn, despite the fact that Windows Longhorn didn’t exist when I wrote the program. Sure, sometimes unexpected things happen and software will break in that situation, but why would you not even attempt to anticipate future versions, or past versions? If I already have a copy of the JRE on my system, I should be able to use it to run your Java application. If your Java application has a good reason to require a more recent JRE (and there are plenty of reasons) then yes I may have to upgrade my JRE. If however, your Java application just specifically wants JRE 1.4.2_01 and won’t run on anything else that’s a sign of poor programming. Even worse, is forcing me to install a second copy of JRE 1.4.2_01 even if I have that exact version on my system. The JRE is a shared library, just like libc is – use it! There’s been a lot of talk recently about Java on the desktop and how Java has to beat .Net or it will disappear off the face of the earth. The way to do that isn’t to improve the language – it’s proven technology that’s widely adopted now. The way to do that isn’t to provide a million different libraries regardless of how good they are – Java already has arguably the best library support around. The way to do that isn’t to provide the best developer tools – there are tons of developer tools out there already and they’re really good. The way to do it is to show people that Java applications can be top quality, well engineered, robust, reliable and fast. If people can see quality software developed in Java – not libraries, not developer tools, real end user software – then people will start to respect Java and it will be appealing to managerial types and pointed haired bosses. Naturally having good tools, libraries and language features can help to create great end user software but the end goal has to be to create great software that actually does something useful, instead of just provides the basis for someone else to do something useful. Am I saying that there is no good Java software out there? Absolutely not. There’s some great Java software around and there continues to be more and more developed – that’s great! Unfortunately, there’s still far too many Java developers who are maximizing their own convenience instead of the convenience for their end users and that’s not the way to write good software. In summary, write robust software. If adding a dependency provides enough benefit to your users to outweigh the problems they will have in acquiring that dependency then go ahead and add the dependency. If however you’re adding the dependency purely for your convenience or because it’s cool and new, stop and think about why you’re writing your software, who you’re writing it for and why they would want to add that dependency to the software. If you can’t justify it to the user, don’t add the dependency. Mostly though, write robust software – it should be able to deal with things suddenly changing underneath it because even if you ship a specific JRE version, the underlying OS will change and that will eventually catch you out.