Thanks Matt!
www.flickr.com
This is a Flickr badge showing public photos from Michael Glenn. Make your own badge here.
When I first learned how to program, object oriented tools didn't exist. Instead for years procedural programming was all I had. As a result my "object-oriented" code tends to smack of procedural code quite often and I have to re-factor to optimize for re-use, modularity and testing.
So it comes as no surprise that post procedural programmers like myself will sprinkle their Java code with poor usage patterns that hark back to earlier days. One example is the absence of proper exception throws. More specifically, using null as an acceptable error condition.
When coding in say C, the programmer will return a particular numerical error code from a function if something was amiss during execution. The calling function was then fit to deal with the error code as it saw fit. This worked but was problematic as the codes were only visible at runtime and only had context if there was a list to match these codes. Those of you who program in Microsoft ASP code and see meaningless error codes such as 502345 know what I'm talking about. Sure you can look it up, but what a freakin' pain.
One of my favourite features of Java is the Exception object. Exceptions are basically object based errors which methods are defined to throw. Utilizing exceptions are often misunderstood. Frequently programmers will simply throw the offending Exception up to the calling method. This isn't great from a data coupling point of view but the worst of these offending implementations is the null return.
The null return is prevalent even in the JDK. It is the absence of a return value from a method because something didn't go right. java.util.Hashtable's get method is an example of the null error. The get method JavaDocs specifies that the return value is "null if the key is not mapped to any value in this hashtable". In other words, if something goes wrong, we're not going to throw an exception but just return null.
Null is like the faceless exception. Something clearly didn't go right because we got null back, but unless we go and look up what this means we're not sure what happened. Changing the method to provide a compile time context makes it much easier to understand what happened and handle it based on context.
// Using null as a return errorObject item = hashtable.get(key);
if(item == null){
// handle error
}
// Using an Exception as a return error
try{
Object item = hashtable.get(key);
}catch(ObjectNotFoundException ex){
// handle exception
}
The amount of code makes very little difference but now we have context for the situation. Hashtable is fairly common so I'm sure you're used to seeing null come back, but if you're using an unfamiliar method and don't remember to check for null you're compile will proceed cleanly and eventually your program will spit up the dreaded NullPointerException.
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.jsp</location>
</error-page>
StringBuffer sb = new StringBuffer();
// append error message
sb.append(exception.getMessage());
sb.append("\n");
// append stack tracce
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
sb.append(sw.toString());
sb.append("\n");
// append request URL
sb.append("Request URL:");
sb.append(request.getRequestURL());
sb.append("\n");
// append parameters
sb.append("Parameters:");
Enumeration enum = request.getParameterNames();
int numValues = 0;
while(enum.hasMoreElements()){
numValues++;
String name = (String)enum.nextElement();
String value = request.getParameter(name);
sb.append(name);
sb.append(":");
sb.append(value);
sb.append("\n");
}
if( numValues == 0 ){
sb.append("No parameters");
}
sb.append("\n");
log4j.appender.mailer=org.apache.log4j.net.SMTPAppender
log4j.appender.mailer.Threshold=ERROR
log4j.appender.mailer.BufferSize=10
log4j.appender.mailer.from=error@domain.com
log4j.appender.mailer.SMTPHost=localhost
log4j.appender.mailer.subject=Website Name Error
log4j.appender.mailer.to=support@support-domain.com
log4j.appender.mailer.layout=org.apache.log4j.PatternLayout
log4j.appender.mailer.layout.ConversionPattern=%t %-5p %d{dd MMM yyyy HH:mm:ss,SSS} %c{2} - %m%n
log4j.logger.com.package=ERROR, mailer
// create the logging category
Category cat = Category.getInstance("com.package.jsp.error");
// log error
cat.error(sb.toString());
<%@ page buffer="50kb" %>
Now making the rounds in Slashdot, blogs and email the internal memo from Sun regarding problems with the Java language.
Being a Java developer I have a bias towards Java. The memo however, is justified in a lot of its comments. Don't run for the hills just yet though. This is constructive criticism that should be looked upon as a way to make some serious improvements and think about the next evolution of Java. Slashdot readers love to dump on Microsoft whenever internal critique comes about, but I'd be more scared of a company that thinks their product is perfect and makes no serious examinations of its flaws.
The idea of Java is fantastic but the implementation falls short first in the area of performance and second in implementation. It's time to stop making excuses and put up or shut up. That said I'm still sticking with Java. The ability to rapidly develop applications with easier debugging and a fantastic library and documentation at my disposal is enough to sell me.
In response to the major points:
"The JRE is very large"
This is a bit misleading in my opinion. I think what they mean is that the JVM can take a significant amount of memory during runtime and is slow with several applications noted. I assume this since a compiled "Hello World" class file would not be anywhere near 9MB as indicated. The size of the JVM is an issue that is separate from the language itself but quite effectively points out that the model for bytecode execution needs some serious examination. The scope of this is quite beyond my level of understanding of the JVM at this point in time. Comparing to Python may be significant although I'm not familiar with Python to determine if it is appropriate.
"Extensions do not support modularity".
Convenience dictates that extensions (javax.*, etc) be put into the main JRE distributions. I think this unnecessary inclusion of optional classes is wasteful. The various Java packages are separate enough that developers can include the .jar files along with their application. This also creates dependency problems as packages such as XML parsers that were once in an application's library are now in the JRE and conflicts arise. I don't know how to back out of this scenario other than to stop adding unnecessary extensions and give precedence over application libraries.
"It is not backward-compatible across minor releases"
The JRE release number has always confused me. JRE 1.2 is Java 2 1.0. Huh? Shouldn't this be Java 2.0? Well yes and no depending on where you draw the lines. In a lot of cases there is backwards compatibility but not in all as the memo points out. Regression tests would be nice. Upgrade to a 2.0 or better yet 3.0 to avoid confusion with Java 2 and start over from there. Make sure that all methods and classes are backwards compatible and that point releases are either an addition of functionality or major bug fixes.
I saw Marc Fleury speak at the latest Toronto Java Users Group meeting. Marc has great ideas about the next level of EJB that's produced in the Jboss product he brought into reality. Most of the room was trying to grasp the concepts while Marc kept pushing more ideas out at us, myself included.
I'm now busily working with the new Jboss 3.2 beta and learning about the 3.x interceptor concepts.
Read Marc's Blue paper "Why I Love EJB" for more information on Jboss concepts and the future of Jboss and EJB.
TheServerSide.com also has a video interview with Marc.