Friday, August 13, 2010

The Compiler Is Not The Audience

Is the code that you write making life easier or harder for the next person who has to work in the same area? Are you creating complexity and fragility that will slow them down, or platforms, patterns, and utilities that will speed them up?

6 comments:

Anonymous said...

The problem with Java is that much of the code you have to write only exists to please to compiler and does nothing to ease understanding by other programmer, sometimes it makes it harder.

Explain the value of this:

ArrayList myStrings = new ArrayList<>();

String data = (String) myStrings.get(0);

The above exists only to please a compiler, and a stupid one at that.

It gets much worse as I am sure you know.

When 50-80% of the code written for Java is only there for the compiler and has absolutely nothing to do with the acutal program, it is rather difficult to not treat the compiler as your audience, in fact it is impossible.

Walter Harley said...

The example you give is valid, but also something that has been addressed in suggested improvements to Java. I haven't paid enough attention to Java 7 to know whether it made it in but it was on the short list.

That example is trival, though. How about the more common one:

List myStrings = ArrayList();

In that one, there is some duplication (e.g., the String type), but also some valuable redirection (i.e., we are saying that although the implementation is an ArrayList, that is just an implementation detail and the user is only allowed to treat it as a List).

The () is arguably unnecessary. One could imagine:

List myStrings = ArrayList;

but it's not obvious to me that it really buys you that much. I have never been one of those programmers who believes that minimizing keystrokes is the best way to make a program clearly readable.

In the code that I write, I do not feel that 50 to 80% of it is there for the compiler; if anything, I could write much tighter code if I only cared about the compiler. But in some way these are orthogonal: the question is, if you took the compiler out of the picture, and only considered the human reader, what would the program look like?

You would probably need a longer code snippet to illustrate an answer to that question. I encourage you to post one!

Walter Harley said...

Gah! That should have been

List <String> myStrings = ArrayList<String>();

HTML conversion ate the tag-looking things.

Walter Harley said...

Oh, to respond a little further: your example was non-generified, which is why you had to cast the return of myStrings.get(0). To deconstruct your example, here's what you're saying, in human language:

1. You have a memory location called "myStrings", which is of type ArrayList. (You didn't generify it; I would have, stating further that it is an ArrayList that contains Strings. You could have chosen to say it was just a List, or even just an Object, so the fact that you call it an ArrayList tells the human reader something about your intent.)

2. You initialize it by setting it to an actual ArrayList object, with empty contents. (You could have initialized it to something else, so this is informational to the compiler. Arguably the compiler could default to calling an empty constructor of the specified type, rather than defaulting to null; but more often you see an assignment like List x = new ArrayList(), so that default does not seem generally useful.)

3. You call a method on the object. Since the object is a container, you get back an Object. There's no way the compiler could do any better than that; you've done nothing so far to tell it that it contains Strings, Integers, or Widgets. If you had generified the container, the compiler would have more information.

4. You assign the result to a variable which you want to treat as a String. Since the result was an Object, you need to cast it. You could argue that the cast is unnecessarily verbose and you should always be able to cast anything to anything, finding the problem at runtime rather than compile time; in this case, with a pure downcast, that is a valid complaint. If you had an ArrayList<String> and you tried to cast it to an Integer, though, the compiler would be able to complain and you would find a bug at compile time rather than runtime, which is beneficial.

Anonymous said...

Yeah I forgot the .

I gave a simple example that I think you focused too much on.

In Java 7 String data = (String) myStrings.get(0); is still required.

Java 8 is supposed to get proper anonymous functions that have the property of being a closure, so that will cut down a lot of unneeded verbosity and gimped nonsense like inner and nested classes if it makes it in. It was supposed to be in 7.

I am not sure how it will effect methods such as Collections.sort where you should be able to bypass Comparable entirely and use the anonymous function in place of compareTo() killing both Comparable and Comparator(see how functionality that exists in other langauges results in less code?). We will see how it works out.

Another example:
Interfaces only exist to help the compiler, many other strongly-typed languages don't need them and in fact would be pointless in them, and not just strongly-typed, dynamic languages like Python or Ruby, but statically typed like Scala, Haskell, OCaml.

Much of the code in libraries like Hibernate and Spring exists to get around the compiler, more specifically Java's brain-dead type system.

My second used language is Ruby. The reason why Ruby requires much much less code is not because there are no generics or type declarations or that () is usually optional. That has nothing to do with it.

It is that things that often take significant amount of LOC like interfaces(and implementations of said interfaces) and abstract classes have no purpose. It is real, actual code you don't have to write, debug or maintain that you must do in Java.

Seriously, take a random class file that you have written, that isn't completely trivial and count up the symbols that exist only because the compiler demands it and if you removed it it would not impact the programmer-understandibility of the code.Then find what percentage it is for that class.

You will likely find that it is 50-80% of the class file.

I din't write this to bash Java as I do use it a lot, nor to disagree with your point which is correct. I am just trying to point out that in Java, your #1 audience member is always the compiler.

In C++, if you use a lot of templates you get the same thing.

In other statically and strongly compiled languages like Haskell, Scala, etc most of the code to port that class over disappears and that code will be almost entirely for the java compiler.

Walter Harley said...

"in Java, your #1 audience member is always the compiler"

So, any given language is more or less verbose, with various trade-offs. To take your latest example, "String data = (String) myStrings.get(0);", we can argue that 'data' does not need to be typed, or alternately that myStrings.get(0) does not need to be cast, but for each of those assertions there is also a reasonable counterargument. If you like Ruby better, use Ruby; no language is perfect.

My point is that these arguments are irrelevant. Every programming language has shortcomings but in all of them you need to address the human audience rather than the compiler. You may be asserting that Ruby makes it possible to write more human-readable code compared to Java. However, so far you have not done anything to demonstrate that. I encourage you to do so. Personally I believe that it is possible to write very human-readable code in Java; the language is not the issue, it is the writing style.