Tuesday, November 30, 2010

Writing Java effectively in a manually compiled DSL

Introduction

When I write Java, I write in a DSL similar to Java which is compiled to Java manually using the UI-actions of the Java-plugin for eclipse. If I ever change IDE, I have to learn a new DSL from scratch. Let me explain:

For me, the Java language and the Eclipse IDE with the Java plugin forms a new language. The language I program in _is_ Java, but not if you look at my keystrokes. The Autocompletion, Refactorings, Quick Helps and other automated source code changing tools provided by Eclipse (henceforth Tools) allow me to program at a much higher level than pure Java. Three years of experience with Java/Eclipse allows me to leverage the Tools a lot.

I almost never declare anything (variables, parameters, fields, methods, constructors, classes)! Instead, I rely on the Tools to do that for me. The lack of declaration will be the focus of the examples in this post.

UI-actions

In the following examples I will be using the UI-actions of Eclipse a lot, they will be mentioned by the name you see in:
Windows -> Preferences -> General -> Keys.
I will always be using keyboard shortcuts to invoke them. A UI-action invocation will be denoted in brackets: e.g. [Quick Fix] A sequence of UI-action invocations will be comma separated in a pair of brackets: e.g. [Next, Quick Fix].

The cursor position will be denoted by _.

Specific keyboard key presses will be denoted by capital letters in braces: e.g. {ENTER}.

Example 1

Scenario: need to invoke the method "Qux foo(Bar, Baz)", and store the result in a local variable.

I type in "foo;" and move the cursor one place to the left:
foo_;
[Content Assist] will immediatly give me:
foo(bar, baz);
Eclipse will now complain about the missing variables "bar" and "baz". I then invoke: [Quick Fix, {ENTER}, Next, Quick Fix, {ENTER}]. Both times Quick Fix will suggest to introduce the variables "bar" and "baz", I let them be introduced. This will automatically import the right version of Bar and Baz.
Bar bar;
Baz baz;
foo(bar, baz);
Eclipse will still complain, this time about uninitialized variables, we need to [Save] in order to write our changes to the underlying AST, which all the UI-actions operate on. Once this is done we use: [Quick Fix, {ENTER}, Next, Quick Fix, {ENTER}, {ENTER}] to initialize the variables. Focus will be given to the initialized value, allowing an immediate definition of the value, but I prefer to finish the task at hand first: invoking foo.
Bar bar = null;
Baz baz = null;
foo(bar, baz);
Finally the return value of foo should be stored in a variable, and as we still are on the line of the invocation: [Quick Assist - Assign to local variable, {ENTER}] will do just that.
Bar bar = null;
Baz baz = null;
Qux foo = foo(bar, baz);_

Comparison

This is definitely not a complete trick you should try to remember. It consists of a lot of small steps, and a lot of experience with the Tools of Eclipse. But it is certainly worth trying to understand the steps involved in the process. For me it is worth it for two reasons: I type less, and I keep focus at the task at hand instead of being bugged down in details about declaring a variable of the right type.

As a meassure of the "less typing" proposition, let's count the number of keystrokes needed to write in a completely naive way (without even using autocompletion or autoformatting) and compare that with the steps just described.

Naive way

Bar bar = null;
Baz baz = null;
Qux foo = foo(bar, baz);_
Contains 56 characters including whitespace, thus 56 key presses. For simplicity, I assume that the Bar, Baz and Qux types are imported already.

My way

Initial typing:
foo_;
contains 4 characters and one cursor move: 5 key presses. The rest: [Content Assist], [Quick Fix, {ENTER}, Next, Quick Fix, FNTER}], [Save], [Quick Fix, {ENTER}, Next, Quick Fix, {ENTER}, {ENTER}], [Quick Assist - Assign to local variable, {ENTER}].

Now, my keyboard bindings to UI-actions are very customized, so you have to trust me here. I do not consider modifiers (ctrl, alt) a key press, as they are done at the same time as the eeypress they modify, and as they are done by my thumbs which rests above the modifier anyway.
UI-actionKey pressesTimes usedToal key pressses
"foo_;"515
[Content Assist]111
[Quick Fix]144
{Enter}166
[Save]111
[Next]122
[Quick Assist - ...]212
21
21 key presses versus 56 key presses, that's a huge saving!

Example 2

Extending the previous example, I present one more example: I want to instantiate an object of a subtype of Baz, which depends on a Bar object.
Bar bar = null;
Baz baz = null;
Qux foo = foo(bar, baz);_
I start by trying to instantiate a subtype of Baz instead of the null value:
Bar bar = null;
Baz baz = new BazSubtype(bar)_;
Qux foo = foo(bar, baz);
Eclipse will complain about the missing type BazSubtype. We generate it: [Quick Fix, {ENTER}, {ENTER}]
public class BazSubtype implements Baz {
}
Our focus is now the newly generated class BazSubtype which implements Baz, but we need to implement the constructor: we go back to the previous editor and make Eclipse generate the constructor for us: [Next Editor, Quick Fix, {Arrow Down}, {Enter}]
public class BazSubtype implements Baz {
  public BazSubtype(Bar bar) {
    // TODO Auto-generated constructor stub
  }
}
Eclipse complain about the missing type Bar, as it wasn't imported automatically (which is odd!) so we import it: [Organize Imports].

We want to assign the parameter to a field. We move the focus to the parameter and generate the field and an assignment: [Quick Assist - Assign parameter to field]
public class BazSubtype implements Baz {
  private Bar bar;
  public BazSubtype(Bar bar) {
    this.bar = bar;
    // TODO Auto-generated constructor stub
  }
}
The "// TODO " should now be removed, and the field should be used for something, such as [Generate getters and setters], [Generate hashCode() and equals()] or perhaps some custom code.

If we save this class, and go back to the class we started in, we should have completed the task. [Save, Next Editor, Save]

Apart from navigating to the "bar" parameter and typing the initial constructor invocation, 14 keystrokes are used to complete the task. It is left as an excercise for the reader to compare this to a more manual way (not necessarily completely naive, like before).

Final remarks

Now it could be claimed that these examples are contrived (they are). But I assure you that this heavy use of UI-actions (and proper bindings for them!!) really boosts my productivity when coding Java.

This way of coding removes a lot of the boilerplate code which is often associated with Java. I hardly ever type the name of the Types in my programs, thus keystroke-wise my Java DSL becomes a language with stronger type inference than regular Java.

In general, the [Next, Quick Fix, {Enter}] combination is incredible effective, but it requires that you know _what_ Quick Fix will suggest as the first action to perform - this requires usage experience. Using [Next, Quick Fix] followed by an inspection of the suggestions is the way to get that experience.

It should be noted that [Quick Fix] isn't the only UI-action you should learn, but it is the one you can use most often. Others, such as [Generate getters and setters], [Generate hashCode() and equals()] generate a lot of (bug free!) boiler plate, but are not used as often. You should look through the list of available actions in the categories Quick Assist, Refactor and Source, you _will_ be surprised about the amount of usable actions!