Michael Bolin's 6.170 Recitation

www.bolinfest.com 
This web site is for students in the recitation that I am teaching for 6.170 Fall 2004. The recitation meets Thursday afternoons from 2-3pm in 34-302 and my office hours are after recitation from 3-5pm in 32-044A.
Recitation Notes
9/16 Recitation 2
9/23 Recitation 3
10/7 Recitation 5
10/14 Recitation 6
10/28 Recitation 8
Recommended Readings
Athena Pocket Reference
The Java IAQ
Programming by Coincidence
Writing a Code Generator
in Java
The Story of Mel
A Comparative Overview of C#
Beating the Averages
J2SE 5.0 New Features

9/23 Recitation 3 - Some Rules and Some Exceptions

  1. Style Points
  2. Think Twice Before Using the @requires Tag
  3. Demonstrating Failure
  4. Do Not Leave catch Blocks Empty

1. Style Points

In the old days, terminals were only 80 characters wide, so programmers would limit lines of source code to 80 characters in length so that they would fit on the screen. Although we do not use such restrictive terminals today, the 80 character rule is still honored so that programmers do not have to use the horizontal scroll bar to read their code. (This also makes it easier for your TA to read your code when he prints it out – this is true for .txt files, too!)

In Eclipse, you can use Ctrl+Shift+F to Format all of your source code so that it obeys the 80 character rule (although it will not reformat your Javadoc comments if they are too long). If you look under the Java preferences, then you will discover that Eclipse provides you with more formatting options than you could ever want. You will probably be interested in setting the option that tells Eclipse whether to put your braces like this:

public void iLikeMyBracesThisWay() {

}
or like this:
public void isntYourJavaFileLongEnoughAlready()
{

}
The other options, such as how many lines to skip between methods, will probably be less interesting to you, but it's comforting to know that they're there if you want them.

2. Think Twice Before Using the @requires Tag

In last week's recitation notes, I made a point about strong versus weak specifications. To review, which clauses should be included in the Javadoc of a sqrt(x) method?

A @requires x >= 0
B @throws IllegalArgumentException when x < 0
C Both A and B

The correct answer is B because it yields the strongest specification.
A is incorrect because it produces a weak specification for sqrt(x).
C is incorrect because A is not really true when B is true. Granted, you are expected to only use sqrt(x) when x >= 0, but you should not say that you can't if you can tell the user what will happen if he does.

To find out what the expected input domain is, then look through the @throws tags to see which inputs throw exceptions. If there are many throws tags, and the set of expected inputs is convoluted, then the expected input domain should be explained in the class overview; however, it should NOT be expressed in the @requires clause, as that would weaken your spec.

Reserve the use of the @requires clause to methods where conditions are prohibitively expensive to check and therefore serve as a true requirement. For example, consider the following:

/**
 * @requires a is sorted
 * @return int i where either i < 0 or a[i].equals(element)
 * @throws IllegalArgumentException if a is null
 * @throws ClassCastException if the array contains elements that are not mutually comparable
 */
public int binarySearch(Object[] a, Object element);
The whole point of a binary search is that it takes advantage of a sorted array to produce a lookup algorithm that runs in O(log n) time. By comparison, checking if an array is sorted takes O(n) time, so doing this check would eliminate the advantage of using binary search!

Further, because the behavior of the method is truly unknown when the input violates this requirement, it gets listed in the @requires clause.

For example, if you have:

int[] a = new int[] {95, 48, 7};
int result = Arrays.binarySearch(a, 48); // result is 1
then result will be the right thing. On the other hand, if you have:
int[] a = new int[] {95, 48, 7};
int result = Arrays.binarySearch(a, 7); // result is -1
then you will get the wrong thing.

Thus, the need for the array to be sorted ought to appear in the @requires clause: as the resultant behavior is unknown when the array is not sorted, it cannot be explained in the @return, @effects, or @throws clause, so it must appear in @requires. Note that this is rarely the case, so think hard before deciding to add an @requires clause to a public specification.

3. Demonstrating Failure

As stated in lecture, there are three ways to indicate errant behavior in a method:

  • return a special value
  • throw an exception
  • invoke System.exit(-1)

Returning a Special Value

Returning a special value is often done because it is fast and convenient. It is fast because it does not require the overhead associated with creating a new Exception object and then throwing and catching it. Further, it is convenient because it does not force the client to create a try/catch block. This is acceptable practice when there is an acceptable special value available to return (such as null or -1), and that the error is not so drastic that you need to call the programmer's attention to it (such as when List.get(Object o) fails to find o).

Throwing an Exception

Sometimes throwing an exception is necessary to indicate a failure because there may be no special value available to return. For example, consider the following method:
public int max(a, b); //returns the max value of a or b
Though it is hard to imagine how this method could fail, it could not return a special int even if it did because every int is a potentially valid return value for max().

However, the lack of a special value is rarely the main motivation for deciding to throw an exception. Often, it is used to call the programmer's attention to an unexpected failure and to give him some information to help him recover from it. An Exception is an object just like any other, so it can have its own fields that can be used to store information about the failure that the programmer can use to help him figure out how to proceed.

For a deeper discussion of deciding when to throw exceptions, see Effective Java as well as the lecture and recitation notes.

Invoking System.exit(-1)

This is just about the worst thing you can do and is probably completely unnecessary – you had better be damn sure that throwing an exception would not be acceptable instead. But if you do decide to do something like this, then be aware that the integer that you pass to the exit method should be some nonzero number since exiting with zero corresponds to a clean termination. Often, the integer passed to exit is supposed to correspond to some sort of error code, though as you know from 6.033, numerical error codes are rarely helpful.

Here is a real example of where using System.exit() had disastrous results: when I worked at Lockheed Martin in the summer of 2002 (right after I finished 6.170), I was supposed to develop a user interface to wrap around this simulation that they had written. They designed it so whenever their simulation threw an exception, they decided to invoke System.exit(). Thus, you could find the following all over their code:

try {
  // thing that may throw an exception
} catch (Exception e) {
  System.exit(-1);
}
When I asked them why, they argued that, "Well, since we can't recover from it, we figured that it should just crash."

Brilliant. Just brilliant.

Thus, when my GUI called their simulation code, and the simulation threw an exception, System.exit() was invoked, bringing down the simulation as well as my GUI. Had they decided to let the exception be thrown, then I could have caught the exception and displayed an alert box that said, "There was an error during the simulation," or something to that effect. (Had they thrown meaningful exceptions, then I could have given the user a better message.) In any event, this would at least have saved the user the trouble of restarting my GUI every time there was a problem with the simulation.

4. Do Not Leave catch Blocks Empty

When you catch an exception, do something with it! Specifically, do NOT write the following:
try {
  doDangerousThingThatMightFail();
} catch (Exception e) {
  // meh.
}
At a minimum, you should at least call e.printStackTrace() in the catch block so that you make yourself aware of the problem (and can hopefully fix it later), or leave yourself a TODO in Eclipse so that you remember to come back to it later and put something useful there. No catch block left behind!


©2004 Michael Bolinwww.bolinfest.com