☰ Contents Recipe for Success

Breaking It Down

Student: I have to say I’m getting a little lost.
Prof: Oh?
Student: We’ve learned a lot of things very quickly – record, methods, built-in methods, writing our own methods, types, definitions, main, assertEquals. That’s a lot! It’s hard to know which parts to use when I sit down to write a program.
Prof: I see what you mean. OK let’s break down some programs from the beginning.
Prof: Here’s one: write a program that would help track subscription pricing, like to Netflix or other online services. It should help calculate how much you’d spend after a certain number of months, taking into account a free trial period given a monthly rate after the trial.
Student: See that’s where I get stuck. I know I probably need like… size and cost? And some methods. But I’m used to getting code that I fill in. I’m not sure what to type first.
Prof: Got it. So lots of problems can be broken down into specific steps. A good first step for a lot of the problems in this book, and for object-oriented programs in general, is to start with a record or class definition, in terms of what fields we need for the data in the problem. Here we’ve got at least the number of trial months, the monthly price, and the name of the service.
Student: OK, so we’re supposed to turn that into a record?
Prof: Right. What name would you use, and what names and types for the fields?
Student: I guess we can call it Subscription, and then have the name and then the monthly cost… you were really adamant that prices should be int, not double, when we talked about that before. And I guess the free trial months is a count?
Prof: Sounds good. Try writing it all down.
Student: So next do I write the methods?
Prof: I have some questions first! One way to make sure we understand our definition is to write a few examples. Let’s do that
Student: Great, easy, I just use new:
Prof: Great. So what does the first one mean in English?
Student: A Netflix subscription that costs 19 dollars a month, with 3 trial months
Prof: I see. Can you represent a subscription that costs $9.99?
Student: I guess not, not without changing it to a double.
Prof: So one idea is to represent it not as dollars but as cents. So a $10 subscription would have 1000 as its price. That keeps it as int.
Student: Oh, that’s a nice trick.
Prof: That’s a trick I know from experience. There are lots of those to learn along the way while you build up programming experience. So we should make sure that the program has these modeling decisions clearly written down. That way anyone (human or AI agent) that reads the code will know what we intended. One way is to choose really descriptive field names; can you think of any here?
Student: Well priceCents would make the price clear.
Prof: And for the trial?
Student: Oh I guess trialMonths would also be pretty good.
Prof: Great. Let’s go forward with that.
Prof: Great. Now let’s write a method that tells us how much we’ll have spent after a given number of months, taking the free trial into account.
Student: OK, so what do I type first?
Prof: So I usually start with either the method header or examples of calling the method. They kind of go together. What should the method be called and how many arguments should it get? What should it return?
Student: Well it takes a number of months… so an int. And it returns… I guess a number of cents?
Prof: Great, how would you write that as a method header?
Student: int costAfter(months)?
Prof: Almost – the parameter needs a type, too.
Student: Ah so int costAfter(int months)
Prof: Looks good. And what does it look like to call it with some of our example data?
Student: Maybe like int price = costAfter(8);
Prof: Close again. That method call needs a receiver. Do you remember what that is?
Student: The object that goes before the method name? So I could use like netflix or disney?
Prof: That’s right. Now try putting it all together.
Student: But that has an error!
Prof: Right. We can’t write a method without a method body. But this is a great start! One thing I sometimes do is write a wrong value with the right type down as the return so I can see the program run. Like a 0 would work here.
Student: Like this?
Prof: Exactly. So the result is 0, but at least it runs! This is called using a placeholder value (some people call it a “dummy” value). I even do this when I’m programming with AI systems – I will tell them “put a placeholder in for now so we can get it running; then we’ll update and write more detailed tests” or similar.
Student: I see. OK so now we fill in the body. This is just the number of months minus the free trial months, times the monthly rate. Straightforward.
Prof: Go ahead!
Prof: There you go! It runs. Think it’s all good?
Student: Yup, I’m satisfied.
Prof: Let me nudge you a little bit… what happens if I want to know how much that Netflix subscription will cost after 2 months? Can you turn that into a runnable example?
Student: Sure!
Student: Wait, it’s negative. Ohhhh I see. Because months is less than trialMonths.
Prof: Right, can you think of a way to fix it? The examples are fine, your method definition just has a bug!
Student: Oh so if the number of months would be negative, the result is just 0.
Prof: Another way to say it is that the number of months you pay for is the max of either 0 or the difference (it should never go negative).
Student: Right. I see how to fix it now, there are a few ways.
Prof: Great! So one last step could be turning the examples at the end into assertEquals tests in a main method. We can leave that off for now, in future examples we will see how to do it. With just a handful we can read the outputs ourselves.
Student: I see a little better now. What were all the steps?

Prof: There were a few:

  1. Define the data and write examples to make the representation clear
  2. Identify the method you want to write
  3. Write the method header and examples together to figure out what it’s supposed to do; placeholder values to make it run
  4. Implement the method body and check it against the examples.

The book How to Design Programs calls this kind of step-by-step process the Design Recipe. It’s just one way to break down programs, but it works well for a lot of cases you’ll encounter.

Practice

Pick the body that fixes costAfter, so that months inside the free trial cost nothing.
record Subscription(String name, int priceCents, int trialMonths) {
int costAfter(int months) { return ; }
}
Subscription netflix = new Subscription("Netflix", 1900, 3);
Subscription disney = new Subscription("Disney+", 2100, 1);
int price = netflix.costAfter(8);
int priceAfter2 = netflix.costAfter(2);