The following situation has arisen several times in my teaching of CS1. In an introduction to object-oriented decomposition, I demonstrate to my students how to create a MyRectangle class. This class has a constructor that takes the x, y, width, and height of a rectangle, and it has one public draw method. We’re using Java, so the draw method accepts a Graphics parameter. The draw method is very simple: g.draw(x,y,width,height), using the attributes of the rectangle. This gets us into a nice discussion of “rectangleness”, which is represented by the class, versus individual immutable rectangles, which are represented by objects. (NB: I do not follow the naive textbook approach and have every object follow whitebox design with public accessors and mutators for all attributes. That is just training students in established bad practice. I teach them to make every object immutable whenever possible.)
From here, I have them create a MyOval class. The students check out the Graphics class in the Java API and realize that there’s a drawOval method that looks deceivingly like the drawRect method, and sure enough, the implementation of MyOval is strikingly similar to MyRectangle’s.
Then, after extended discussion about encapsulation and object-oriented systems as collections of objects that communicate by passing messages, I explain the following problem: when I define a circle, I don’t like defining it by its bounding box. Unlike a rectangle or an oval, I want to define a circle by a center point and a radius. This is “natural” to me, the user of the MyCircle class. I give them this challenge, and away they go.
First, they look for a drawCircle method in the Graphics class. There is no such thing, so I point out that circles are special cases of ovals. Then, one of two things happens: either the student jumps in and writes MyCircle’s draw method to be exactly like MyOval’s, or they make some attempt to determine how a circle would be specified by its center and radius instead of a bounding box. Note that in the former case, the circle will end up in the wrong position.
I recently gave this assignment, and for the first time, none of the students got it right. Usually, it’s a minority, but at least somebody gets it. The astonishing thing this semester is that only one or two submissions showed any evidence of analysis of the problem, doing anything beyond simply calling g.drawOval(x,y,radius,radius).
The solution to the problem is, to me, so dazzlingly obvious that it’s hard to explain. It is simple geometry: if you want to get the x,y coordinates of the bounding box of a circle, you subtract the radius from the center point’s x and y. It’s geometry, easily solved visually:
- They did not take the time to read and understand the problem before solving it. This is a common failure among CS1 students: given a problem, they start at the keyboard instead of in their brains. They start typing and typing and typing and expecting magic to happen. After all, isn’t that what the professor does in class?
No it isn’t! For all of these drawing problems, I always start with “analytical mode”, drawing the problem out on the whiteboard, asking for suggestions, talking through my thought process, and then writing the code for it.
This is, in my estimation, a failure of knowing how to think, i.e. a failure of reflective practice. This is the result of inculcation in regurgitative non-learning, in which points are given for showing up.
- They lack mathematical literacy. In this particular case, they have no grasp of geometry or the relationships between numbers. I know much less about acquisition of mathematical literacy than I do about the science of learning in general, and so the specific evidence is unclear to me. However, I have noticed that, when faced with any mathematical task — even trivial ones — many of my students will completely lock up. Of those who don’t completely freeze and wait to be handed an answer, many of these go off in crazy flights of fantasy, totally unrelated to the problem at hand.
These two factors are not independent. Although the latter may have more specific roots in the cultural establishment of math anxiety, I suggest that they may have the same root cause: students do not know how to learn. This can manifest as (1) not taking the time to read and think about a problem before answering it and (2) never needing to learn how to solve problems. This experience makes me think of Polya’s How to Solve It. I was underwhelmed reading the book because I knew most of it already, but I think this is significant: I would wager that successful students (i.e. students of life, not just people taking classes) would read that book and see it as confirmation of what they already do. On the other hand, it’s exactly the kind of thinking that many people lack, such as struggling CS1 students.
Bringing this back to the task at hand, I am forced again to look at this question: What should students learn in CS1? There are a few reasonable answers, but the two that rise to the top of the list are (1) how to think computationally and (2) how to program. The latter is dependent on the former. The former is dependent on being able to do elementary mathematics, which itself is dependent on being able to learn in the first place. If it’s true that many of my students don’t know how to learn and don’t have fundamental mathematical literacy, then it’s no wonder that CS1 courses have 50% withdraw and failure rates!
I am reminded of an uncomfortable discussion I had with a mathematics professor about programming. She was pooh-poohing the study of programming, claiming that it was easy to teach programming to her upper-level mathematics students, so therefore programming was easy. I take this as anecdotal evidence of my theories: give me a class full of students with mathematical aptitude and problem-solving skills, and I could certainly teach them the simple task of programming. They already know the hard part—thinking and problem-solving strategies—that I am challenged to teach to everyone else.