Disclaimer
This post is not intended as some kind of Groovy-bashing. It rather points out some of the common problems and maybe differences in the mindset between Java and Groovy developers.
Groovy powers Grails and Grails empowers us to build modern web application conveniently and fast. We have come to love many of the features of Groovy and Grails. Sadly, every once in a while you stand puzzled before some bizarre error message. Often it is our own fault, but sometimes it is a real problem in the software stack we are using. Grails was quite buggy some time ago (pre 1.2) but we are more and more happy with its development. Groovy itself has been quite solid for a long time but things like dynamic dispatch with null values, boolean truth and its NullObjects (e.g. null + null == "nullnull"
!) may yield some surprises.
A few days ago we received an failure report from a client and found the corresponding exception in our logs:
java.lang.UnsupportedOperationException at $Proxy12.toString(Unknown Source) at java.lang.Throwable.getLocalizedMessage(Throwable.java:267) at java.lang.Throwable.toString(Throwable.java:343) at java.lang.String.valueOf(String.java:2827) at org.apache.commons.logging.impl.SLF4JLocationAwareLog.error(SLF4JLocationAwareLog.java:211) at org.apache.commons.logging.Log$error$0.call(Unknown Source) at UserGroupController.callSendMail(UserGroupController.groovy:117)
You may be able to see that the UnsupportOperationException
is thrown in an attempt to log a Throwable
. Yeah right, toString()
of some generated proxy object throws an exception. This is not exactly what you would expect from a call to the toString()
-method and violates the principle of least astonishment. Equally bad in this case is he fact that it shadows the real cause of the problem. Some research revealed that this is a bug in Groovy itself.
This leads me to my highly speculative and opionated “mindset” hypothesis. In the java world people try to avoid the unexpected at the cost of clunkyness and verbosity. Examples for this mindset are static typing and checked exceptions. Sources of uncertainty, like reflection and downcasts, are minimized and put in defined areas of code often documented and annotated. Everyone tries to strictly define everything and relies on these definition. All this sums up to quite a burden and may feel like a cage.
Enter Groovy which removes a lot of the burden by its more liberal syntax and other language features. Suddenly, you are free and do not have to define everything and life feels a lot easier at first. You do not need well designed class hierarchies, just use duck typing. Everything is dynamic and you can cope with things at runtime. Not only the above bug (it is still a bug, not intended behaviour!) but also the handling of boolean values or null may make the seasoned java developer shout “WTF!” from time to time.
So what is the bottom line now? Both worlds have their pros and cons. In Groovy fundamental things can surprise you at runtime so there is an even greater need for good test coverage and a thorough knowledge of the language. Groovy (and Grails for that part) are not as easy as they seem in smaller projects when going big. The Java camp has a solid base but should strive to make life more convenient for the developer. It is mostly the verbosity and some missing features like type inference or closures that are driving people away from Java. Imho both languages and the Java platform with its great community and eco-system have a bright future ahead but there are some bumps on the way ahead.
I sometimes run into problems with GString vs String. Like all languages, there are good practices that go with it which eliminate these problems. I use Groovy for all my scripting needs and it fits the bill pretty well, definitely better than Perl and Python.
You are so right, I did miss out the String-thing and a few other things but yes, overall Groovy is quite nice and works well for a lot of tasks. All languages have their pitfalls…
Think I found a patch for that Groovy bug, fingers crossed it will be fixed in 1.8.2 😉