03: Developing with Eclipse

Welcome

Welcome to people of all ages, all colors, all cultures, abilities, sexual orientations and gender identities.

Welcome to those who identify as biracial, multi-racial, or multi-ethnic.

Welcome especially to those of you who added the course recently, and to those of you coming from the 5-colleges!

Announcements

Are you a new add to the course? You need to get in touch with me immediately.

More on for loops

Remember we were talking about terrible for loops?

In-class exercise

int sum(int[] array) {
  int total = 0;
  for (int i = 0; i <= array.length; i++) {
    total = total + array[i];
  }
  return total;
}

What will this method return?

Back to for loops

But there are at least two major times when this style comes in handy.

The first is when you need access to the index, not just the element.

And the second (which is related) is when you need to compare different elements within the same array. For example, let’s say you wanted to write a method to check if an array of ints was sorted. You’d need to make sure each element was less than or equal to the element after it (and make sure you didn’t check anything past the end of the array!):

boolean isSorted(int[] array) {
  for (int i = 0; i < array.length - 1; i++) {
    if (array[i] > array[i + 1]) {
      return false;
    }
  }
  return true;
}

In either style of for loop, you can, just as in while loops, modify the flow of control with break, continue (which performs the update and checks for termination), and return.

There are a few common patterns of for loops you should learn about and master, as they’ll make writing for loops easier for you. We’ve seen some of them already, but let’s talk about patterns. Here they are:

Doing something to every element of an iterable: Suppose we have an array of Things, and we want to doSomething to each one. The enhanced for loop lets us do so in a straightforward manner:

void doAll(Thing[] array) {
  for (Thing t: array) {
    doSomething(t);
  }
}

Testing that everything in an iterable satisfies a property:: Let’s say we want to check if every element in an array is even. The general idea here is to check each element one-by-one. As soon as one thing violates the property (that is, is odd), we can return false. If we get to the end, we can return true:

boolean allEven(int[] array) {
  for (int e : array) {
    if (e % 2 == 1) {
      return false;
    }
  }
  return true;
}

Another example is a linear search, where we scan every element of an iterable and do a thing if an element is found. We do a different thing if it’s not found:

boolean containsValue(int[] array, int value) {
  for (eint e : array) {
    if (e == value) {
      return true;
    }
  }
  return false;
}

The pattern is the same; the test and the return values differ.

Or if we wanted to see if a String contained a particular letter we can iterate over its contents. Probably the easiest way to do this is using the charAt member method of String, as Strings don’t directly support enhanced for loops:

boolean containsChar(String s, char c) {
  for (int i = 0; i < s.length(); i++) {
    if (s.charAt(i) == c) {
      return true;
    }
  }
  return false;
}

Again, the pattern is the same; the test and the return values differ.

Comparing elements in an array for some property: We saw this already in the isSorted method. Here, we cannot easily use the enhanced for loop, since we need access to the index (to check the current against the next value):

boolean isSorted(int[] array) {
  for (int i = 0; i < array.length - 1; i++) {
    if (array[i] > array[i + 1]) {
      return false;
    }
  }
  return true;
}

The pattern is similar to before, but we need to sure we access the right elements of the array, and we need to be careful with bounds. You could rewrite it as:

boolean isSorted(int[] array) {
  for (int i = 1; i < array.length; i++) {
    if (array[i - 1] > array[i]) {
      return false;
    }
  }
  return true;
}

but I find the former more natural.

Exceptions and other abnormal exits

We’ll cover these more later – for the first part of this course, I won’t be asking you to write code in this class that needs to throw or handle exceptions. We’ll return to them in more detail when the need arises.

Developing with Eclipse

So, you’ve used Eclipse in a very hand-holding way to complete HamSpam, but it hasn’t really required you to understand the use of unit tests, or how you can use the Eclipse debugger to help find errors in your code.

Today, we’re going to do a little practice run through Eclipse, demonstrating some of its features that you’ll find useful for the rest of the semester.

In particular, we’ll show you how you might write and use your own “unit tests” to check your progress toward completing an assignment. We provide tests for the projects early on, but you’ll need to start writing your own at some point this semester, and sooner is probably better than later.

(Next class) we’ll show you how you might use the debugger to figure out what’s going on when things aren’t working the way you’d expect. Sometimes “caveman debugging” (liberal use of “println”) is good enough, but sometimes it’s helpful to examine a program’s state while it’s running to figure out what’s going on.

We’ll use everyone’s favorite game, tic-tac-toe (also known as “naughts and crosses”) as our running example.

(demo the game on the board)

Creating a project

For this class we typically give you a project template to work with. One way to create such a template is to just use Eclipse to make a new project. Eclipse can be used for development in several languages, so make sure you start a new Java project. For our purposes, most of the defaults are fine.

On the second page there are various options you can configure. One is the “source” folders. These are the places in the project that are checked for source files – sometimes there are referred to as elements of the CLASSPATH, which we’ll talk about more later. Similarly, the “libraries” are bundles (usually in the form of “.jar” files) of Java code written by others that this project will leverage. This week’s programming assignment uses the “Processing” jar to draw the bus simulator graphics and handle mouse and keyboard input. We won’t use any libraries for our example, instead using simple text-based input and output.

OK, so now we have an empty project.

In-class exercise

A new project! I should…

Blank slate

Now, what should we do? Think about about the data our program is going to represent, and the actions we’re going to perform upon that data.

What kinds of data is this program going to need to keep track of? (ask class, on board) Try to give me answers that are nouns.

  • a board / spaces
  • which spaces have been claimed by which player
  • winner/loser/draw (or game still in progress?)

And what kinds of actions are we going to be performing? This time, verbs.

  • creating a new board
  • claiming spaces
  • checking winner
  • checking end-of-game
  • printing the board (actually, converting it to a string)

If we squint at this for a bit, we get a sense of what types of data we’re going to have and the methods we might use to. We need something to represent the state of a space, and an array of these somethings to represent the board. And…that’s about it, actually – all of the other things can be computed by looking at the board – we don’t need to keep track of them, though we might choose to do so to keep the code simple or clear.

Keeping track can be detrimental: we might make a mistake (and desynchronize between our board and, say, an xHasWon instance variable) which would lead to hard-to-track down bugs.

Whether to keep track is a judgment call. If computing one of these properties was actually hard – that is, used a lot of time or space, or the code to do so was complicated and error prone, we might just track the thing in a variable. But if the code to update that variable was complicated and needed to be considered in many methods, we might compute the value, instead.

You’ll develop a sense for this as you gain in programming experience.

Getting started

Let’s start by creating an empty board class, and put our state (instance variables) and behavior (methods) there. We’ll also create a driver class for interactive testing. Notice I can ask Eclipse to write the main method for me.

package tictactoe;

public class TicTacToeBoard {

}
package tictactoe;

public class TicTacToeDriver {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }

}

Also notice that both classes are by default in a package, a namespace that Java allocates to each class. Eclipse creates a package by default, and the classes are in a correspondingly-name folder. We’ll talk about this more in a later class.

Spaces and the board

How should we represent spaces? Two-state variables are naturally represented as booleans, but spaces can be in one of three states: empty, ‘X’ or ‘O’. How can we deal with this?

One option is to use Strings (“empty” “x” “o”); another might be to use integers (0 = empty, 1 = x, 2 = 0), where we restrict our attention to the values of interest. These aren’t bad choices per se, though they do come with some risks.

    private String[] board;

    // or we might use
    // private int[] board;

In-class exercise

Suppose we define our board as an array of strings, where each entry is X, O or (empty).

    board[0] = "X";
    // ... later ...
    board[1] = "0";

Is this code correct? And, will the compiler catch the error?

For strings, if we make a typo in the string in our code somewhere (“X” vs “x”) then comparisons will fail and some conditionals might not execute.

For ints, they aren’t very readable, and again, we might slip with our fingers and accidentally assign the value 3 to a board space.

One way to mitigate these issues somewhat by using “named constants”. We have to use two keywords to signal some things to the compiler:

  • final: This variable’s value is not variable at all, but instead constant. It’s a compile-time error to attempt to change its value.
  • static: There is only one copy of this variable, shared between all instances of the class. We don’t need a separate copy per-instance. (Strictly speaking, this is optional, but since the values of the spaces won’t vary between games, we might as well make that clear.)
    public static final String X = "X";
    public static final String O = "O";
    public static final String EMPTY = "empty";

“Marc,” you may be thinking, “this seems like a lot of extra work.” Well, “a lot” is relative. But the goal here is to let the computer (in this case, the compiler) help you. If you only use these constant names in your code; it prevents you from making typos and is a good idea if you are working with constants. It also tells other people what the otherwise “magic numbers” or “magic strings” in your code mean. >50% of programming jobs in the real world don’t let you write everything from scratch – you’re often reading other people’s code, too, so it pays to make your code legible.

We might also consider using an “enumeration type.” Enumeration types are kinda like very short classes, which can only hold a small set of values, that have specific support in the Java language. Enums are reference types, and like all reference types, their default value is null – watch out for that!

An enum provides additional benefits over constants. Namely, it defines its own type so you can’t accidentally compare it with another String. (Maybe someday take a deeper dive into a programming language that supports algebraic data types and pattern matching to see how you can really leverage the compiler to help you avoid mistakes.)

    private enum Space {
        X, O, EMPTY;
    }

Back to the job at hand! Now that we’ve defined the types of values that can be stored in spaces, we need a place to put them. We’ll create a Space array:

    private Space[] board;

Now, let’s see if we can see the board. Switch over to our driver and add this to the main() method:

    public static void main(String[] args) {
        TicTacToeBoard board = new TicTacToeBoard();

        System.out.println(board);
    }

And… tictactoe.TicTacToeBoard@677327b6! What’s this?

We’re attempting to print board, which is an instance of TicTacToeBoard. What does println do when we pass it an object? It attempts to coerce it to a string, using the object’s builtin toString method. But we didn’t define one. Where is this string coming from?

Java classes use inheritance, and if a method isn’t defined in the current class, the Java language says we should look up the inheritance hierarchy to find its definition. What’s the superclass of TicTacToeBoard? We didn’t define one (using extends), but there is a default for all objects: Object. What does it have to say about toString?

http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#toString--

So if we don’t define a toString, we get the typename and the hash code. (And lookie there, the default value of the hashcode is the object’s address! So they are, kinda, sorta, sometimes, available.)

Anyway, one of the important rules for development and debugging is that you can’t fix what you can’t see. So let’s write our own toString method now. It should convert the current instance of the board into a string:

In-class exercise

    public String toString() {
        String string = "";
        for (Space s : board) {
            string = string + s;
        }
        return string;
    }

What’s the output?

And… Boom! Null pointer exception! We didn’t initialize the board! Let’s make a constructor and do that.

    public TicTacToeBoard() {
        board = new Space[9];
    }

And… not quite Boom! But still lotsa null. Eff null, seriously, what were language designers thinking? The guy who invented null has since claimed it was the billion dollar mistake, and that’s before adjusting for inflation.

How do we fix it? By fully initializing the board:

    public TicTacToeBoard() {
        board = new Space[9];
        for (int i = 0; i < board.length; i++) {
            board[i] = Space.EMPTY;
        }
    }

Unfortunately we still get something ugly: EMPTYEMPTYEMPTYEMPTYEMPTYEMPTYEMPTYEMPTYEMPTY.

Let’s fix this in two parts. First, let’s teach the enumeration toString() itself:

        public String toString() {
            if (this == X) {
                return "X";
            } else if (this == O) {
                return "O";
            } else if (this == EMPTY) {
                return ".";
            }
            return "WTFBBQ";
        }

Annoyingly we can’t prove to Java that we’ve covered all the cases here (again, languages with better type systems can handle this) so we need a default, obviously wrong value in case of programmer error. Choose your own if you like.

Then, while we don’t need to, it might be nice to structure the board’s toString output to be a little more human-readable. We want spaces between each item, and a newline at the end. And we want to trim the last newline. OK, here we go:

    public String toString() {
        String string = "";
        for (int i = 0; i < board.length; i++) {
            string = string + board[i];
            if (i % 3 == 2) {
                string = string + "\n";
            }
            else {
                string = string + " ";
            }
        }
        return string.substring(0, string.length() - 1);
    }

Well, not beautiful but it gets the job done. It might be marginally more efficient to use a StringBuilder, but enough premature optimization for now.

Tests

So we’ve been manually running our driver after each change to see if the output matches what we want. You know what else is really good at running things repeatedly and checking for matches against expected results? A computer.

It turns out this is the basis of automated testing, and there’s a system we can use in Java called JUnit to do this for us. We can still use our driver, of course, but we can also run automated tests to make sure that output is what we expect it to be.

I’ll use Eclipse to add a new JUnit test class, though note (again) in most class projects it’s already set up for you, and you can just edit the classes yourself if you like.

The default test fails (by design). Let’s remove it, and add a test to capture the expected behavior of toString on an empty board:

    @Test
    public void testEmptyBoard() {
        assertEquals(". . .\n. . .\n. . .", new TicTacToeBoard().toString());
    }

First, note that the method is prefixed with an @Test decorator. This tells Eclipse to run this method as a test – not all methods in a XXXTest class will be run as tests, only @Test methods.

Next, note it returns void. We don’t care about it’s return value; instead, we use an assertion inside it.

Speaking of which, assertEquals checks that the first argument (the expected value, which we hardcode to the “right” answer) and the second argument (the actual value, which should be computed by the code being tested) are equal; if so, the test passes, if not, it fails. The test can also have an error, if it (for example) throws an unexpected exception.

This is a pretty common pattern, actually, where you write and test code in a driver class, then encode your test as a test case. This way, if you break something by accident later, you’ll discover it immediately before you move on.

Good tests are generally small – testing just one thing.

They usually use assertEquals(), where the first argument is what’s expected and the second is what’s being tested. (They might use assertTrue or assertFalse when checking boolean results, and there are a few others, but we’ll come back to this later). It’s important to write tests this way, so that tools (like, for example Eclipse) show the right things to you in their UI.

More next class! Then a little bit more review, then onto new material: the List Abstract Data Type!