Groovy: Public Variables Aren't

2013-12-15 in groovy java
Not my favourite programming language

One of the most obvious ways with which Groovy tries to “reduce” boiler-plate is to make all members (both variables and methods) of any class public by default, as opposed to Java in which members are package-private by default. While this is already a questionable design decision at best, an interesting facet of the implementation is that implicitly public member variables are private, though another Groovy feature masks this detail when operating within Groovy.

We’ll start with a Groovy class that just has some members of varying types and access modifiers. You can compile these files yourself by dropping them into the base project.

Base.groovy

1package gl.lin;
2
3public class Base {
4  public int pub;
5  int integer;
6  boolean bool;
7
8  protected int prot;
9}

If we make a derived class in Java, where the compiler actually verifies that things we reference exist, we discover some interesting things about what Groovy did with the access modifiers.

Deriv.java

 1package gl.lin;
 2
 3public class Deriv extends Base {
 4  public void foo() {
 5    pub -= 1;
 6    integer += 1;
 7    bool = !bool;
 8    prot += 2;
 9  }
10}
 1$ ./gradlew build
 2
 3...
 4
 5src/main/groovy/gl/lin/Deriv.java:6: cannot find symbol
 6symbol  : variable integer
 7location: class gl.lin.Deriv
 8    integer += 1;
 9    ^
10src/main/groovy/gl/lin/Deriv.java:6: operator + cannot be applied to integer,int
11    integer += 1;
12            ^
13/tmp/base-project/src/main/groovy/gl/lin/Deriv.java:7: cannot find symbol
14symbol  : variable bool
15location: class gl.lin.Deriv
16    bool = !bool;
17    ^
183 errors

Analysing the compiler’s output tells us the following:

  • pub really was public, as we declared it to be. This is exactly what anyone in their right mind would expect.

  • prot was similarly at least protected, as the derived class could access it directly. Again, exactly what should be expected.

  • integer and bool are nominally public, and in other Groovy code can be accessed as such. However, our Java code failed to compile — since both classes are in the same package, and one class derives from the other, this means that these two variables are actually private.

What Groovy actually does with implicitly-public variables is to make them private, then generate JavaBeans-style get() and set() accessor methods for them. Groovy implicitly calls these when a non-existent field is accessed, so the fields really look public to other Groovy code.

But, of course, in Java we end up having to write things like

Deriv2.java

 1package gl.lin;
 2
 3public class Deriv2 extends Base {
 4  public void foo() {
 5    pub -= 1;
 6    setInteger(getInteger() + 1);
 7    setBool(!isBool());
 8    prot += 2;
 9  }
10}

Notice in particular how the verb that should be get is actually dependent on the declared type of the variable — it changes to is for booleans. This quirk, albeit conventional for JavaBeans, is especially interesting since variable types are often undeclared in Groovy.

As is par for the Codehaus course, Groovy has taken something from Java that couldn’t possibly be simpler — the public access modifier — and transformed it into something inconsistent and surprising. Groovy is the only language I know of where making a variable protected increases the accessible scope of what previously was a “public” field.

Of course, some people will say that a private-field-with-accessors pattern is “good programming practise”, and that this is obviously what Groovy is doing. This is an empty argument: A getter-setter pair whose contract indicates that the setter takes no further action, and whose getter returns the most recent set value, is a public variable, merely a less convenient one.

Regardless of how one feels about public variables in code, a programmer has to be able to deal with them effictively, especially in a code-base built in Groovy, which is almost certainly full of variables that are implicitly public (Is it intentional? Maybe the original programmer forgot the access modifier?). Writing new code in Java within such a code-base becomes unnecessarily tedious, especially in the context of an anæmic domain model, giving the programmer a choice of continuing the use of Groovy and worsening the problem, or writing Java littered with verbose get/set calls.