Laurence Gonsalves prefers compile-time code generation over runtime reflection. He writes,
I think all-in-one toolchains (eg: IDEs) are actually part of the reason Java has both not enough magic (like Rob says) and also the wrong kind of magic (runtime reflection). If there was a standard way to plug code generators into Java (or better yet: a Common Lisp-like macro system) then the vast majority of places where people use runtime-magic (reflection) today could be replaced by compile-time magic, and your static analysis tools (ie: compile-time type checking, code navigation tools, refactoring tools, etc.) would actually still work.Java has a standard way to generate code: the annotation processing tool. Laurence acknowledges APT, but claims it it's useless for his purposes:
[...] I have to say that annotation processors are like lobotomized macros. I did some experiments wit them a few years ago (when Java 5 first came out), and it was pretty difficult to do any useful metaprogramming with them without doing really nasty things.Among others, one core claim Laurence makes is that refactoring still works if there's a standard way to generate code. I disagree.
Code Generation
Suppose I'm using code generation to do JSON-Java object mapping. One hypothetical code generator takes a JSON text:
{
"name": "Jesse Wilson",
"age": 28,
"favorite taco flavors": [ "fish", "steak" ]
}`</pre>...and generates a Java class:
<pre class="prettyprint">`/* GENERATED FILE! DO NOT EDIT */
public class TacoCustomer {
private final String name;
private final double age;
private final ImmutableList<String> favoriteTacoFlavors;
public String getName() {
return this.name;
}
public double getAge() {
return age;
}
public ImmutableList<String> getFavoriteTacoFlavors() {
return favoriteTacoFlavors;
}
public static TacoCustomer parseJson(String jsonText) {
...
}
}`</pre>This looks fine enough, almost like code a human would write. But when I exercise my refactoring tool to rename `getFavoriteTacoFlavors` to `getFavoriteTacos`, I face one of two ugly situations:
-
It appears to work. But later when I regenerate the code, my edits are clobbered and my application no longer builds. Yuck.
-
It doesn't work, because the generator made the file non-writable file. Now I need to figure out the code generator, use its name mapping to rename accessors, regenerate, and manually fix the callers.Code generation is bad because you cannot edit your code directly. You need to edit something upstream.
Runtime Reflection
To contrast, here's how the equivalent runtime-reflection solution might look. We manually write an interface that describes our JSON schema, plus some pretty annotations to define the mapping:
`public interface TacoCustomer { @JsonProperty("name"); String getName();
Exercise the runtime-generated code with a shared, type-safe entry point. It might be invoked like this:@JsonProperty("age"); double getAge(); @JsonProperty("favorite taco flavors"); ImmutableList<String> getFavoriteTacoFlavors();
}`
` String jsonText = ... TacoCustomer jesse = new JsonToJava().parse(TacoCustomer.class, jsonText);
Refactoring this interface just works, and I can even attach meaningful Javadoc to its methods. The interface can be an inner-interface of another class. And it can host a manually-coded inner classes.
Runtime reflection is good because it lets you put your code wherever you want it, and edit it directly.