Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Don’t Overdo the ''Principle of Least Astonishment'' Cargo Cult

DZone's Guide to

Don’t Overdo the ''Principle of Least Astonishment'' Cargo Cult

Being astonished from time to time is not that bad. We might learn something new or question the so-called best practices instead of cargo culting.

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

As we all agree, GOTO is evil, right? Relevant XKCD.

Or even funnier: New Intern Knows Best.

Of Course, GOTO Isn’t Evil

Of course, somewhere deep down in our professional selves, we know that GOTO isn’t evil, it’s just a very basic processor instruction that was available in the early days of assembly code. GOTO is everywhere. Take the following Java code, for instance:

public class Goto {
    public static void main(String[] args) {
        if (args.length == 0)
            System.out.println("No args");
        else
            System.out.println("Args");
    }
}


The bytecode that is generated from the above logic is this:

  // Method descriptor #15 ([Ljava/lang/String;)V
  // Stack: 2, Locals: 1
  public static void main(java.lang.String[] args);
     0  aload_0 [args]
     1  arraylength
     2  ifne 16
     5  getstatic java.lang.System.out : java.io.PrintStream [16]
     8  ldc  [22]
    10  invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
    13  goto 24
    16  getstatic java.lang.System.out : java.io.PrintStream [16]
    19  ldc  [30]
    21  invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
    24  return


There are two jumps. Byte code (like assembly code) doesn’t know anything about local scope nesting. There’s just a stream of instructions and we’re jumping around them. If GOTO were possible in Java (the keyword is reserved, so who knows about a future Java…?), we could write this:

public class Goto {
    public static void main(String[] args) {
        if (args.length != 0)
            goto args;

        System.out.println("No args");
        goto end;

        args:
        System.out.println("Args");

        end:
        return;
    }
}


Of course, the if-else construct is more readable and less error prone. Some of the biggest problems we’ve had with true GOTO instructions in the past are that they tend to lead to spaghetti code. In particular, notice how we’re not jumping from the “args statement” to the “endstatement”. We’re just “falling through” like in a “bad” switch statement:

switch (i) {
    case 1:
    case 2:
        System.out.println("1 or 2");
        // no break
    case 3:
        System.out.println("3 (or 1 or 2!)");
}


If you will, the switch statement is very similar to a set of GOTO instructions.

The Argument Today Is About Readability, Not Executability

The case against “classic” GOTO is a case in favor of correctness.

These days, however, we don’t have to squeeze our code into 16KB of RAM anymore, these problems are problems of the past. So we’ve started “engineering” our code (whatever that means ;)) according to “best practices,” most of which involve more humans than machines. We don’t really need “GOTO” anymore, since we have higher forms of control flow abstractions.

Java has “GOTO” in the form of labeled statements and break and continue statements. For instance:

Jumping forward:

label: {
    // do stuff
    if (check) break label;
    // do more stuff
}


In bytecode:

2  iload_1 [check]
3  ifeq 6          // Jumping forward
6  ..


Jumping backward:

label: do {
    // do stuff
    if (check) continue label;
    // do more stuff
    break label;
} while(true);


In bytecode:

 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..


Just recently, I’ve blogged about labeled statements making a case against extracting everything into a method. Why? Because a labeled statement is a (slightly unusual) way of creating a local “method.” Squint really hard for a moment and let your imagination run wild with this:

Labeled statement Method
Label name Method name
Local variables prior to label Method arguments
Break statement Return statement
Continue statement Recursion (remember: squint :))


So, if we admit the above, then labeled statements in Java are almost like one-shot local methods (oh how I wish Java had actual local methods). There’s no risk of doing things wrong like with GOTO. There’s no risk of any accidents in jumping to the wrong location because we still have a locally nested scope and everything is well-defined. So, there’s no correctness argument against using labeled statements.

Principle of Least Astonishment

Why aren’t they used more often? Probably because we rarely write such complicated imperative code where labeled statements become really useful. I do from time to time, especially inside jOOQ’s parser logicLabeled continue is especially useful to break out of an inner loop, continuing the outer loop.

Labeled breaks (as mentioned in that previous article) are less commonly useful. Usually, it is easy to refactor an if-else branch into a more readable version (e.g. by inverting if and else) than by breaking out of it. But sometimes, breaking is the more readable version, e.g. because it leads to a more consistent indentation where each branch is at the same level of indentation.

Remember the analogy with methods? Break is the same as return. Every time you want to return early from a method, you could break early out of a statement. Why not? Does it have to be a new method every time? Certainly not. Sometimes, jumping to a method is more distractive than jumping locally (again, as long as Java doesn’t have local methods).

The important thing here is that simplicity and readability, just like beauty, are in the eye of the beholder. We’re in an area where we cannot clearly say that something is correct or wrong. Better or worse. Because again, unlike GOTO, breaking out of labeled statements yields no correctness risk.

So, people start arguing in favor of the “principle of least astonishment” (especially in the comments of the DZone version of my post). Sure. Labeled breaks are a bit harder to read because we hardly ever see them in our code. That doesn’t mean they’re bad. We hardly ever use synchronized these days (with JDK library support having become much better), but that doesn’t mean synchronized is bad and shouldn’t be used. It’s rare, and thus we might get astonished. We don’t usually use exceptions as control flow signals, but why shouldn’t we? It is important to remind ourselves that the principle of least astonishment is a general guideline that allows for exceptions.

After all, astonishment is what makes us stop and think a bit more thoroughly about code. We might even learn something new, like the fact that we can break out of labeled statements.

I like Ryan James Spencer’s way of putting it:

Follow   Ryan James Spencer @_justanotherdot

I wouldn't have learned vim if I was a dogged slave to the principle, I'd look at it more like a heuristic https://twitter.com/lukaseder/status/887206134934896640 …

Learning vim… It has become a joke that programmers don’t know how to exit vim:

Follow  I Am Devloper @iamdevloper

I've only restarted my Mac 3 times in the past 6 months, each time was to exit Vim safely.

(Of course, it’s simply “:q!”, how hard can it be ;))

The point is, yes this is surprising, astonishing, unexpected. If you’re not using vim frequently. If you’re using it frequently, it’s obvious, just like any other funky IDE keyboard shortcut. Or like using the middle mouse button to close tabs in almost any program (that was a revelation).

In order to be productive, we shouldn’t be astonished constantly. We should mostly be in our technical/infrastructure comfort zones because most of us write business logic, and we want to spend our precious time and brain cells on that, not on the infrastructure logic. Everyone likes SQL. No one likes working around funky JDBC edge cases.

Another interesting reply by Chris Martin:

Follow   Chris Martin @chris__martin

I think the idea is astonishments ought to be selected and worthwhile, not a constant onslaught of random novelty https://twitter.com/lukaseder/status/887206134934896640 …

Absolutely. That was the point of my previous article. Breaking out of labeled statements is cool every now and then. It might be astonishing if you don’t write complex algorithms (like parsers) every day. It is not at all astonishing if you do.

So, please. Be open. Stop cargo culting. Use labeled statements and break out of them. Every once in a while. When you think that makes your code clearer.

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
java ,goto ,principle of least astnonishment ,cargo cult

Published at DZone with permission of Lukas Eder, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}