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":
readAndValidateAndAverageis 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.
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);
}
}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.
- Read one mark and refuse anything outside 0 to 100.
- Read a whole set of marks into an array.
- Work out the average of an array.
- Find the highest value in an array.
- Find the lowest value in an array.
- Turn an average into an Ontario achievement level.
Step 2 — name each job, then write its signature
| Job | Signature | Why that return type |
|---|---|---|
| Read one valid mark | static int readValidMark(int number) | It produces a mark, so it returns one |
| Read all the marks | static int[] readAllMarks(int howMany) | It produces a filled array |
| Average an array | static double average(int[] marks) | An average is rarely a whole number |
| Find the highest | static int highest(int[] marks) | It produces one of the marks |
| Find the lowest | static int lowest(int[] marks) | Same shape, opposite comparison |
| Average to level | static String levelFor(double mark) | The level is text, not a number |
| Show the report | static void printReport(int[] marks) | It displays; it produces nothing |
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);
}
}// 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
}