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:
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);
if
, switch
, for
, and catch
we find code written in parenthesis. Each of these keywords should be separated from the opening parenthesis (the "(" character) by a single space, to distinguish these from function calls.return
and throw
are not functions. Do not wrap the returned or thrown expression in parenthesis.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 parenthesis 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):
*
, /
and %
.+
and -
. ==
, !=
, >
, <
, >=
, and <=
.!
operator.&&
operator.||
operator.=
, +=
, -=
, *=
, %=
, &=
, |=
, <<=
, >=
and >=
.The obscure operators are those that do not commonly occur in mathematics. Use parenthesis 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.
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.
Remember that your code is read sequentially. Thus, avoid loading the reader's memory unnecessarily:
Consider the following routine, taken from class Constant in package classycle.classfile of the classycle 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;
}