Concepts / Designing Interfaces for Reuse

Designing Interfaces for Reuse

A subroutine is a procedural abstraction whose interface can be used without knowing how the procedure is implemented.

  • Programming

Using Code You Never Open

Suppose you need a list of numbers sorted, the text pulled out of a web page, or a date converted into a different format. You can often accomplish this by calling a subroutine that someone else wrote, and you may never look at the code inside it. You only need to know what to give the subroutine and what it will hand back to you.

This article is about why that works, and about the idea that makes it safe: the details of how a task is carried out can stay hidden from the people who merely use the result. Keep one question in mind as you read. If you were handed a subroutine with no access to its code, what would you still need to know in order to use it correctly? By the end you should be able to answer that precisely, and explain why the answer is so small.

Predicting the Effect of a Rewrite

Before reading on, picture a rounding subroutine that a program calls. Its author rewrites the body so that it uses a completely different method. The new version still returns the same results the documentation promises. Would the calling program need to be edited?

What do you think happens?

If the body of a subroutine is rewritten with a different method but still returns the promised results, must the calling program be edited?

  • Yes, every caller must be changed

  • No, the caller was written against the interface alone

Reveal answer
Answer:

No. The calling program does not need to be edited.

The caller depends on the interface, namely how to invoke the subroutine and what it produces. As long as that interface and its promised results stay the same, the internal method can change without any change to the caller. The rest of this article explains why.

Caller, Interface, and Implementation

A subroutine is described as a procedural abstraction. The word abstraction here means that the subroutine stands in for a procedure, which is a sequence of steps that accomplishes some job. The caller does not work with those steps directly. It works with the subroutine's interface, and the interface is the abstraction you actually use. The interface tells you how to invoke the procedure and what it will produce. It does not require you to know how the procedure is carried out.

The same relationship has three parts, and it helps to name them separately. The caller is the code that wants the job done. The interface is the agreed way of asking and answering. The implementation is the actual sequence of steps that runs when the call happens. The caller and the interface are what the rest of a program sees. The implementation is what the interface stands in front of.

Caller codewants the job doneInterfacename, parameters, returnvalueImplementationhidden sequence of stepsinvokesis backed by
What is visible to the caller (name, parameters, return value), and what is hidden inside the subroutine? Where is the boundary between them?

The diagram shows the chain of responsibility. The caller depends on the interface. The interface, in turn, is backed by an implementation that the caller never has to inspect. From the outside, you reason about what goes in and what comes out, and the middle is someone else's concern.

The Black Box Principle in Use

The source describes the black box principle as another name for the idea just discussed: using a subroutine or an abstraction without knowing its internal implementation. The phrase is informal, but the picture is useful. Think of a black box as something with an input slot and an output slot. You feed it what it needs, you receive what it produces, and the mechanism between those slots is not part of your reasoning.

Consider a small subroutine that averages a list of scores. The example below is an original illustration, not taken from the source. Notice that the caller needs only the name, the parameter, and the returned value to use it correctly.

Code walkthrough
def average_of(numbers):
    total = 0
    for n in numbers:
        total += n
    return total / len(numbers)

score = average_of([80, 90, 70])
print(score)
  1. 1
    The function is defined with one parameter called numbers. This is part of the interface.
  2. 2
    Inside the body, a running total is built up with a loop. A caller does not need to know this.
  3. 3
    The return statement hands back the average. This is the output slot of the black box.
  4. 4
    The caller passes a list and stores the result in score. It never inspects total or the loop.
  5. 5
    Printing score shows 80.0, the average of 80, 90, and 70.
  • The def line is the interface's name and parameter
  • The loop and total variable are internal details the caller never sees
  • The return line is the value handed back through the interface
Output
80.0

Tracing a Call Through the Boundary

Follow one call step by step. The caller supplies an input through the interface. The interface passes that input to the hidden implementation. The implementation carries out its steps and produces a result, which travels back out through the interface to the caller. At no point does the caller need to know which steps ran in between. The caller's entire view is a name, some inputs, and a returned result. The implementation sits behind the interface and is reachable only through it.

A caller who never opens the implementation can still write a correct program. That is precisely what the black box principle promises.

Information Hiding and Changing the Inside

An important aspect of abstraction in general is information hiding. An abstraction hides information about what lies behind it, in its implementation. Information hiding is what allows a subroutine's inside to be changed without affecting the programs that use it.

The state change works like this. The body moves from one version to another, while the interface that callers rely on is not part of the change. Because the callers were written against the interface alone, their code does not need to be edited. The diagram below marks what changes and what stays the same.

average_ofinterfaceone parameter, returnsaverageLoop-based bodyrunning total with a loopaverage_ofinterfaceone parameter, returnsaverageBuilt-in sum bodysum then divide by lengthbacked bybacked by
If the implementation is replaced while the interface stays the same, what changes and what stays the same for the calling code?
Before
def average_of(numbers):
    total = 0
    for n in numbers:
        total += n
    return total / len(numbers)

The body builds a running total with an explicit loop.

After
def average_of(numbers):
    return sum(numbers) / len(numbers)

The body uses a built-in sum. The name, parameter, and return value are unchanged, so score = average_of([80, 90, 70]) still prints 80.0 in both versions.

The source also gives two concrete illustrations of this idea. Java control structures can be used on different types of CPU with different underlying machine-language implementations. The inside of a subroutine can be changed without affecting the programs in which it is used.

What a Caller Depends On

The interface is a contract. Changes to what callers depend on still matter, even when the inside changes freely. The caller relies on the signature, which is the subroutine's name and how it is invoked. It relies on what each parameter means and on the return type. It also relies on the documented behavior, meaning the results the documentation promises. The caller does not rely on internal variables, the specific algorithm, or the data structures used inside the body.

SignatureInternal variablesParameter meaningsAlgorithmReturn typeData structuresDocumented behavior
Which details (signature, parameter meanings, return type, documented behavior) does the caller rely on, and which (internal variables, algorithm, data structures) does it ignore?
DetailDoes the caller depend on it?Example from average_of
Subroutine nameYesaverage_of must be spelled exactly as defined
Parameter meaningYesnumbers is a list of scores to average
Return type and valueYesThe caller receives the average, such as 80.0
Documented behaviorYesThe promised result is the mean of the values
Loop or built-in sumNoHow the total is computed
Local variable totalNoA name used only inside the body

The caller's dependencies are the interface. The body's choices are invisible to the caller.

Mistakes That Break the Boundary

  • Common mistake

    Reading the body to decide how to call the subroutine

    Why it fails

    The interface already tells you how to invoke the procedure and what it produces. Relying on the body makes your code depend on details the caller is not meant to use.

    Do this instead

    Read the name, parameters, return value, and documentation. Use the subroutine according to its interface alone.

  • Common mistake

    Assuming any change inside the subroutine is harmless

    Why it fails

    Information hiding protects callers only when the promised results stay the same. The interface is a contract, so changes to what callers depend on still matter.

    Do this instead

    Replace the internals freely, but keep the signature, parameter meanings, return type, and documented behavior fixed.

  • Common mistake

    Treating information hiding as security

    Why it fails

    The source describes information hiding as a discipline for separating parts that evolve freely from parts other code depends on, not as protection against attackers.

    Do this instead

    Use hiding to keep callers independent of implementation changes, not as a way to protect secrets.

  • Common mistake

    Changing the parameter order while keeping the name, which silently breaks every caller

Practice: Spot the Dependency

EASY

A teammate replaces the body of average_of with a version that sorts the list first and then averages it. The name, parameter, and documented result are unchanged. Explain whether the calling code needs editing, and identify one detail the caller would depend on if the parameter were renamed to scores with a different meaning.

Hints
  • Ask which parts of the interface the caller actually relies on.
  • Sorting is an internal algorithm choice, the same kind of detail as a loop versus a built-in sum.
  • A different meaning for the parameter changes a part of the contract, not just the body.

Key Takeaways

  • A subroutine is a procedural abstraction: callers use its interface without knowing how the procedure is implemented.
  • The caller, interface, and implementation form a chain. The caller depends on the interface, which is backed by a hidden implementation.
  • The black box principle means reasoning about inputs and outputs while leaving the internal mechanism out of your reasoning.
  • Information hiding lets the inside of a subroutine change without affecting callers, as long as the interface and its promised results stay the same.
  • A caller depends on the signature, parameter meanings, return type, and documented behavior, but not on internal variables, algorithms, or data structures.