I just had a rather difficult day of programming that reminds me how frustrating writing code can be. I like writing code, and as an architect I often don't get to spend as much time writing code as I'd like. But today I spent too much time, or at least didn't accomplish as much as I would've liked to in the time I spent.
My task was to write a relatively simple JMS producer and consumer pair, just to show that we could successfully send and receive a message over a topic or a queue. I quickly got past the coding errors and the runtime exceptions and got to the point where the code seemed to be sending a message successfully, but didn't seem to be receiving one. Everything seemed to work fine, except nothing was happening in the receiver.
This kind of asynchronous messaging problem is notoriously difficult to debug. Maybe the sender isn't sending right, or the receiver isn't receiving properly, or maybe the messaging system isn't working, or maybe some combination of the three. When it doesn't all work, it's hard to tell what's wrong. I spent several hours this afternoon looking at code that ought to work right but clearly wasn't, changing all kinds of things just to see if something would help but ultimately making very little visible progress.
Luckily a colleague came through for me. He was reviewing my code and comparing it to some JMS sample code, and eventually he asked, "Where do you call start?" I was only half paying attention and didn't understand the question, but I had it in the back of my mind for several hours that I seemed to remember the need to start things somehow. But what? MessageConsumer didn't have any sort of start() method; what did?
My colleague pointed out to me that once you have a JMS Connection, you have to call start(). More specifically, according to the javadocs, start() "Starts (or restarts) a connection's delivery of incoming messages." So that was it. I added connection.start() to the receiver's code, started it again, and out popped all those test messages I'd been sending. start() was all I needed. Now I'm going back and uncommenting all of the code I commented-out or otherwise changed trying to find the problem.
This seems like an example of the ultimate limitations of programmers writing code: It can be very difficult to get simple things working, and simple mistakes can cause strange behavior that is difficult and time-consuming to diagnose. (For another example, see Problem with Service Locator Pattern.)
Even with higher-level languages like Java, is code just too detailed and fragile a way to describe how an application should behave? Shouldn't I be able to simply describe the sender and receiver and have the IDE write a pair of compatible ones for me? Should I have to remember that it's not just enough to have to create a Connection, you also have to start it? But you can send to a Connection without starting it, you just can't receive from it? And receive() on an unstarted Connection doesn't throw an Exception, it just waits forever on a message that is never going to be delivered? Isn't this a lot to have to know, just to send (and receive!) a message?
I guess this why fans of model driven architecture think it'll save the world. I think the jury's out on that. But there's got to be a better way.