Knowing J2ME, Knowing J2YOU
I've been working on a project for about six months now (including a three-month "freak-out quitting and doing other things" period in the middle) and thought it was about time I share with you some advice in case you're considering a similar project in the future.
For those of you who are unfamiliar with the J2ME environment, Java, or programming in general, allow me to explain. J2ME is a toolkit I use for developing programs (MIDlets) on cellular phones running SymbianOS (it stands for Java 2 Mobile Edition). Because a cellphone isn't a computer, the Java libraries are not exactly as full-featured or robust as those in the standard Java library, but when it come to development this is only a problem in certain areas. For most purposes the reduced libraries are, for lack of a more descriptive word, suitable. When it comes to finding and fixing bugs, especially in a threaded environment, however, you're better off taping every possible solution to a giant wall and throwing a knife at the right one blindfolded.
The good folks at Sun are easily bored and must live very dull lives, because it would seem from a debugging standpoint that the exception handling abilities of J2ME were a cruel joke to provide them with brief amusement and the programmer with nothing. Normally when an error occurs and the code catches it, an exception is thrown and one is able to view the procedural stacktrace to track down which function caused the problem. Despite the performance loss due to having to include dozens upon dozens of try {} catch {} statements, Java's exception handling saves countless hours of scouring a very large program for errors. And indeed, as some may have noted while reading this, there is a printStackTrace() method for Exceptions in J2ME. Let's compare the output of two stacktraces: the first is one I pulled off the web that illustrates how well a bug or unexpected error is documented by the JVM, and the second one illustrates why mobile programmers often take three month freak-out vacations from a project:
Standard stacktrace:
java.lang.ArrayIndexOutOfBoundsException
MESSAGE: 8
STACKTRACE:
java.lang.ArrayIndexOutOfBoundsException: 8
at com.mysql.jdbc.ByteArrayBuffer.readInt(ByteArrayBuffer.java:224)
at com.mysql.jdbc.MysqlIO.unpackField(MysqlIO.java:607)
at com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:414)
at
com.mysql.jdbc.MysqlIO.readResultsForQueryOrUpdate(MysqlIO.java:1962)
at com.mysql.jdbc.MysqlIO.readAllResults(MysqlIO.java:1419)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1728)
at com.mysql.jdbc.Connection.execSQL(Connection.java:2988)
at com.mysql.jdbc.Connection.commit(Connection.java:2161)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at
com.ibatis.common.jdbc.SimpleDataSource$SimplePooledConnection.invoke
(SimpleDataSource.java:946)
at $Proxy0.commit(Unknown Source)
at
com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransaction.commit(Jdbc
Transaction.java:66)
at
com.ibatis.sqlmap.engine.transaction.TransactionManager.commit(Transa
ctionManager.java:83)
at
com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.commitTransactio
n(SqlMapExecutorDelegate.java:759)
at
com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.commitTransaction(Sql
MapSessionImpl.java:133)
at
com.ibatis.sqlmap.engine.impl.SqlMapClientImpl.commitTransaction(SqlM
apClientImpl.java:110)
at
com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.autoCommitTransa
ction(SqlMapExecutorDelegate.java:866)
at
com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.insert(SqlMapExe
cutorDelegate.java:451)
at
com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.insert(SqlMapSessionI
mpl.java:81)
at
com.ibatis.sqlmap.engine.impl.SqlMapClientImpl.insert(SqlMapClientImp
l.java:58)
at net.saf3.biblio.Test.main(Test.java:265)
Simplified mobile stacktrace:
Are you asking yourself where the second stacktrace is? That's a coincidence - so am I! It turns out it's sent to the error stream, which is defined by the System class. In regular Java you can easily view this stream because it prints to your console or wherever you have it piped. You can even use a method called System.setErr to send it somewhere else! In J2ME there is no such method, so the only way of retrieving it is to view the error stream. Where is that? Good fucking question. It turns out that for anyone using SymbianOS 7.0 prior to Feature Pack 3, there isn't any possible way to view the error (or the standard output) stream (for those using FP3, you still can't change it; you can, however, download an external program to run in the background and capture the error stream). So why do they have an error stream you: a) can't view; b) can't change; and c) provide as the only means of viewing useful error messages considering points a and b above? Those are three good questions that deserve answering. But considering this is Sun Microsystems, the answers probably cost $3,000USD for a 4-hour executive programming seminar to learn.
If that weren't bad enough, for security reasons you do not even have access to FileStreams. In other words, if you want to try and do your own debugging and save it to a file, you have to use binary-format RecordStores that slow your program down to the point of making debugging real-time apps where milliseconds make a difference almost worthless.
Did I forget to mention threading? What a fun place to track down bugs. It's difficult enough in the World of PCs and Servers to quickly debug multi-threaded applications. But if you ever get cocky and think it's not so hard, try it on a platform where a synchronising error crashes your program with an extremely helpful message from the OS along the lines of: "java.comms.38af01@ab5550f Monty Thread -7"
Is -7 the error code or the thread number? If it's the thread number, can I just send a list of all the threads to the RecordStore I'm using to make debug logs with and track the error down within that particular thread? Sure, but ask yourself this: is the inclusion of all that expensive RecordHandling having an effect on which threads are created in what order, or is it the same order as without? Will the error appear again or did the addition of all those costly debug lines make it just squeak by this time exception-free? The fun thing about J2ME is, you haven't got a fucking clue if your program is running fine or just dependent upon a careful balance of shitty, slow code that gives the illusion of success. Have you ever watched a slapstick comedy or a cartoon where a character is unknowingly in danger but keeps bending over or turning around at just the right time to avoid some sort of attack or peril? That's essentially a very elaborate flowchart for the design of most multi-threaded mobile applications.
But we Java mobile developers are not completely without assistance with these strange messages. There's a handy little program called Panix one can download which runs in the background and, upon an error such as this, will provide you with some useful information. Of course, a few times I've tried to run it after getting an error like this the added cost of having a second program running in the background has altered the behaviour of the program I'm trying to debug so that the error doesn't appear anymore. Close Panix and try again, and there's the error just as before. Ah, the magic of threaded programming. One change in timing and your programming suddenly appears bug-free until some date later in the future when the alignment of the stars causes it to show up again and disappear just as quickly.
Perhaps some of you, most likely Java programmers who haven't had the misfortune to have to develop mobile apps, are shouting, "What an idiot! Why doesn't he just use the emulator to debug?" Ye poor, naive bastards, ye. While the emulator is handy (no pun intended) for working out the early-stage bugs, when it comes to things like Bluetooth or networking under changes in signal strength, the emulator either doesn't support it at all or would be completely unreliable as an accurate test of the real hardware. You end up commenting-out the unsupported objects and methods just to keep the emulator from exiting with an Exception, which creates a much different application from the one you're attempting to roll-out.
In conclusion, I'd just like to say that you people who use your cellular phones blindly, complaining about how Program X is always crashing or not giving a fig about what kind of work went into developing something that isn't always crashing...you people can kiss the biggest part of my ass. If it were up to me we'd all go back to using old-timey wooden telephones with hand-cranks and Java would go back to being put to use where it is best: making shitty games for Yahoo and BitTorrent programs that require tons of RAM.


