20. Review and Misc Topics

Part of CS:2820 Object Oriented Software Development Notes, Fall 2015
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

There are several things we could add to our simulators to make them nicer. These are not part of any assignment for this class, but they provide nice little exercises to test your understanding of the programs we've developed up to this point.

Better Error Handling

Right now, in my solution to MP4, if you make any errors in the circuit definition, there will be a number of null pointers in the data structure, and the simulation will bomb out with some null-pointer exceptions. Really, what we ought to do is prevent simulation of circuits that contain errors. How can we do this?

One option someone in class suggested was a bit of code akin to the current LogicCircuit.checkCircuit() routine or the RoadNetwork.checkNetwork() routine that would traverse the entire data structure and check for any incompletely defined components. Only if the circuit passes the test would the simulation proceed.

This proposed solution would work. I do not recommend it, but there is no question that it can be made to work. This is a programming problem, so there are always alternative workable solutions.

Why don't I recommend it? Because if there are any errors in the circuit, they should have been reported with error messages. In our logic simulator, a method in class Errors was called for every single error detected in the input file. All we need to do is have class Errors keep records of what went wrong. For example, we could write this:

class Errors {
        /** Error reporting framework
         */

        private static errorCount = 0;

        static void warn( String message ) {
                /** Report a nonfatal error with the given message
                 */
                System.err.println( message );
                errorCount = errorCount + 1;
        }

        static int count() { return errorCount; }
}

With this, the main program can check the error count after reading the circuit before simulating it. Instead of blindly calling Simulator.run(), we might do this:

if (Errors.count() == 0) Simulator.run();

Another thing to notice is that when there are multiple errors, later errors are frequently consequences of earlier errors. If you have two open windows on your screen, you might edit the simulation model in one window while trying to deal with the errors shown in the other window, but you are unlikely to try to fix more than a few errors per try, so it is not useful to have more than a few error messages output per run of the simulator.

We can easily modify the above code to do this by adding one line of code:

        static void warn( String message ) {
                /** Report a nonfatal error with the given message
                 */
                System.err.println( message );
                errorCount = errorCount + 1;
                if (errorCount > 15) System.exit( 1 );
        }

Of course, the number of error messages you allow it to output before giving up is somewhat arbitrary. The typical shell window is 24 lines long, and having so many error messages that the first ones scroll off the screen is clearly a bad idea. Numbers from 10 to 20 seem equally sensible in that context.

Default Delays

Specifying delays for each wire and gate or for each intersection and road is a nuisance. Shouldn't there be a default value? Most road segments in an urban American traffic network are one block long, 1/8 of a mile in many cities. Most intersections are about 50 feet wide. These could set the defaults in a traffic simulation. We can add this rather easily.

The current code for our simulators, in Gate.scan() and Wire.scan() in the logic simulator, and in the initializers Road() and Intersection() in the road network simulator, delays are read with code like this:

        if (!sc.hasNextFloat()) {
                Errors.fatal(
                        "Intersection '" + name
                        + "'-- travel time not specified"
                );
        }
        travelTime = sc.nextFloat();

We can easily rewrite this code to substitute a default time delay for the error message as follows:

        if (!sc.hasNextFloat()) {
                travelTime = defaultDelay;
        } else {
                travelTime = sc.nextFloat();
        }

Obviously, the default delay should depend on context. Intersections have different default delays from roads. In a more advanced simulation, there might be commands in the input file to set the default delay for each class that has an applicable delay.

Comments in Circuit Descriptions

What about comments in the circuit description? Again, this is easy. Consider using // to mean comment. There are two contexts where comments occur. At the ends of lines that contain simulation commands, and in blank lines.

Both our logic simulator and our road network simulator contained methods in the main class that did top level processing of the input file. For example, RoadNetwork.readNetwork() does this. There, we have the following code:

        while (sc.hasNext()) {
                // until the input file is finished
                String command = sc.next();
                if ("intersection".equals( command )
                ||  "i".equals( command )           ) {
                        String kind = sc.next();
                        
                        ...

                } else if ("road".equals( command )
                ||         "r".equals( command )   ) {
                        roads.add( new Road( sc ) );
                } else {
                        Errors.fatal(
                                "'"
                                + command
                                + "' not a road or intersection"
                        );
                }
        }

We can add comment processing to this code by treating the string "//" as a new command:

                } else if ("road".equals( command )
                ||         "r".equals( command )   ) {
                        roads.add( new Road( sc ) );
                } else if ("//".equals( command )) {
                        sc.nextLine(); // skip the remainder of the comment
                } else {

This version of the code has a serious problem: It requires a space between the comment marker and the rest of the comment. This is easy to repiar:

                } else if ( (command.length() > 1)
                &&          (command.charAt( 0 ) == '/')
                &&          (command.charAt( 1 ) == '/') ) {
                        sc.nextLine(); // skip the comment
                } else {

This is much harder to read. The following is easier to read but probably much slower because it involves creating a new throw-away string object:

                } else if ( (command.length() > 1)
                &&          "//".equals( command.substring( 0, 2 ) ) ) {
                        sc.nextLine(); // skip the remainder of the line
                } else {

But what about comments on the end of a line such as a road declaration or a wire declaration? Here, of course, we could add code to the appropriate scan methods for each type of object, but note that, in all of our code, we used a call to syntaxCheck.lineEnd() to force the remainder of each input line to be blank. We could easily add code here to allow comments:

class SyntaxCheck {
        /** Syntax checking support
         */

        ...

        static void lineEnd( Scanner sc, ByName c ) {
                /** Check for end of line on sc,
                 *  Use c to provide context in any error message
                 */
                String s = sc.nextLine();
                if (  !(  (s.length() > 1)
                      && (s.charAt( 0 ) == '/')
                      && (s.charAt( 1 ) == '/')
                ) &&  !s.isEmpty() ) { // not a comment and not blank
                        Errors. ...
                }
        }
}

In the above, all the alternative ways of checking for // are applicable.