My programming students are learning about methods. Functions, subroutines, what ever you want to call them they are important. Coming up with examples for demos and projects is an interesting exercise. There are actually several parts of teaching methods so starting with something that is too complex can overwhelm students. So I like to start easy.
Yesterday I started using area and circumference of a circle methods.
public double GetArea(double r)
{
return Math.PI * Math.Pow(r,2);
}
public double GetCircumference (double r)
{
return 2 * Math.PI * r;
}
Nice and simple. I can focus on the function specification – the function’s type, it’s name, and it’s short parameter list. I can also use the result very easily in the demo program.
Student want to know why they can’t just use that simple bit of code from the body of the function inline with the rest of their code though. And it is a fair question. That lets me talk about reusable code, avoiding redundancy, and all that stuff. It’s almost convincing. It’s all true of course but it hardly makes an impression. That requires examples that are a bit longer and more complicated.
Lately I have been using the Palindrome project for this next level. Now I have students create a program to determine if a string is a palindrome or not before this. They generally have the various steps (strip out not letters for example) inline in a single method. What I do here is ask them to break the various code segments out into methods. This makes the main routine simpler and easier to understand. (Assuming good names.) and this makes the point better, I think, than the simple one line methods.
It’s still hard to get students to think of this sort of modularization when they do their own designs. It gets easier when we talk about writing classes though.
I suspect that getting students to design around methods would be easier if I were teaching with functional programming language. I can actually hear several voices in my head saying “Yep” and “I told you so.”
The problem for me is that a) I’ve never used a functional programming language, and b) since I tend to think of breaking things down into methods already there is a part of me that sees it as obvious. Yep, the old “I learned it this way so my students should learn it this way” attitude. Now I have an image of Grace Hopper standing next to me with a firm look on her face.What’s my point? I guess it is that I have to constantly look at what I am doing with a critical eye to what I can do better. I’ve really liked the way I have been teaching methods but I have to ask myself if the design part is coming too slowly should I be doing something different? Darn but this thinking and caring thing is hard.
4 comments:
Caveat: I have not done a ton of Java.
The general way in FP to approach a palindrome is 1) reverse the list, and 2) compare the original with the reverse.
Here's an adaption from OCaml's 99 problems (which is the same 99 problems from Lisp, which itself was based on Prolog) assuming the string has already been converted to a list of chars. The essential lesson here, as is often the case in FP, is to use recursion, in this case within a subfunction:
let rev list =
let rec aux acc = function
| [] -> acc
| h::t -> aux (h::acc) t in
aux [] list;;
let is_palindrome list =
list = rev list;;
Again, for simplicity, I've not included a str_to_list function but - surprise - it would also use recursion.
So, my question would is there a way to do something similar in Java using methods akin to this approach?
See http://ocaml.org/learn/tutorials/99problems.html, problems 5 and 6, for more context.
Separating functions into small pieces with single lines of code is probably OK in small programs, but when you get into larger suites of code/projects, it becomes critical, even for calculations like area of a circle, which will never change.
In professional software development you'd want to provide a unit test for the GetArea() and GetCircumference() functions.
If you embed your one-line calculation into a 20-line function, you must navigate the 20 lines of your function to test your one-line calculation, which is difficult. You now have a function that does multiple things, and testing those multiple things together, including failure cases, is very complicated. If you have a one-line function, you can write a unit test(s) directly against it with ease.
It's an art, and something many professional software developers don't do well.
As an exercise, you might want to ask your students to create a program with small functions, a few lines each, and then trade them back and forth. If a function can't be traded with another student that performs a single operation, then that's an example of a function that does too much. For a professional world example, with unit tests, if we find ourselves going to great lengths to test a function that does multiple things, then that's a signal to us that the function should be broken up.
Recursion is an important concept to understand and a good teaching tool but I use it only as a last resort and I see no good reason to use recursion for either reversing a list or verifying if a string is a palindrome.
Even the classic Towers of Hanoi problem becomes simpler with iteration.
Repeat until done:
1. Move the smallest disk one tower to the right
2. Make the only other legal move possible
How simple is that?!
Regarding methods, as code becomes more complex, one popular new paradigm is test-driven development (which makes automated regression testing possible) and with TDD, I/we break methods based on that are considered to be the testable units.
In OOP, the obvious class for this example is Circle, with Area and Circumference being class-level methods (or properties), operating on the class-level radius, not a parameter.
Well, the choice of recursion is not up to you alone, right? It's a bit on how the language is implemented in the compiler and what the Assembly (really machine code) looks like. This is why it can be dangerous to use FP techniques in non FP hybrid languages that don't bias to FP -- sure it might run/compile but that doesn't mean it'll run fast.
Same opposite way. OCaml is first and foremost a FP language that also supports OO and imperative. You'll generally get better performance (better optimized machine code) by developing in the predominant paradigm (in OCaml's case FP). You could bend C to your will and do some FP but you might not like the machine code that results.
Post a Comment