I think the best way to improve as a developer is to accept the fact that every line of code you've ever written could have been a whole lot better. Nobody writes perfect code, and the sooner you accept that, the sooner you'll start learning new ways you can improve.
Yesterday this blog post from the boys at Atlassian highlighted a little gem that I realised I've been guilty of on more than a few occasions.
It turns out that java.text.DateFormat isn't thread safe. Who would have guessed? Well, besides the fact that there's a huge disclaimer at the bottom of the Javadoc, who would have guessed? RTFM next time I suppose.
Anyway, the Atlassian blog post pretty much sums it up - DateFormat isn't thread safe, and more importantly, the most common symptom from thread contention is output that is perfectly valid, but also completely wrong! Chances are you'll get a nicely formatted date, it just wont be the date you were expecting. Needless to say, that's bad.
I wonder how many hair-pulling, late nights have been spent by developers around the world trying to figure that one out.
I was curious to see what the results would be if two threads fought over a single DateFormat so I put together some code to test it out.
public class TestDateFormat {
public static void main(String[] args) {
// a date format that two threads will contend for
final DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
class DatePrinter extends Thread {
private String dateString;
public DatePrinter(String dateString) {
this.dateString = dateString;
}
public void run() {
while (true) {
try {
Date parsedDate = dateFormat.parse(dateString);
String parsedDateString = dateFormat.format(parsedDate);
if (! dateString.equals(parsedDateString)) {
System.err.println(dateString + " != " + parsedDateString);
}
} catch (Exception e) {
System.err.println(e);
}
}
}
}
new DatePrinter("01/08/2007").start();
new DatePrinter("14/03/1978").start();
}
}
The code creates a single DateFormat instance and two threads that both attempt to use it simultaneously. The threads are constructed with a string representation of a date. Each thread simply loops around indefinitely, converting its date string into a date object, and then converting that date object back into a string again. If all goes well then the original string should equal the converted one. If not, then it prints a message to the console.
Running the program produces a flurry of output as the threads argue over who's turn it is to use the DateFormat. Here's a snippet of some of the output it produced when I ran it:
14/03/1978 != 12/01/1982 14/03/1978 != 14/03/1414 14/03/1978 != 01/08/1978 14/03/1978 != 01/08/2007 01/08/2007 != 01/08/20072007 14/03/1978 != 13/06/56933 01/08/2007 != 15/07/19781984 01/08/2007 != 17/10/56168 01/08/2007 != 01/08/0001 01/08/2007 != 01/08/0000 14/03/1978 != 28/08/1983
As well as the occasional (and very strange) exception:
java.lang.NumberFormatException: For input string: ".1401E.14012E2" java.lang.ArrayIndexOutOfBoundsException: -343
As you can see, many of the dates that are produced are nicely formatted, but very, very wrong. The Atlassian post suggests a number of good solutions for fixing things to ensure you get the results you expect.
I have to admit, I'm guilty of using DateFormat in the past with absolutely no regard for thread safety whatsoever. I wonder how many nasty defects I've left in my wake as a result. My apologies to any past colleagues who ended up stuck in the office until 3am fixing a DateFormat related bug I left behind :)
But at least now I've learned something to make my code just that little bit better next time.