Concepts / Designing a Program with Subroutines

Designing a Program with Subroutines

Stepwise refinement develops a program's algorithm gradually, turning a rough list of steps into smaller steps that are precise enough to implement.

  • Programming

A Rough Plan for a Mosaic Program

Suppose you have been asked to write a program that opens a window, fills it with colors, and then keeps changing squares in that window. You already know roughly what the program should do, but you do not yet know how each piece will be coded. A natural first move is to write the plan down as a short list of big steps. The author of the source example did exactly this: open a Mosaic window, fill the window with random colors, and then move around changing squares at random.

That list is a legitimate algorithm, but it is far too vague to turn directly into code. Phrases like move around changing squares at random hide many decisions: where to start, how to choose the next square, and when to stop. This article follows the process of making such a plan precise enough to implement, and then looks at how the program keeps track of where it is while it runs.

Refining Steps Level by Level

Definition

Stepwise refinement develops a program's algorithm gradually, turning a rough list of steps into smaller steps that are precise enough to implement.

Instead of coding the vague version, you keep examining each step and asking whether it can be described more precisely. Each pass produces a slightly more detailed algorithm. You do not have to decide everything at once. This repeated, gradual elaboration is the heart of stepwise refinement, and it is what makes a large task manageable: a big task that is hard to hold in your head becomes a set of smaller tasks you can work on one at a time.

The mosaic author refined the vague third step by expanding it. The program starts in the middle of the window and then repeatedly moves to new squares and changes their colors, for as long as the window remains open. The expansion adds two things the vague version lacked: a starting condition and a stopping condition. A refined loop usually needs explicit starting and stopping conditions like these before it can be coded.

Mosaic programOpen windowMosaic.open()Start in middle ofwindowrow 8, column 10Fill window withrandom colorssubroutineRepeat while windowopenloopChange squares atrandomstill too vaguestep 1step 2step 3refined intorefined into
How does one rough step expand into smaller, more precise steps across successive levels of refinement?

Read the diagram from the top down. The top node is the whole program, and each level below it is a more detailed description of the level above. The starting position and the loop are refinements of the vague third step. In the source they are not separate subroutines; they are steps that the refinement makes precise.

Choosing Which Steps Become Subroutines

Not every step in a refined outline should be pulled out into its own piece. The useful question is whether a step describes a coherent task that can be handled on its own. In the mosaic example, filling the window with random colors is a clear, self-contained job. It does not depend on how the program will later move around the window, so it can be worked on separately. The author therefore writes a separate subroutine for it.

A subroutine is a separate block of code that carries out a specific task. Because it is a unit of its own, the main routine can call it by name at the point where that task is needed. The source gives the reason the mosaic author pulls tasks out: using subroutines makes the main routine simpler and easier to understand.

The source already has one subroutine available, Mosaic.open(), which opens the window. The author then decides to write two more subroutines to carry out the two tasks inside the while loop. The refined algorithm reads as a short sequence of calls, and each detail lives inside the subroutine responsible for it.

Tracking Position with currentRow and currentColumn

A loop that moves around a window needs a record of its location. The author represents the current position with two int variables. The variable currentRow holds the row number of the square where the disturbance is currently located, and currentColumn holds the column number. A variable holding information like this, which describes what the program is doing at a given moment, is one way of representing program state.

The mosaic has 16 rows and 20 columns of squares. Placing the current position in the center therefore means setting currentRow to 8 and currentColumn to 10. These two values are the state the loop depends on. Each time the loop moves the current position, these values are the things that must change.

Worked example

Setting the starting position

The mosaic has 16 rows and 20 columns. Set the current position to the middle square and record the state.

  1. 1
    Find the middle row

    The source places the current position in the center and sets currentRow to 8 for a 16-row mosaic.

  2. 2
    Find the middle column

    For the 20 columns, the source sets currentColumn to 10.

  3. 3
    Record the state

    The loop now starts with currentRow = 8 and currentColumn = 10. Both variables are the program state at the moment the loop begins.

Result

currentRow = 8 and currentColumn = 10 at the start of the loop.

Startrow 8, column 10After pass 1row 8, column 11After pass 2row 9, column 11After pass 3row 9, column 12move rightmove downmove right
What is the value of each variable after each step, and how does it change from one iteration to the next? The moves after the start are illustrative, chosen by the learner to show how state is updated; the source does not specify these particular moves.

Read the diagram from left to right. Each pass starts from the values left by the pass before it. If you want to know where the disturbance is at any point, you look at the two variables, not at the loop's code. This is why the values of this state are the first place to check when a result differs from what was expected.

Finding Where the Plan Departs from Intent

Suppose the refined algorithm is correct on paper but the program changes the wrong square. The fastest way to find the fault is to walk through the refined steps in order and compare the state at each step with what was intended. The first step where the recorded state disagrees with the plan is where the path departs from the intended behavior.

Consider the mosaic example. If the start was supposed to be the center, the intended state is currentRow = 8 and currentColumn = 10. If, while tracing, the values after the start step show something else, the error lies in the start step or in how the variables were set there. The loop's body may look correct when read by itself, but the source warns that if the position variables are wrong, the program changes the color of the wrong square even when every line of the loop looks right.

What a Subroutine Is

Definition

A subroutine is a block of code that can be called multiple times from different parts of a program.

Imagine a program that needs the same job done in three different places. You could copy the same lines of code three times, or you could write those lines once and let the rest of the program use them whenever they are needed. A subroutine is the second option. Writing it once lets the program reuse it at many points.

The important word is called. A subroutine does not run just because it exists in a program. It runs when the program reaches a point where it calls it. Because the same block can be called more than once, a single piece of code can serve many moments in a single run of the program.

One Subroutine Called at Several Points

When the main program reaches a call, control jumps into the subroutine, the subroutine's instructions run, and then control returns to the main program at the point after the call. The main program can then reach another call to the same subroutine later in the run. The diagram below shows one main program calling the same subroutine three times. The three calls are illustrative, written for this article to show the pattern.

Main programChange-squaresubroutinecall 1returncall 2returncall 3return
When the main program reaches each call to the same subroutine, where does control jump, and where does it return afterward?
Output (expected)
Each pass through the while loop calls the same change-square code, which is written once and used on every pass.
Notes on the code
  • The fill step is a separate subroutine, called once at a single point in the main routine.
  • The change-square subroutine is called inside the loop, so it runs once per pass.
  • Position updates happen in the main routine, so the state values stay in one place.

The pseudocode above is an illustration of the mosaic structure described in the source, not the source's own code. Notice that the change-square code is written once but runs on every pass. The main routine stays short because each detail is delegated to a named subroutine.

Where ThreeN2.java Uses Subroutines

The source points to a concrete program that uses subroutines: ThreeN2.java. It is introduced in Section 4.4 as an improved version of the 3N+1 program, and the source describes it as a program that uses subroutines and prints its output in neat columns. This is the source's own example, so it is a good place to see the idea in an actual program.

The source gives two reasons ThreeN2.java relies on subroutines. The first is code organization: separating work into named blocks makes the program easier to read and manage. The second is output formatting: the program prints its results in neat columns, and the subroutines are part of how that formatting is achieved.

The source does not list the individual subroutines inside ThreeN2.java or show their code, so this article does not describe them in detail. When you open the file yourself, look for the places where the program calls a named block of code, and compare them with the places where it prints results.

Mistakes Beginners Make with Subroutines

  • Common mistake

    Treating every line of a refined outline as its own subroutine

    What it looks like

    Splitting the start-in-the-middle step and the repeat-while-open loop into separate subroutines even though the source treats them as refinements of one vague step.

    Why it fails

    A subroutine should be a coherent, self-contained task. Pieces that only make sense as part of a loop do not stand alone.

    Do this instead

    Ask whether the step describes a self-contained task that does not depend on how the rest of the program works.

  • Common mistake

    Assuming a subroutine runs just because it is written

    What it looks like

    Writing the fill-with-colors code inside a subroutine and expecting the window to fill without any call to it.

    Why it fails

    A subroutine runs when the program reaches a point where it calls it.

    Do this instead

    Make sure the main routine contains a call to the subroutine at the point where its task is needed.

  • Common mistake

    Checking the loop body but not the state values

    What it looks like

    Rereading the loop line by line when the square changed is the wrong one.

    Why it fails

    Variables such as currentRow and currentColumn are the state the loop depends on. Wrong values produce wrong results even when the loop looks correct.

    Do this instead

    Trace the values of currentRow and currentColumn step by step and find the first step where they differ from the plan.

  • Common mistake

    Copying the same lines of code into several places instead of calling one subroutine from each place

Practice Tracing a Refined Plan

EASY

A refined mosaic plan starts at row 8, column 10 and moves the current position one square to the right on each pass. After three passes, what are the values of currentRow and currentColumn? Then identify which step you would check first if the program changed the wrong square.

Hints
  • Write down the starting values before tracing any pass.
  • Each pass changes only the variable it moves, so track both values after every pass.
  • The first step where your trace disagrees with the plan is the place to start checking.
MEDIUM

Take the vague step change squares at random and write two refinements of it that add a starting condition and a stopping condition. Then decide whether either refined step is coherent enough to become its own subroutine.

Hints
  • The source's refinement adds a starting position and a loop that runs while the window is open.
  • Ask whether the step depends on the loop's changing values before deciding it stands alone.

Key Takeaways

  • Stepwise refinement turns a rough list of steps into progressively more detailed steps that are precise enough to implement.
  • A step becomes a subroutine candidate when it describes a coherent, self-contained task, such as filling the window with random colors.
  • Variables such as currentRow and currentColumn record program state, and checking their values step by step is the fastest way to find where a plan departs from what was intended.
  • A subroutine is a block of code that can be called multiple times from different parts of a program, and it runs each time the program reaches a call to it.
  • ThreeN2.java uses subroutines to organize its code and to help format its output in neat columns.