Concepts / Designing Black-Box Interfaces for Subroutines

Designing Black-Box Interfaces for Subroutines

A subroutine can use local variables declared inside it, formal parameters that receive values from the caller, and variables defined outside it that remain accessible to other parts of the program.

  • Programming

A Scenario to Predict Before Reading On

Once you start writing subroutines, a practical question comes up quickly: when code inside a subroutine works with a name, whose name is it, and who else is allowed to see it? Many beginners assume that every name inside a subroutine is connected to the rest of the program in the same way. That assumption leads to predictable surprises. This article sorts out which names a subroutine can reach, which of those names can influence the caller, and why the answer depends on where a name was originally declared.

What do you think happens?

A caller has a whole-number variable holding the value 5 and passes it to a subroutine. Inside the subroutine, the parameter is increased by 10. Once the subroutine returns, what does the caller's variable hold?

Reveal answer
Answer:

5

The subroutine works with its own parameter name. Increasing that parameter by 10 changes only the parameter, not the caller's variable. For primitive types, the caller's variable keeps the value it had before the call.

Three Places Names Come From

A subroutine is a block of code that can be called multiple times from different parts of a program. Inside that block, the code can work with three different kinds of variables. The first is a local variable, which the subroutine declares for its own use. The second is a formal parameter, which is the name in the subroutine's header that receives a value from whoever calls it. The third is a variable declared outside the subroutine, which the subroutine can still reach. The source material calls this third kind a static member variable declared outside the subroutine. These three categories differ in where their names come from and in how far their effects travel.

Names inside asubroutineLocal variabledeclared inside thesubroutineFormal parameterreceives a value from thecallerOutside variabledeclared outside, sharedcreated forcreated foralready exists
Which names can the code inside a subroutine see and use, and where does each one come from: inside the subroutine, passed in by the caller, or defined outside it?

The diagram shows the main sources of names a subroutine can use. Parameters and locals are both created for the subroutine, while the outer variables already existed before the subroutine ran. That single difference, whether a name belongs to the subroutine or to the wider program, explains most of the behavior discussed in the rest of this article.

Definition

A local variable is declared inside a subroutine and has no connection to the outside world. Because of this isolation, it never affects the rest of the program. A formal parameter is the mechanism for getting information into a subroutine: the caller's values are dropped into the parameter names when the subroutine is called. A variable defined outside a subroutine exists independently of it and is accessible to other parts of the program.

Following a Value Into a Subroutine

A formal parameter is the point where a value enters a subroutine. When the subroutine is called, the caller's value is copied into the parameter name, and from then on the subroutine works with that name. The source puts it this way: once execution of the subroutine begins, a parameter behaves much like a local variable. The parameter name is a fresh name inside the subroutine, even though it started with a value supplied by the caller. Picture the caller and the subroutine as two separate boxes, each with its own labeled storage. The value moves across when the call happens, but the two labels still belong to different boxes.

caller's x5caller's x5parameter n5parameter n15value copied on calladd 10 inside subroutine
When the subroutine changes its formal parameter, what happens to the caller's original variable, and why is the original unchanged?

Why Primitive Parameters Stay Isolated

For primitive types, a change made to the formal parameter inside the subroutine has no effect on the rest of the program. The caller's variable keeps whatever value it had before the call. The source adds a qualification: the situation is more complicated for arrays and objects, which it promises to treat later. This section limits itself to primitive types, where the rule holds cleanly. The following program is an original illustration of the rule, not code from the source.

java
Output
Inside subroutine: 15
After call: 5
Notes on the code
  • n is the formal parameter; it receives a copy of x's value 5 when addTen is called
  • n = n + 10 changes only the parameter name inside the subroutine
  • x in main is a separate variable and still holds 5 after the call returns
Output
Inside subroutine: 15
After call: 5

Global Variables as Back-Door Communication

A variable defined outside a subroutine is treated differently from a parameter. It is called global to the subroutine. It can be used throughout the class in which it is defined and, if it is not declared private, in other classes as well. Because the variable is shared, a change made to it inside a subroutine can have effects that extend beyond that subroutine. The next program is an original example. The counter lives outside increment, so each call changes the same stored value, and main sees the result after both calls return.

java
Output
Count is 2
Notes on the code
  • count is declared outside increment, so it is shared with the rest of the class
  • increment takes no parameters, so nothing about its header tells a reader it changes count
  • main sees the change made inside increment without any value being returned

Reasonable Use Versus Weakened Interfaces

The source is careful not to forbid the practice. Global variables are not always bad inside subroutines. If the global variable is treated as part of the subroutine's interface, the communication is acceptable. The source recommends asking, before you use a global variable in a subroutine, whether it is really necessary. The comparison below sets out what a caller has to know in each case.

FeatureDepends only on parametersReads or writes outside variables
What the caller must knowThe parameter list and the return valueThe parameter list plus every shared variable the subroutine touches
Visibility from the headerInputs and outputs are visible in the headerSome dependencies are hidden and appear only in the body
Effect on the rest of the programLimited to what the caller receives backCan persist and be seen by other parts of the program
Ease of reuse at new call sitesCallers supply inputs and get resultsCallers must also account for the shared state

How a parameter-only subroutine differs from one that uses outside variables

The comparison is an interpretation of the source's point about interfaces, not a rule the source states in this exact form. The source's core claim is that a shared variable becomes part of the subroutine's interface, and that a black box should have an interface that is straightforward and easy to understand.

Calling One Subroutine from Several Points

A subroutine does not run just because it exists in a program. It runs when the program reaches a point where it calls it. Imagine a program that needs the same job done in three places. You could copy the same lines three times, or write them once and call them wherever they are needed. The next program is an original example that calls one subroutine twice from main, once before a heading and once after. Each call transfers control into the subroutine, runs its body, and returns control to the exact place where the call was made.

java
Output
-----
Results
-----
Notes on the code
  • The first printLine call runs the subroutine body and then returns to main
  • main prints Results between the two calls
  • The second call runs the same stored instructions again
main programprintLine subroutine1. call printLine2. return to main3. print Results4. call printLine again5. return to main
In what order does the main program call the same subroutine at different points, and what happens each time control enters and leaves it?

Where ThreeN2.java Fits

The source describes ThreeN2.java as an improved version of the 3N+1 program, introduced in Section 4.4. It is described as a program that uses subroutines and prints its output in neat columns. The source gives two reasons for the 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 When Reasoning About Subroutine Names

  • Common mistake

    Assuming a parameter is the same variable as the caller's variable

    What it looks like

    Changing n inside addTen and expecting x in main to change as well

    Why it fails

    The parameter is a fresh name inside the subroutine. For primitive types, the caller's variable keeps its value.

    Do this instead

    Trace the caller's variable and the parameter as two separate names, each with its own value.

  • Common mistake

    Treating every outside variable as harmless because the subroutine is short

    What it looks like

    A short increment method that quietly changes a shared count

    Why it fails

    A shared variable is part of the subroutine's interface, even when the header does not mention it.

    Do this instead

    Before using a global variable in a subroutine, ask whether it is really necessary, and document the dependency.

  • Common mistake

    Believing a subroutine runs whenever it is written in the file

    Why it fails

    The source says a subroutine runs when the program reaches a point where it calls it.

Tracing a Subroutine Yourself

EASY

A caller has a variable total holding 7 and passes it to a subroutine named bump, whose parameter is named value. Inside bump, value is increased by 3. After bump returns, what does total hold? Then suppose a static variable hits is declared outside bump, starts at 0, and bump adds 1 to it. If main calls bump twice, what does hits hold at the end?

Hints
  • Decide first whether value and total are the same name or two names.
  • Recall that for primitive types the parameter behaves much like a local variable once execution begins.
  • For hits, track how many times the body runs and whether the change persists after each return.

Keeping Subroutine Interfaces Clear

  1. 1
    List the names a subroutine uses and label each as local, formal parameter, or outside variable
  2. 2
    For each parameter, remember that a primitive value is copied in and changes stay inside the subroutine
  3. 3
    For each outside variable, record it as part of the subroutine's interface and ask whether it is really necessary
  4. 4
    Trace each call from the main program, noting where control enters and returns

Key Takeaways

  • A subroutine can use three kinds of names: local variables it declares, formal parameters that receive values from the caller, and variables declared outside it.
  • For primitive types, changing a formal parameter inside a subroutine does not change the caller's variable.
  • A global variable used inside a subroutine becomes part of its interface, and its effects can persist beyond the subroutine, so use it only when it is really necessary.
  • A subroutine is a block of code that is called from different points in a program, and it runs each time the program reaches a call to it.