Skip to content

Readability

Ori Roth edited this page Apr 2, 2017 · 3 revisions

Readability is an abstract, illusive objective, which, as dictated by the nonsense principle is often abused. Religious wars often waged on issues of "readability". The common theme to these is is that what is described as readable by one, is vehemently portrayed as unreadable by others. Arguments in readability disputes use technique such as "proof by authority", "proof by exhaustion", tautologies and the such. The truth is that most differences in opinion which are not mere superstitions, are traceable to differences in cultural background and education.

The following guidelines seem to stand out as being as universal, not as disputable as others, means for enhancing readability

  1. The uniformity rule, suggesting that unneeded variety is minimized.
  2. Imitation of common textual and mathematical typographical conventions.
  3. Presentation of shorter and easier to understand items first.

Typographical conventions

  • In order to take advantage of the reader's familiarity with mathematical functions, do not separate a function name from its arguments by spaces. Thus, you should write y = sin(x); rather than y=sin (x);
  • After each of the following control flow keywords if, switch, for, while and catch we find code written in parentheses. Each of these keywords should be separated from the opening parenthesis (the "(" character) by a single space, to distinguish these from function calls.
  • Similarly, return and throw are not functions. Do not wrap the returned or thrown expression in parentheses.
  • On the other hand, the this and super keywords can be used as constructor functions. If this is the case, there should be no space between such a keyword and the opening parenthesis.
  • In expressions, it is safe to assume that the reader is familiar with the precedence rules of mathematical expressions. Extra parentheses that restate these precedence degrade readability. Thus, do write if (b*b - 4*a*c > 0) rather than if (((b*b) - (4*a*c)) > 0)

More generally, it is wise to assume that the reader is familiar with the following order of precedence (from highest to lowest)

  1. Multiplication and division, that is *, / and %.
  2. Addition and subbstraction, that is + and -.
  3. Comparison, that is ==, !=, >, <, >=, and <=.
  4. Negation, that is the ! operator.
  5. Conjunction, that is the && operator.
  6. Disjunction, that is the || operator.
  7. Assignment, that is =, +=, -=, *=, %=, &=, |=, <<=, >= and >>=.

The obscure operators are those that do not commonly occur in mathematics. Use parentheses for operators such as instanceof, and bitwise operators including &,^, >>, and <<.

Typographical convention of mathematics dictate that the space surrounding higher precedence operators is smaller than that surrounding lower precedence operators. In b^2 - 4ac. for example, the space between a and c is slightly smaller than the space around the minus sign. It is a good idea to try to imitate this in your code, by writing e.g., b*b - 4*a*c instead of just b*b-4*a*c Never use spaces to mislead your reader, as in the following b * b-4 *a *c

This technique of enhancing readability is limited since mathematical typography use various fractions of the (average) character width as space padding around operators.

Mathematical conventions

Most software engineers are graduates of computer science or other scientific discipline, and have had at least basic mathematical education. They would find code relying on common conventions used in mathematics very readable. This is the reason that integral loop variables often use the letters i, j, and k as in the above example.

This is also the reason that cot(double x) is immediately recognized as the function computing the trigonometric cotangent, and that the name x for it argument is recognized.

For the same reason, a generic function is often named f in code.

Simpler first

Remember that your code is read sequentially. Thus, avoid loading the reader's memory unnecessarily:

  • Place the shorter branch first in a two-way conditional statement.
  • Treat simpler cases first.
  • Place simpler functions before more complex ones, unless there is a compelling reason not to do so.

Example

Consider the following routine, taken from class Constant in package classycle.classfile of the classycle (http://classycle.sourceforge.net/) project.

public static Constant[] extractConstantPool(DataInputStream stream) throws IOException {
   Constant[] pool = null;
   if (stream.readInt() == MAGIC) {
     stream.readUnsignedShort();
     stream.readUnsignedShort();
     pool = new Constant[stream.readUnsignedShort()];
     for (int i = 1; i < pool.length;) {
       boolean skipIndex = false;
       Constant c = null;
       int type = stream.readUnsignedByte();
       switch (type) {
         case CONSTANT_CLASS:
           c = new ClassConstant(pool, stream.readUnsignedShort());
           break;
         case CONSTANT_FIELDREF:
           c = new FieldRefConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
           break;
         case CONSTANT_METHODREF:
           c = new MethodRefConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
           break;
         case CONSTANT_INTERFACE_METHODREF:
           c = new InterfaceMethodRefConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
           break;
         case CONSTANT_STRING:
           c = new StringConstant(pool, stream.readUnsignedShort());
           break;
         case CONSTANT_INTEGER:
           c = new IntConstant(pool, stream.readInt());
           break;
         case CONSTANT_FLOAT:
           c = new FloatConstant(pool, stream.readFloat());
           break;
         case CONSTANT_LONG:
           c = new LongConstant(pool, stream.readLong());
           skipIndex = true;
           break;
         case CONSTANT_DOUBLE:
           c = new DoubleConstant(pool, stream.readDouble());
           skipIndex = true;
           break;
         case CONSTANT_NAME_AND_TYPE:
           c = new NameAndTypeConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
           break;
         case CONSTANT_UTF8:
           c = new UTF8Constant(pool, stream.readUTF());
           break;
       }
       pool[i] = c;
       i += skipIndex ? 2 : 1; // double and long constants occupy two
                   // entries
     }
     return pool;
   } else
     throw new IOException("Not a class file: Magic number missing.");
 }

The main if statement in the above has two branches. In the first, comprising 49 lines, the entire processing is carried out. The second branch is a one liner, taking care of the case that the first 16 bits word in the input is not a magic number. While examining the first branch, the reader should constantly be aware that it is executed conditionally, and that there is still another main case to worry about.

Flipping the order of the two branches removes this burden. Further, since the case that input does not start correctly leads to abnormal early termination of the function, the second branch does not need to be nested.

public static Constant[] extractConstantPool(DataInputStream stream) throws IOException {
    Constant[] pool = null;
    if (stream.readInt() != MAGIC) 
        throw new IOException("Not a class file: Magic number missing.");
    stream.readUnsignedShort();
    stream.readUnsignedShort();
    pool = new Constant[stream.readUnsignedShort()];
    for (int i = 1; i < pool.length;) {
      boolean skipIndex = false;
      Constant c = null;
      int type = stream.readUnsignedByte();
      switch (type) {
        case CONSTANT_CLASS:
          c = new ClassConstant(pool, stream.readUnsignedShort());
          break;
        case CONSTANT_FIELDREF:
          c = new FieldRefConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
          break;
        case CONSTANT_METHODREF:
          c = new MethodRefConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
          break;
        case CONSTANT_INTERFACE_METHODREF:
          c = new InterfaceMethodRefConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
          break;
        case CONSTANT_STRING:
          c = new StringConstant(pool, stream.readUnsignedShort());
          break;
        case CONSTANT_INTEGER:
          c = new IntConstant(pool, stream.readInt());
          break;
        case CONSTANT_FLOAT:
          c = new FloatConstant(pool, stream.readFloat());
          break;
        case CONSTANT_LONG:
          c = new LongConstant(pool, stream.readLong());
          skipIndex = true;
          break;
        case CONSTANT_DOUBLE:
          c = new DoubleConstant(pool, stream.readDouble());
          skipIndex = true;
          break;
        case CONSTANT_NAME_AND_TYPE:
          c = new NameAndTypeConstant(pool, stream.readUnsignedShort(), stream.readUnsignedShort());
          break;
        case CONSTANT_UTF8:
          c = new UTF8Constant(pool, stream.readUTF());
          break;
      }
      pool[i] = c;
      i += skipIndex ? 2 : 1; // double and long constants occupy two
                  // entries
    }
    return pool;
}
Clone this wiki locally