36. A Lost Opportunity for Information Hiding

by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

An Aside

When developing something like the event cancellation and rescheduling capacity, it is always useful to have some small examples on hand for testing it. Here is an example:

public class Test {

    private static void tick( double time ) {
	Simulator.schedule( time + 1.0, ( double t )-> tick( t ) );
	System.out.println( "tick " + time );
    }

    public static void main( String[] args ) {
	Simulator.schedule( 10.0, ( double t )-> System.exit( 0 ) );
	Simulator.schedule( 1.0, ( double t )-> tick( t ) );
	Simulator.run();
    }
}

This little example schedules time to end at time 10.0 and then starts up a little process to output a series of ticks, starting at time 1.0 an repeating every time unit. Nothing exciting except as a demo for the basic event scheduling mechanism.

Given an implementation of class Simulator that supports cancelling and rescheduling, we can minimally test and demonstrate it by changing the main program above as follows:

    public static void main( String[] args ) {
	Simulator.Event e
	    = Simulator.schedule( 10.0, ( double t )-> System.exit( 0 ) );
	Simulator.schedule( 1.0, ( double t )-> tick( t ) );
	Simulator.reschedule( e, 5.0 );
	Simulator.run();
    }

All we did was capture the "end of time" event in the variable e so we could reschedule it at a different time. The versions of reschedule discussed in the last three lectures all work for this, moving the end of time from 10.0 to 5.0..

Now consider trying to abuse the pending event set by reaching into an already scheduled event and tinkering with its fields:

    public static void main( String[] args ) {
	Simulator.Event e
	    = Simulator.schedule( 10.0, ( double t )-> System.exit( 0 ) );
	Simulator.schedule( 1.0, ( double t )-> tick( t ) );
	e.time = 15.0;
	e.act = ( double t )-> System.out.println( "oops" );
	Simulator.reschedule( e, 5.0 );
	Simulator.run();
    }

If we use the first version of event cancellation and rescheduling we discussed, the attempt to change e.time is legal, while the attempt to change e.act is illegal because that field is final. Running the same code under the later versions, where a wrapper or use of the class hierarchy between inner classes is used to hide things, and neither of our added lines will compile.

Under the first version of the code, we can even do this:

    public static void main( String[] args ) {
	Simulator.schedule( 10.0, ( double t )-> System.exit( 0 ) );
	Simulator.schedule( 1.0, ( double t )-> tick( t ) );
	Simulator.event e = new Simulator.event(
	    5.0,
	    ( double t )-> System.out.println( "oops" )
	);
	Simulator.reschedule( e, 5.0 );
	Simulator.run();
    }

This code compiles, and it even runs under our original implementation of reschedule and cancel. We can freely create events outside the simulation framework, and we can try to reschedule them. Doing so causes no harm because the only things we can do with our illicit events is try to reschedule or cancel them. Our original versions of cancel() and reschedule() do nothing if the event in question has not been scheduled yet, so this code is merely wasteful and does no harm.

We were lucky, the code for reschedule() we wrote included a check to prevent rescheduling an event that had already occurred, and this check had the side effect of preventing rescheduling an event that had not already been scheduled.

The code above to try to create an illicit event and reschedule it does not even compile under either of the later versions of class Simulation that effectively hid class RealEvent from the user.

A Failed Information Hiding Strategy

Java offers an attractive possibility. Fields, constructors and methods of a class may be marked as:

The defuault, package access, has an obvious meaning when used at the global level in a file, but what about inner classes?

The designers of Java had the option of defining package-level access for inner classes to mean "accessible in the enclosing class, but not accessible outside that class." Had the designers of Java done this, the following declarations for use in class Simulator would solve our information hiding problem at no run-time cost:

public static class Event {
    double time;
    final Action act;
    Event( double t, Action a ) {
	time = t;
	act = a;
    }
}

If the designers of Java had taken the path suggested, the Event constructor and the two fields of the Event would be visible only within the enclosing class, Simulator and invisible to the outside world. The documentation for Java is sufficiently opaque that it was not obvious how this would work, but a few experiments with variations on the test main program given above make it clear that this does not work.

The designers of Java have opted to implement the access restrictions on fields of inner classes so that, if the inner class is public, those restrictions operate as if the inner class was declared at the outer level. Too bad.

Kluges

What we've done to enforce access restrictions by clever use of wrappers or inner classes can be described as kluges, forced on us by an inadequate language design.

Similarly, our use of InfectMeTime in the epidemic simulator as a way to effectively cancel events was a kluge forced on us by an inadequate initial version of class Simulation.

The word kluge is old computer-science and engineering jargon. It probably dates back to the U.S. Army Signal Corps during World War II, but it first saw widespread publicity in a tongue-in-cheek article published in Datamation, a slick magazine targeted at programmers, back in 1961. The article was entitled How to Design a Kluge and it was written as if creating kluges was the goal of every programmer and engineer. It's still a great article, worth looking up.

The word kluge has two widespread pronunciations; the one that rhymes with luge the french word for a small sled and the accepted name of an Olympic sport, is probably the older name, and more likely considered correct. Pronuncing it to rhyme with fudge is likely the result of learning it from reading the word instead of hearing it in conversation, and the spelling kludge seems to follow from this pronunciation.

The best speculation about the origin of the word traces it to one of the many German scientists who fled Germany prior to World War II and took faculty jobs in US Universities. Imagine being such a scientist, grading assignments in a math or engineering course, and you see something that a student did that is clever to the point of being impossible to figure out. As a brilliant scientist with a red pencil in hand, you mark it as "clever" but as a native speaker of German, you write it in German as klüge.

Your students, not being German speakers, ignore the umlaut and mispronounce the word. They also don't know that it means clever, and take red-inked klüge on part of their work as a criticism, a suggestion that what they've done may be clever, but there must be a better way to do it.

The original How to Design a Kluge article in 1961 said that kluges may have had their origin in "hardwaresville" but that they reach their true pinnacle in "softwaresville." That is, even in 1961, it was clear that programmers were out-kluging the engineers. That certainly remains true today.

To Infinity and Beyond?

In Java, if you write 1/0, your code will throw an arithmetic exception. If you write 1.0/0.0, however, nothing at all will happen. That is because Java's floating point representation, used for both float and double, has representations for both positive and negative infinity. Positive infinity is greater than all non infinite values, and those, in turn, are all greater than negative infinity.

Add infinity to any non-infinite value, and you get infinity -- the sign of the infinity dominates the sign of any non-infinite value.

Multiply by infinity, and the value will be infinite, with the sign determined by the usual rules.

Divide a finite value by infinity, and you get zero.

There are two special cases: Plus infinity added to minus infinity gives a value that is not a number, abbreviated NaN. Similarly, infinity divided by infinity gives NaN. The value NaN is a very weird thing, because the expression NaN==NaN is false. This is the only case in Java where comparing two values that are certifiably the same does not give true.

Java provides special methods in classes Float and Double to check for infinity (isInfinite()) and for not-a-number (isNaN()).