Skip to content
Study Hall
Lesson 3 of 3Methods
Unit 5 · Lesson 3 of 3

Decomposition and Program Design

Turning one long main into a set of small methods that each do one job — naming them, testing them with stubs, and designing from the top down.

10 min read10 practice questionsAsk AI about this →

Key things to remember

  1. 1One method, one job — if you need the word "and" to describe it, split it.
  2. 2Method names are verbs or verb phrases in camelCase; boolean methods start with is or has.
  3. 3Top-down design: split the job into steps, then split any step that is still too big, before writing any logic.
  4. 4A stub has the correct signature and a placeholder body so the program compiles and runs while it is unfinished.
  5. 5Test one method at a time as you replace each stub, so any new bug is in the piece you just wrote.
  6. 6Every method called directly from main must be static, because main runs without an object.

Decomposition means breaking a problem into pieces small enough to be obviously correct, then assembling the pieces. It is the difference between a program you can reason about and one you can only hope about. The target is simple: each method should do one thing, and its name should say what that thing is. If you cannot describe a method in a single short sentence without using the word "and", it is doing too much.

  • You need a comment to explain what a section of the method does — that comment is the name of a method waiting to be written.
  • The name contains "and": readAndValidateAndAverage is three methods wearing a trench coat.
  • The body is long enough that you have to scroll to see all of it.
  • You cannot test one part of it without running all of it.
  • The same block of code also appears somewhere else in the program.

Top-down design means writing the outline before the details. Start with the whole job and split it into a handful of major steps. Ask of each step whether you could write it in a few clear lines; if not, split it again. You stop when every piece is small enough to write directly. The payoff is that main ends up reading like a summary of the program, with the detail tucked into the methods that need it.

Decomposition: break one long method into several that each do one job.BEFORE — one long main()read the markscheck they are validwork out the averagefind the highestprint the reportsplitAFTER — main just callsmain()readMarks();validate();average();report();readMarks()validate()average()report()Now main reads like a summary of the program, and each method can be tested on its own.A job you need twice is written once and called twice — one place to fix when it is wrong.
Fig 1One job splits into steps, and a step that is still too big splits again.
The shape carries meaning — never draw everything as a box.START / ENDterminator — one entry, one exitINPUT / OUTPUTread a value, print a resultPROCESSa calculation or assignmentDECISIONa yes/no test — exactly two exitsa loop is a decision whose “yes” goes back upPseudocode does the same job:READ markIF mark >= 50 THEN PRINT "Pass"ELSE PRINT "Fail"ENDIF
Fig 2Standard planning shapes: oval for start and end, parallelogram for input and output, rectangle for a process, diamond for a decision.
import java.util.Scanner;

public class MarkReport {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int[] marks = new int[5];

        System.out.println("=== Mark Report ===");

        for (int i = 0; i < marks.length; i++) {
            System.out.print("Enter mark " + (i + 1) + ": ");
            int value = input.nextInt();
            while (value < 0 || value > 100) {
                System.out.print("Marks must be 0 to 100. Try again: ");
                value = input.nextInt();
            }
            marks[i] = value;
        }

        int total = 0;
        for (int i = 0; i < marks.length; i++) {
            total = total + marks[i];
        }
        double average = (double) total / marks.length;

        int highest = marks[0];
        for (int i = 1; i < marks.length; i++) {
            if (marks[i] > highest) {
                highest = marks[i];
            }
        }

        int lowest = marks[0];
        for (int i = 1; i < marks.length; i++) {
            if (marks[i] < lowest) {
                lowest = marks[i];
            }
        }

        String level;
        if (average >= 80) {
            level = "Level 4";
        } else if (average >= 70) {
            level = "Level 3";
        } else if (average >= 60) {
            level = "Level 2";
        } else if (average >= 50) {
            level = "Level 1";
        } else {
            level = "Below Level 1";
        }

        System.out.println("--- Results ---");
        System.out.println("Average: " + average);
        System.out.println("Highest: " + highest);
        System.out.println("Lowest:  " + lowest);
        System.out.println("Level:   " + level);
    }
}
A monolithic main: correct, about 55 lines, and hard to work with. Testing the level rules means typing five marks in by hand every single time.

Step 1 — find the separate jobs

Read the long method and mark where one idea stops and the next begins. The blank lines are usually a good first guess, because you already put them there for exactly that reason; section comments are an even stronger clue. Ask as well which parts you could test on their own, since anything checkable with a handful of values is a natural method. In this program there are six distinct jobs.

  1. Read one mark and refuse anything outside 0 to 100.
  2. Read a whole set of marks into an array.
  3. Work out the average of an array.
  4. Find the highest value in an array.
  5. Find the lowest value in an array.
  6. Turn an average into an Ontario achievement level.

Step 2 — name each job, then write its signature

JobSignatureWhy that return type
Read one valid markstatic int readValidMark(int number)It produces a mark, so it returns one
Read all the marksstatic int[] readAllMarks(int howMany)It produces a filled array
Average an arraystatic double average(int[] marks)An average is rarely a whole number
Find the higheststatic int highest(int[] marks)It produces one of the marks
Find the loweststatic int lowest(int[] marks)Same shape, opposite comparison
Average to levelstatic String levelFor(double mark)The level is text, not a number
Show the reportstatic void printReport(int[] marks)It displays; it produces nothing
Name the method before you write it, then decide what it needs and what it produces. At this point the whole program is designed and not one line of logic has been written.
Name methods with verbs, variables with nouns
A method does something, so its name should be an action: readValidMark, printReport, levelFor. A variable holds something, so its name should be a thing: total, marks, average. Methods that answer a yes or no question conventionally start with is or has, such as isValid or hasPassed, which makes them read naturally inside an if.

Step 3 — the decomposed program

import java.util.Scanner;

public class MarkReport {
    static Scanner input = new Scanner(System.in);

    public static int readValidMark(int number) {
        System.out.print("Enter mark " + number + ": ");
        int value = input.nextInt();
        while (value < 0 || value > 100) {
            System.out.print("Marks must be 0 to 100. Try again: ");
            value = input.nextInt();
        }
        return value;
    }

    public static int[] readAllMarks(int howMany) {
        int[] marks = new int[howMany];
        for (int i = 0; i < marks.length; i++) {
            marks[i] = readValidMark(i + 1);
        }
        return marks;
    }

    public static double average(int[] marks) {
        int total = 0;
        for (int i = 0; i < marks.length; i++) {
            total = total + marks[i];
        }
        return (double) total / marks.length;
    }

    public static int highest(int[] marks) {
        int best = marks[0];
        for (int i = 1; i < marks.length; i++) {
            if (marks[i] > best) {
                best = marks[i];
            }
        }
        return best;
    }

    public static int lowest(int[] marks) {
        int worst = marks[0];
        for (int i = 1; i < marks.length; i++) {
            if (marks[i] < worst) {
                worst = marks[i];
            }
        }
        return worst;
    }

    public static String levelFor(double mark) {
        if (mark >= 80) {
            return "Level 4";
        } else if (mark >= 70) {
            return "Level 3";
        } else if (mark >= 60) {
            return "Level 2";
        } else if (mark >= 50) {
            return "Level 1";
        }
        return "Below Level 1";
    }

    public static void printReport(int[] marks) {
        double avg = average(marks);
        System.out.println("--- Results ---");
        System.out.println("Average: " + avg);
        System.out.println("Highest: " + highest(marks));
        System.out.println("Lowest:  " + lowest(marks));
        System.out.println("Level:   " + levelFor(avg));
    }

    public static void main(String[] args) {
        System.out.println("=== Mark Report ===");
        int[] marks = readAllMarks(5);
        printReport(marks);
    }
}
main is now three lines and reads like a description of the program. Every helper is static, because main is static and runs with no object in existence to own them.
What decomposition actually bought
levelFor can now be tested on its own with the values 85, 79, 50 and 12, without typing five marks in first. readValidMark is reused for every mark instead of being written once and trusted. Changing the report layout touches only printReport. And main states the whole program in three lines, so a reader knows what it does before reading any detail.
// Stubs: right signature, placeholder body, compiles and runs today
public static double average(int[] marks) {
    return 0.0;                        // TODO: real calculation
}

public static String levelFor(double mark) {
    return "Level ?";                  // TODO: real thresholds
}

public static void printReport(int[] marks) {
    System.out.println("printReport called");   // TODO: real layout
}
A stub keeps the program compiling while the real body is still missing, so you can run it from day one and replace one stub at a time. A void stub may be empty; a returning stub must return something of the right type.

Worked examples

Reveal one step at a time and try to beat it to the next line.

Example 1
Decompose this into method signatures: ask the user for a list of item prices, add 13% HST, and print a receipt showing the subtotal, tax and total.
Example 2
A classmate's method is named processData and is 70 lines long. What questions would tell you how to split it?
Example 3
Using the decomposed MarkReport program, what does it report for the marks 90, 80, 70, 60 and 50?

Where marks get lost

  • Writing every method before running anything, so the first test produces a pile of errors with no way to tell which method caused which.
  • Naming methods with nouns or with nothing in particular — data(), stuff(), calc() — which forces a reader to open the body to learn what it does.
  • Forgetting static on a helper, then getting non-static method cannot be referenced from a static context and not recognizing the cause.
  • Making a method both calculate and print, so it cannot be reused anywhere the value is needed but should not be displayed.
  • Leaving a stub in place after moving on, so the program quietly reports 0.0 for something that was never finished.

Flashcards 10

What is decomposition?
How do you know a method does too much?
What is top-down design?
What is a stub?
+ 6 more

Practice

1
Which is the clearest sign that a method is doing too much?
2
Suggest a good method name for a method that works out and returns the average of an array of marks.
3
Order the steps of top-down design.
  1. 1Write stubs so the program compiles and runs
  2. 2Name each piece with a verb and write its signature
  3. 3Replace one stub at a time, testing after each
  4. 4State the whole job in one sentence
  5. 5Split it into a handful of major steps
  6. 6Split any step that is still too big to write directly
4
What is a stub?
5
A helper method gives the error non-static method cannot be referenced from a static context when called from main. What is the fix in a single-class program?
6
Using the decomposed MarkReport, what average is printed for the marks 90, 80, 70, 60 and 50?
7
For that same set of marks, what does levelFor(70.0) return?
8
Match each job to the best method signature for it.
Produce the largest value in an array
Turn an average into an achievement level
Display the finished report
Read one mark, rejecting anything out of range
9
Why is average(int[] marks) returning a double better than a method that prints the average itself?
10
Which principle says that the same logic should not be written out in several places?

Spot a mistake on this page? Let us know