[Bluej-discuss] Programming as random process [LONG]

Stephen Bloch sbloch at adelphi.edu
Thu Apr 20 16:15:07 BST 2006


Gordon Royle writes:
>One thing that increasingly frustrates me about trying to teach Java 
>is the number of students who seem to view programming as some sort 
>of random process where they type anything at all and then come and 
>complain that "it doesn't work".

Amen, brother!

>I use BlueJ precisely for the purpose of removing ALL of the things 
>like "public static void main(String[] args)" where you just have to 
>say "type this in without understanding it and we'll explain it 
>later", so in general they should be able to understand every part of
>the classes that they are assigned to write..

Amen, again.

>How can I overcome this?

If anybody finds a "magic bullet" for that, we'll all be happy. 
However, here are some thoughts.

Aryeh Friedman writes:
>is always true to some extent.  One way to conteract (and no offense ment to
>the text) is start with very simple programs like hello, world from the
>*COMMAND LINE* not a IDE then introduce the IDE for more advanced stuff.

I disagree with Aryeh, and agree with Gordon, on this: it requires 
introducing a lot of concepts as "black magic -- trust me, I'll 
explain later".  One of BlueJ's greatest strengths is that it allows 
you to postpone discussion of "public static void main (String[] 
args)" until you're ready, and it allows you to postpone discussion 
of I/O until you're ready.  You can introduce each of these concepts 
ONE AT A TIME, each with its own motivation, rather than throwing 
them all at the students on the first day of CS1.

To my mind, the other of BlueJ's greatest strengths is the automatic 
class diagrams.  Which isn't particularly useful until you've got 
several classes interacting, i.e. not the first few weeks of CS1.

One problem I've found with starting in BlueJ is that it's so easy 
for students to invoke methods and constructors with the mouse that 
they never learn to do it with Java syntax.  To avoid this, I start 
in DrJava or ProfessorJ (which, like BlueJ, allow you to invoke 
methods and see the results directly and therefore postpone 
discussing "public static void main" and I/O until you're ready), and 
switch to BlueJ later when the programs get complex enough to need 
class diagrams, and when students are fluent enough with Java syntax 
that they won't use the mouse as a crutch.

Another advantage of DrJava and ProfessorJ over BlueJ, for absolute 
beginners, is that they enforce language subsets.  If a first-month 
student misplaces some curly braces, how do you explain the resulting 
error message that talks about an "inner class"?  Better to just make 
inner classes, and various other things that the first-month student 
doesn't need, illegal and produce a level-appropriate error message.

Here's another issue.  BlueJ (and DrJava and ProfessorJ) makes it 
easy to experiment and play with objects.  This is mostly a good 
thing: I WANT students to get a hands-on feel for how objects behave 
by trying things and looking at the results.  But it also, perhaps, 
encourages the sort of "modify the program randomly and see if it 
works" philosophy Gordon complains of.  So it needs to be combined 
with a rigorous, step-by-step "design recipe" (to use the terminology 
of Felleisen et al's _How to Design Programs_): when faced with an 
assignment in English, this is what you do first, and this is what 
you do next, until after, say, 6 steps you have working, tested code. 
I tell my students that I WILL NOT HELP THEM with step N until they 
show me a satisfactory solution to step N-1.  (If I have TA's or 
tutors, I tell them to be equally strict.)  My grading rubric 
reinforces this step-by-step design recipe: you get a certain number 
of points for doing step 1 correctly, and a certain number of points 
for step 2, etc.  Even if you don't end up with working, tested code, 
you get partial credit for having (say) correctly specified the 
interface and written a good test suite.

So what is this step-by-step recipe?  Well, there are different 
recipes for constructing a method, constructing a class, constructing 
a collection of related classes, etc. but here goes

To Design a Method
1) Identify the data types it takes in and the data type (if any) it returns
2) Write a method header, specifying return type, method name, 
parameter names and types
(with a "purpose statement" if it's not obvious)
3) Write a test suite, i.e. a collection of several examples of 
invoking the method, each with a specified "correct answer"
4) Write a "method skeleton", in which you list (in comments) the 
expressions available to you and their types
5) Choose among these expressions the ones relevant to the problem at 
hand, and write code that computes the right answer from those 
expressions
6) Run your test suite, identify and fix any bugs found

For example, here's a recent assignment I gave:
1) Define a class HotelRoom with five fields: the room number, how 
many king-sized beds it has, how many twin beds it has, how much it 
costs per night (in cents), and whether it's non-smoking. Be sure to 
include a constructor that allows people to specify the values of all 
five fields. Also write at least two examples of instances of the 
class, showing how to extract the values of various fields from them.

2) Add a method named capacity to the HotelRoom class that computes 
how many people can sleep in the room. Assume that two people fit in 
a king, one in a twin, and nobody's on the floor or in the bathtub.

3) Add a method named ok which takes in a number of people and a 
boolean indicating whether they want smoking or non-smoking, and 
tells whether the specified room will suit their needs (having at 
least enough sleeping capacity and the right smoking flag).

[problems 4-14 omitted]

Let's assume the student has already done problem 1 and produced
class HotelRoom {
	int roomNumber;
	int kings;
	int twins;
	int price; // per night, in cents
	boolean nonSmoking;
	HotelRoom(int roomNumber, int kings int twins, int price, 
boolean nonSmoking) {
		this.roomNumber = roomNumber;
		this.kings = kings;
		this.twins = twins;
		this.price = price;
		this.nonSmoking = nonSmoking;
		}
	}
class HotelRoomExamples {
	HotelRoom room1 = new HotelRoom (12, 1, 0, 5995, true);
	HotelRoom room2 = new HotelRoom (15, 1, 1, 7995, false);
	HotelRoom room3 = new HotelRoom (121, 0, 4, 8500, true);
	HotelRoomExamples () {}
	boolean testBasics () {
		return (this.room1.roomNumber == 12) &&
			(this.room1.kings == 1) &&
			(this.room1.twins == 0) &&
			(this.room1.price == 5995) &&
			(this.room1.nonSmoking == true);
		}
	}
(The "testBasics() method isn't really testing anything that could 
possibly go wrong, but it gives students practice constructing 
instances of the class and extracting their fields.  I haven't 
introduced "private" yet at this point in the semester.)

Now the student starts on problem 2.  For step 1, I would expect to 
see (somewhere in the HotelRoom class)
// The "capacity" method takes in no additional information, and 
returns an int.

For step 2, the student adds the lines
	int capacity () {
		...
		}
which are essentially step 1 translated into Java syntax.

For step 3, the student adds to HotelRoomExamples the lines
	boolean testCapacity () {
		return (this.room1.capacity() == 2) &&
			(this.room2.capacity() == 3) &&
			(this.room3.capacity() == 4);
		}
	boolean testEverything () {
		return this.testBasics() &&
			this.testCapacity();
		}
(In this case, no additional example rooms were necessary, but the 
student should consciously choose test cases at this step to make 
sure all cases of the code are tested.)  The process of choosing test 
cases helps students to clarify in their minds what the method is 
really supposed to do, and particularly illuminates borderline cases.

For step 4, the student adds a "skeleton" to the "capacity" method, getting
	int capacity () {
		/*
		this			HotelRoom
		this.roomNumber		int
		this.kings			int
		this.twins			int
		this.price			int
		this.nonSmoking		boolean
		*/
		...
		}
I liken this step to getting all the ingredients out of the cupboard 
and arranging them on the counter before I start baking a cake.

For step 5, the student observes that roomNumber, price, and nonSmoking
are irrelevant to the problem at hand (but let's leave them in the comment,
so we can copy-and-paste that list into each subsequent method we 
write) and adds the line
		return 2 * this.kings + this.twins;

For step 6, the student runs new HotelRoomExamples().testEverything() 
.  If it returns true, the student pops the champagne and celebrates 
the completion of Problem 2.

Then the student goes on to Problem 3.  I'll leave the various steps 
to you, pointing out only that in step 4 the "method skeleton" has 
some additional lines:
	boolean ok (int people, boolean wantSmoking) {
		/*
		this			HotelRoom
		this.roomNumber		int
		this.kings			int
		this.twins			int
		this.price			int
		this.nonSmoking		boolean
		this.capacity()		int
		people			int
		wantSmoking		boolean
		*/
		...
		}


Naturally, as classes get bigger and more complex, the "method 
skeletons" get unwieldy, so after a certain point in the course, 
students are allowed to "pre-select" the expressions likely to be 
relevant to the problem at hand.

By the way, these "method skeletons" are especially helpful with 
recursive data structures: they give you a correct recursive function 
definition before you have time to notice that recursion is 
difficult.  Consider

interface HotelRoomList {
	int countRooms ();
	boolean anyOK (int people, boolean wantSmoking);
	int countOK (int people, boolean wantSmoking);
	HotelRoomList extractOK (int people, boolean wantSmoking);
	// etc.
	}

class EmptyRoomList implements HotelRoomList {
	int countRooms () {
		return 0;
		}
	boolean anyOK (int people, boolean wantSmoking) {
		return false;
		}
	int countOK (int people, boolean wantSmoking) {
		return 0;
		}
	HotelRoomList extractOK (int people, boolean wantSmoking) {
		return this;
		}
	}

class NonEmptyRoomList implements HotelRoomList {
	HotelRoom first;
	HotelRoomList rest;
	NonEmptyRoomList (HotelRoom first, HotelRoomList rest) {
		this.first = first;
		this.rest = rest;
		}
	int countRooms () {
		/*
		this					NonEmptyRoomList
		this.first					HotelRoom
		this.rest					HotelRoomList
		this.rest.countRooms()			int
		*/
		return 1+ this.rest.countRooms();
		}
	// ...
	HotelRoomList extractOK (int people, boolean wantSmoking) {
		/*
		this					NonEmptyRoomList
		this.first					HotelRoom
		this.rest					HotelRoomList
		this.first.ok()				boolean
		this.rest.extractOK (people, wantSmoking)	HotelRoomList
		*/
		if (this.first.ok()) {
			return new NonEmptyRoomList (this.first, 
this.rest.extractOK(people, wantSmoking));
			}
		else {
			return this.rest.extractOK(people, wantSmoking);
			}
		}
	}



Now for one more thought.  I don't start students in Java if I can 
possibly help it: I much prefer to start in Scheme, where the syntax 
and semantics are much simpler and more consistent than in Java, and 
where the widely-used and well-tested DrScheme IDE enforces language 
subsets and provides support with parenthesis-matching, indentation, 
etc.  Once students have learned in that syntactically-simpler 
setting the important concepts
* variable
* function
* parameters
* function composition
* data types
* conditionals
* types with fields, aka "definition by parts"
* types with variants, aka "definition by choices"
* self-referential data types and functions (once you already know 
how to work with definition by parts and definition by choices 
individually, just combine them in a certain way)
* higher-order functions (either taking in or returning functions as 
first-class values), including GUI callbacks
* maybe (if I have time) I/O and assignment statements

we make the transition to Java and show how all the same concepts 
arise with different syntax.

I've taught Scheme-first a number of times, both to CS majors and to 
non-majors, I've taught Java-second to students who had started in 
either Scheme or C++, and I've taught Java-first a few times.  This 
term is the first time I've taught the Scheme-to-Java transition all 
within CS1, and I think it's working quite well.

-- 
					Stephen Bloch
					sbloch at adelphi.edu


More information about the bluej-discuss mailing list