9. Errors, Organization, and Scaffolding

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

 

Where were we?

Try to compile our code and we get an error message! We declared several variables to be of class string when we should have said String, and we forgot to import FileNotFoundException. Also, we wrote the initializer Road() but not Intersection(), but we had calls to both of them.

Fixing these left us with a program that contained no compile-time errors, but there was no way to test it. Our extreme-programming framework demands that every step in our development be testable. To do this, we need some immediate testable goal for our code, something far short of an executable road-network simulation.

Something Testable

The code we have reads a file describing a road network, so our initial test can be code that reads this file and then writes it out. If the program is correct, the output should resemble the input.

Resemble, not be exactly the same as. Why? Because the tools in class Scanner squeeze out extra spaces and because, depending on how the members of class LinkedList are handled, the order of roads and intersections may not be the same.

Every Java object has a toString() method that converts that object to a textual representation as a character string. The default method creates a near-useless representation, but we can easily override the default. For example, here is an appropriate toString() method for class Road:

        public String toString() {
                return (
                        "Road " +
                        sourceName + " " +
                        dstName + " " +
                        travelTime
                );
        }

Assuming that we write similar code for class Intersection, the main program can be modified to call printNetwork() after it calls initializeNetwork(). The printNetwork() method is a static method of RoadNetwork, just like initializeNetwork(). The following code should suffice: toString() method for class Road:

        /** Print out the road network from the data structure
         */
        static void printNetwork() {
                for (Intersection i:inters) {
                        System.out.println( i.toString() );
                }
                for (Road r:roads) {
                        System.out.println( r.toString() );
                }
        }

Errors

There are some obvious ways to deal with errors! We could throw an exception, for example, and leave it to the default handler to deal with the problem. This is not a great idea unless you also include an exception handler to deal with each error.

An alternative is to call System.exit( 1 ). This terminates the program rather violently. By convention, the integer parameter to exit() should be zero if the program is terminating normally, while it should be nonzero in case of abnormal termination. The exit is violent, in the sense that all kinds of things are abandoned -- finalization on open classes, for example. The convention that the parameter should be zero for success and nonzero for failure is a standard inherited from Unix and Linux; it only really matters when programs are called from elaborate shell scripts or Perl scripts.

Of course, before calling exit(), we should output an error message! Consider the following rewrite of the start of the main method:

        public static void main(String[] args) {
                // open the input file from args[0]
                if (args.length < 1) {
                        System.err.println( "Missing filename argument" );
                        System.exit( 1 );
                }
                if (args.length > 1) {
                        System.err.println( "Unexpected extra arguments" );
                        System.exit( 1 );
                }
                ...

First note, instead of outputting error messages to System.out, the standard output stream, we have output the error messages to System.err. All processes on system descended from UNIX have two default output streams, one for normal output, and the other for error messages. Linux and Windows both support this idea. System documentation usually refers to these streams as stdout and stderr, but in Java, they are called System.out and System.err.

This approach to dealing with errors is verbose, but is has a second problem. What if we want to add a GUI interface to this application? The problem with GUIs is, we probably don't want to just output a message and then immediately kill the applicaton. If we did that, all the open windows attached to the application would close with a bang, including wherever the error message appeared.

So, what we must do is provide a centralized error handler, for example, consider:

/** Error reporting framework
 */
class Errors {
        static void fatal( String: message ) {
                System.err.println( message );
                System.exit( 1 );
        }
}

Later, if someone wants to convert our program to use a GUI, this one method would be responsible for opening a popup window, reporting the error, and then waiting for the user acknowledgement. Later, we might also add non-fatal error dialogues, possibly also managed through this class. Our main method would now begin like this:

        public static void main(String[] args) {
                // open the input file from args[0]
                try {
                        if (args.length < 1) {
                                Errors.fatal( "Missing filename argument" );
                        }
                        if (args.length > 1) {
                                Errors.fatal( "Unexpected extra arguments" );
                        }
                        sc = new Scanner( new File( args[1] ));
                } catch (FileNotFoundException e) {
                        Errors.fatal( "Can't open file '" + args[0] + "'" );
                }
                ...

We probably also want to add a way to report less serious errors. For example, we could add a warning() method to class Errors that works much the same as the fatal() method except that it does not exit.

We can call this, for example, in the readNetwork() method:

        private static void readNetwork( Scanner sc ) {
                while (sc.hasNext()) {
                        // until the input file is finished
                        String command = sc.next();
                        if ((command == "intersection")
                        ||  (command == "i")) {
                                inters.add( new Intersection( sc, inters ) );
                        } else if ((command == "road")
                        ||         (command == "r" )) {
                                roads.add( new Road( sc, inters ) );
                        } else {
				// Bug: Should probably support comments
                                Errors.fatal(
                                        "'"
                                        + command
                                        + "' not a road or intersection"
                                );
                        }
                }
        }