Clojure is certainly my language of choice nowadays.
But recently I spent a day working on an old ~35k LOC Java code base, and it reminded me what I still love about Java.
The tools are magnificent
I was using Eclipse to refactor and modernise the code base. This involved stuff like:
- Updating old data structures to use generics
- Renaming packages
- Updating to Java 1.7 syntax
- Renaming functions and argument names to improve consistency and make them more self-documenting.
Looking back at the commits, I changed about 10k lines of code in about a day’s work. That’s roughly a line every four seconds. Certainly faster than I can type.
And the only reason I could do this was the strength of the tooling. The major time savers were:
- Automatic warnings. Most importantly, this flagged which areas of the code base still needed work. A lot of refactoring is fairly brainless (e.g. conversion to generics), so I did whatever was needed to trigger them (e.g. changing a data structure to have a new generic type argument) and then used the warnings like a big “TODO” list.
- “quick fixes” for may common warnings that I could just apply with a single mouse click. This helped tidy import statements, add generic type arguments etc.
- Continuous syntax checking – this meant I could work very fast, in the confidence that mistakes would get picked up immediately by the syntax checker and highlighted in red
- Refactoring – renaming and re-ordering code is trivial with good refactoring tools. This helped enormously with function renaming, argument renaming, class renaming etc.
It was a long day’s work but at the end I was pretty stunned by what I had a achieved. A completely modernised code base, all tests passing and the warm glow of a job well done. I had even found and fixed a few subtle bugs and logic errors that my re-factoring work had exposed.
What was really surprising? Refactoring actually turned out to be fun. So much so that I even got carried away in my coding and turned up late to the pub. For anyone who knows me (or is at least vaguely familiar with the English affection for pubs….), that should tell you how seriously I must have been enjoying my refactoring :-)
Will dynamic languages ever match this?
Anyway, this experience led me to thinking when and if the tools in the Clojure world will get to this level of maturity and sophistication in terms of code management and manipulation.
Certainly, the Java world has had many years to mature and develop a comprehensive tool ecosystem. It shouldn’t be surprising that the tools are good, they’ve had plenty of years of widespread use and polishing. They’s had massive ongoing contributions from dedicated open source communities like the Eclipse Foundation and its related projects. There’s also the advantage of corporate use: the huge popularity of Java in the corporate world over the last 15 years has undoubtedly helped drive investment in tools.
Nevertheless, there are some things about Java that I think are intrinsic advantages in the tool space, more than just time to mature:
- Static typing: this is clearly a big advantage: static types effectively provide an automatic sanity-check on every single line of code by asserting that you haven’t make a type error. While re-factoring, it helps you preserve an important invariant, namely that you haven’t changed the type of the inputs or outputs to any function. In a dynamic language you don’t have this assurance – ever passed a map to something that should have received a function in Clojure and wondered why you have a bug?
- Compile time certainty: Java compilers can effectively build a complete model of the code at compile time – all the classes, methods are well-defined at compile time and this information can be used by the compiler to spot issues, provide warnings and make guarantees about the safety of many re-factoring operations. By contrast, it is much harder for a dynamic language to achieve this: the full model of the code only gets constructed at runtime (e.g. after you have run through all your “.clj” files, expanded macros and called all the relevant defines in Clojure). Thanks to our good old friend Turing we can prove it is impossible to fully reason about the overall structure of our code in such a situation.
- A simple language – Java has a deliberately simple syntax – it was designed to be “Simple, Object-Oriented and Familiar“. As a Lisp, Clojure syntax is simple too of course – but in Clojure you can extend the language in arbitrary ways with macros, whereas in Java it is totally fixed. This guaranteed simplicity makes things much easier for tool writers. As a result Java simultaneously annoys programmers who wish they could do things like override operators while delighting the same programmers with awesome tools. Hard to have it both ways…..
- Stability – admittedly this is more of a social phenomenon than a language features, but it is still important: the Java world has shown a very strong commitment to language and API stability, beyond that of most dynamic languages. You might argue that is a downside if you want innovative new language features, but again it is an significant advantage for tool makers and users who don’t have to deal with nearly as many version idiosyncrasies or breaking changes
For all these reasons, I am inclined to believe that it will be very hard for any modern dynamic language to catch up with Java in the many areas of the tooling space (by which I refer to areas like refactoring, automatic code quality analysis etc.)
Still there are compensations…
Nevertheless, I’m still going to stick with my Clojure. In particular there are two things that I get in Clojure that I think (mostly) compensate for losing the phenomenal IDE tooling that Java offers. These are:
- The REPL – one of the things that truly makes Clojure coding great. I’d rather have the REPL at my fingertips and do proper interactive live coding than any number of static code analysis tools
- Conciseness – so I may not have all the great Java refactoring tools. But if I need only 10% of the number of lines to do things, perhaps this doesn’t worry me that much. When I refactor a line of code in Clojure, it is more likely to be a genuine logic change than a mechanical change that is require to conform with syntactic formalities.
Finally this post wouldn’t be complete without a nod to the importance of testing. You should write tests regardless of whether you are using Clojure or Java. And it is particularly important if you are about to do a lot of refactoring that risks introducing careless errors or bugs. But I think it helps in dynamic languages a lot more: testing is an important way to compensate for the loss of the various compile-time guarantees that statically typed languages provide, especially around type safety.