Writing Your Own Subroutines
A subroutine's contract describes what it does when the caller supplies acceptable parameter values, and it is violated when bad values are supplied.
Try it: try / except / else / finally
How an exception jumps out of a try block to the first matching except clause, when else runs, and why finally always runs.
How it works
- Statements in try run until one raises an exception; the rest of try is skipped.
- Python checks the except clauses in order and runs the first one whose type matches.
- If nothing was raised, the else block runs instead.
- finally runs in every case.
Default run (9 steps): Call safe_divide('4'). … Return 25.0. Output was: Worked: 25.0 / Done.
Simplified: One fixed function; int() and / are modelled exactly (including how Python prints floats), but nothing is executed as Python.
Loading the simulation…
Why Write Code Once
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. The second option is what a subroutine gives you. Before reading on, pause and ask yourself: if a program needs the same calculation or the same printed output in several places, would you rather keep the instructions in one place or in every place they are needed? The main idea of subroutines is an answer to that question.
This article follows one idea through to its consequences. Every subroutine works under a kind of promise. Its contract describes what the subroutine will do, but only when the caller hands it values the subroutine can accept. When a caller breaks that bargain by passing a bad value, the person who wrote the subroutine has to decide what should happen. The short answer taught here is that a well-written subroutine does not quietly carry on with a value it cannot use.
A Subroutine Is a Reusable Block
A subroutine is a block of code that can be called multiple times from different parts of a program. 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 one run of the program.
Tracing Repeated Calls from main
The example below is an original illustration, not taken from the source. It defines one subroutine named printSquare and calls it three times from main. Notice that the guard check sits at the top of the subroutine, before any real work happens. That placement is the pattern the source recommends, so that illegal values never reach the normal work.
2 squared is 4
5 squared is 25
3 squared is 9
main is finished.- The guard check at the top rejects any n that is zero or negative.
- The same
printSquareblock is called three times, each time with a different value. - Each call runs the whole subroutine, then control returns to main at the line after the call.
Trace the run step by step. main reaches the first call and control moves into printSquare with n equal to 2. The check passes, the line prints, and the subroutine finishes. Control then returns to main, which continues to the second call. The same block of code runs again with n equal to 5, and then again with n equal to 3. The code of printSquare is written only once, yet it does its job at three different moments in a single run.
What a Subroutine's Contract Promises
A subroutine's contract describes what it does when the caller supplies acceptable parameter values, and it is violated when bad values are supplied.
Think of the contract as the subroutine's side of a bargain. For printSquare, the parameter is supposed to be a positive integer. When the caller passes 5, the caller keeps the bargain and the subroutine does its job. When the caller passes -4, the caller breaks the bargain, because the subroutine has no sensible result to compute. The subroutine itself did nothing wrong; the fault lies in the value it was handed.
| Value supplied by the caller | Does it honor the contract? | What the subroutine does |
|---|---|---|
| 5 | Yes, a positive integer | Carries out its job and prints the result |
| 1 | Yes, a positive integer | Carries out its job and prints the result |
| 0 | No, not positive | Throws an IllegalArgumentException |
| -4 | No, not positive | Throws an IllegalArgumentException |
Acceptable and violating parameter values for a subroutine whose parameter must be positive
Signaling a Broken Contract with throw
A subroutine can respond to a violated contract by throwing an exception, which is a signal that the contract has been violated. The source gives a typical case: a subroutine whose parameter must be positive tests that parameter before doing any real work. If the test fails, it throws an IllegalArgumentException with a message describing the problem. The source's example line reads as follows.
- throw tells the computer to begin the exception-handling process at this point.
- new creates the exception object. An exception is an object, so it must be created before it can be thrown.
IllegalArgumentExceptionis the kind of exception chosen to signal a bad parameter value.- The string in quotation marks is the message that describes the error that was detected.
The word new is what creates the object, and the message is a string. The source notes that the full mechanics of creating exception objects are covered later in the course, so for now this syntax is enough to write working guard checks in your own subroutines.
What a throw Skips
Once the computer reaches a throw statement, the subroutine stops right there. The statements that follow the throw inside the subroutine body are skipped entirely. This is a stronger action than returning early. A return gives control back to the caller in an orderly way, while a throw starts the exception-handling process. If that process finds no handler, the program as a whole crashes.
What do you think happens?
After the throw statement runs inside a subroutine, does the rest of that subroutine's body still run?
Reveal answer
No, the statements after the throw are skipped entirely.
The subroutine terminates immediately at the throw. Only a try..catch around the call can keep the program running after that point.
Read the flowchart as a trace of one bad call. The check fails, so the throw runs and the normal work lines are never reached. Control leaves the subroutine, and then it is up to the caller to catch the exception. If nothing catches it, the program crashes.
How an Exception Travels Back to the Caller
The throw happens inside the subroutine, but the decision about what to do with the exception often belongs to the caller. A subroutine that throws is announcing a broken contract. A caller that wants to survive the failure has to wrap its call in a try..catch statement. The source is explicit that the subroutine call itself must sit in the try part of that statement for the handling to work.
The diagram shows a two-level call. The throw sits in printSquare, but the try..catch that could stop the crash must surround the call made from main. Placing the try..catch anywhere else would leave the call unprotected and the program would still crash.
Catching the Exception Around the Call
The next example is original and extends the earlier CallDemo. The call to printSquare with a bad value now sits inside the try part of a try..catch statement, so the handler runs instead of the program crashing.
public class SafeDemo {
static void printSquare(int n) {
if (n <= 0) {
throw new IllegalArgumentException("Value must be positive.");
}
System.out.println(n + " squared is " + (n * n));
}
public static void main(String[] args) {
try {
printSquare(4);
printSquare(-1);
System.out.println("This line is skipped.");
} catch (IllegalArgumentException e) {
System.out.println("Caught a bad value; the program keeps going.");
}
System.out.println("main is finished.");
}
}- 1
printSquare(4)passes the check and prints 4 squared is 16. - 2
printSquare(-1)fails the check and throws anIllegalArgumentException. - 3Control leaves
printSquareand jumps to the catch block in main, skipping the remaining statements of the try block. - 4The catch block prints its message, and main prints its final line.
- The try block contains both calls, so the exception raised by the second call is caught here.
- The line after the bad call inside the try block never runs, matching the rule that control leaves at the throw.
- The catch block runs, and the program then continues with the line after the whole try..catch statement.
4 squared is 16
Caught a bad value; the program keeps going.
main is finished.Mistakes Beginners Make with Guard Checks
The following mistakes come up often when students first write subroutines that throw exceptions. Each one is tied to a rule stated in the source.
Common mistake Expecting the lines after a throw to run
What it looks likeA subroutine throws on a bad value and then prints a final line, expecting that line to appear anyway.
Why it failsA throw statement immediately terminates the subroutine, so the statements that follow it inside the body are skipped.
Do this insteadPlace any cleanup or result-printing logic where it will run only when the check has passed, or handle the failure in the caller.
Common mistake Putting the parameter check after the real work
What it looks likeA subroutine prints the square first and checks for a positive value afterward.
Why it failsBy then the subroutine has already used the illegal value, so the check protects nothing.
Do this insteadPlace the parameter check at the top of the subroutine so that illegal values never reach the normal work.
Common mistake Wrapping the subroutine definition in try..catch instead of the call
Why it failsThe source says the subroutine call must sit in the try part of the statement for the handling to work. A try around the definition does not catch an exception raised when the call is made.
Do this insteadPlace the call, such as printSquare(-1), inside the try block in the caller.
Common mistake Assuming a subroutine runs just because it is written
Why it failsA subroutine runs only when the program reaches a point where it calls it.
Do this insteadTrace the calls in main to see exactly which calls run and in what order.
Where ThreeN2.java Uses Subroutines
The source describes ThreeN2.java, introduced in Section 4.4 as an improved version of the 3N+1 program, as a program that uses subroutines and prints its output in neat columns. The source gives two reasons for this design. The first is code organization: separating work into named blocks makes the program easier to read and manage. The second is output formatting: the subroutines are part of how the results are printed in neat columns.
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. Each named block you find is a candidate for the same reuse you traced in the CallDemo example.
Tracing a Run with a Bad Value
Using the printSquare subroutine from CallDemo, which rejects zero and negative values, main calls printSquare(4), then printSquare(-1), then printSquare(6), with no try..catch anywhere. Write down exactly what the program prints, and state which call (if any) is the last one that runs.
Hints
- Trace the calls in order and check each value against the guard before deciding what happens.
- Once a throw runs, think about whether any later statement in main can execute without a try..catch.
Key Takeaways
- A subroutine is a block of code that can be called multiple times from different parts of a program, and it runs only when the program calls it.
- A subroutine's contract describes what it does for acceptable parameter values, and it is violated when bad values are supplied.
- A throw statement with new
IllegalArgumentExceptionsignals a violated contract; it immediately stops the subroutine, skipping its remaining statements. - An exception crashes the program unless a try..catch statement catches it, and the subroutine call itself must sit in the try part.
ThreeN2.javauses subroutines to organize its code and to help format its output in neat columns.