Thursday, December 17, 2009

NetBeans -- The Least Terrible IDE

My favorite text editor is MacVim. It lays a nice Aqua interface over the top of the classic editor. It is not perfect. I could rant for hours on its shortcomings, but it is still a shorter rant than I have for Aquamacs and TextMate, and these are the only two other text editors that I find even usable.

But text editors and IDEs are not the same thing, despite the fact that they do many of the same tasks. When I start working with a Java or Scala project, I want the advanced functionality that a full-featured IDE offers. (And yes, I am aware of Eclim, but that does not really change the fact that an IDE has been brought into the process.)

I have played around with a number of IDEs over the years; I have yet to settle on one. Eclipse is the classic, with extensions and plugins for about everything. Now that IntelliJ has an open-source version, I have been spending a good deal of time with that. Nonetheless, NetBeans is still, in my opinion, the best.

It is not that it is the prettiest. In fact, it is by far the ugliest of the three on any other system besides OSX, and still not that pretty even then.

It is not that it has the best Scala plugin. While I have had more success with the NetBeans plugin than the Eclipse one, I have found IntelliJ's to be more full-featured (albeit a little more of a hassle to install, since the version offered through their plugin manager does not seem to work. But that is another rant...).

And while I love some of the plugins available (the jVi plugin in particular being the best vi emulator that I have seen for any IDE or editor), they still pale compared to the wealth offered by Eclipse.

No, these are pluses, but relatively minor points. The reason that I keep returning to NetBeans, in a word, is Ant.

There is a fantastic article by Jeff Atwood titled "F5 is not a build process". This comes close to the core of the issue: every project should have a build process that functions outside of the IDE. This is a problem I have seen even with some open-source projects. Instructions like "start up NetBeans" or "download the Eclipse plugin" should never appear for any tool that is not specific to an IDE.

Unlike Eclipse and IntelliJ, NetBeans does not have its own special build tools. It defers this to Ant (or yes, you can use Maven instead). As a result, the build process is available outside of NetBeans. In my experience, projects designed with the other IDEs only create a build process as an afterthought.

This is huge. It means that the IDE actually takes care of the build process for you, as is just and proper. It also, however, allows you some extra options when things go wonky.

I do some weird things with my IDE. Using Scala is one of the more tame uses, but even that can start causing problems when you combine it with Antlr or JavaCC. And God help you if you want to add unit tests into this mix. I have managed to confuse and break every IDE I have used, to one degree or another. (To be fair, IntelliJ has generally handled more weird stuff than the others).

The beautiful thing about NetBeans is that when it stumbles, it can still function. For one project, NetBeans gave me hundreds of spurious errors, but the build process still worked.

NetBean's generated ant files include a number of hooks where you can add in your own ant tasks, a feature that I have found invaluable. Furthermore, if something in NetBean's ant files is horribly broken, you can replace it with your own; not something that I would recommend, but it can save you in extreme cases.

And so, despite NetBeans numerous quirks and limitations, I still list it as my favorite IDE. And while I believe the NetBeans team will address the product's current limitations sooner or later, I doubt that either the IntelliJ or Eclipse teams will ever fix what I see as a fundamentally broken architectural decision.

Monday, April 20, 2009

What main method?

I've been looking for an excuse to learn more Scala for a while. Currently, I am working on implementing the TAPL interpreters in Scala.

I ran into a problem that took me a while to figure out. To illustrate, I'll give a simple example. Consider this main method:

def main(args: Array[String]) = {
var sum = 0
for (s <- args) {
sum += s.toInt }
if (args.length > 0) {sum/args.length} else 0

}


This simply averages a list of numbers specified from the command line. But when I try to run it, I get this error:

Exception in thread "main" java.lang.NoSuchMethodError: main
Java Result: 1

With a very slight change, this will work:

def main(args: Array[String]) = {
var sum = 0

for (s <- args) {
sum += s.toInt }
println(if (args.length > 0) {sum/args.length} else 0)

}


This just prints out the average, no complaints.

So what is the difference? Why does printing out the result make a difference? The answer requires a little understanding of the way that Scala works.

Scala relies on type inference. This means that it tries to determine the types of your variables (including methods) from analyzing your program. This is not the same as dynamically typed languages like Perl or Ruby -- the type checking is still done at compile time. The compiler is smart enough to infer the types of your variables from the context.

But there is a problem here. It also has implicit returns. The program will automatically return the last executed expression in the program. This has been a popular feature in many programming languages, including the Lisps, Ruby, and the various ML dialects (of which Scala itself is something of a shirttail cousin). Personally I despise this feature, but I am in the minority.

Combining these features with Java programmer expectations leaves a nice little trap. The main method must not return anything. (Or if you rather, its return type must be Unit). If we copy the second version into the interpreter, we will see this:


main: (Array[String])Unit

The second version works because it prints out the results -- println has a return type of Unit. However, the if statement in this example has a return type of Int. The first version of the program, although it looks nearly identical, has a different type:

main: (Array[String])Int

As a result, Scala looks for main: (Array[String])Unit but cannot find it and returns an error message. This is easy to fix:

def main(args: Array[String]): Unit = { ... }

Or better yet, we can just remove the equals sign from the original:


def main(args: Array[String]) { ... }

Interestingly, the original version actually catches a problem in my code; I am calculating an average, but then I do nothing with it. More often than not, if you see this error, you are probably doing something wrong -- why calculate a value for no reason?

Still, when you are learning a language, a stumbling block like this can be confusing. The error message is clear enough to anyone who understands Scala. My only suggestion would be to add a special case for main specifically. It is a slight hair in the implementation, but it can save clueless newbies (like me) a little frustration, and really... what other kind of method would be named "main"?