Home Structuring methods
Use methods to organize your code
Besides using methods to help reduce redundancy, you should also use methods to help structure and organize your code into distinct sections to enhance readability.
A good rule of thumb is to try and write methods that contain a single primary responsibility. Try and avoid trying to do too many things at once: use helper methods to divide and delegate subproblems.
More specifically, we are not saying that each method has to be made up of literally a single operation or component – that would be silly. Rather, what we're saying is that your method should be responsible for doing only a single thing, and should delegate everything else to other methods and classes.
For example, take the following method which prints out a staircase, square, and rectangle in succession:
public static void drawArt() { for (int i = 1; i <= 5; i++) { for (int j = 0; j < i; j++) { System.out.print("*"); } System.out.println(); } for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { System.out.print("#"); } System.out.println(); } for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 20; j++) { System.out.print("@"); } System.out.println(); } }
The code is really doing three separate things here: drawing a staircase, drawing a square, and drawing a rectangle. Forcing the method to explicitly handle all three of these things is giving it too much to do. When you're trying to draw art, you shouldn't have to worry how exactly a square is drawn. A better version would look something like this:
public static void drawArt() { drawStaircase(5); drawSquare(5); drawRectangle(5, 20); } // etc
Now, our drawArt
is directly responsible for one and only one thing: drawing
something. Our helper methods have now been placed in charge of drawing a staircase,
drawing a square, and drawing a rectangle respectively.
Note that after completing this refactoring, we haven't decreased the amount of redundancy at all. This is ok – we did end up improving the overall readability, so we've experienced a net gain on that front.
In general, you should feel free to use methods to either reduce redundancy or to help organize your code, or both.
In some cases, it can be more subtle and difficult to tell when it's a good idea to split apart an existing method. For example, take the following method, which prompts the user if they want to change their email address:
public static String tryGettingNewEmail(Scanner scan, String oldEmail) { System.out.println("Old email: " + oldEmail); String answer = ""; while (!answer.startsWith("Y") || !answer.startsWith("N")) { System.out.print("Change email? (Y/N): "); answer = scan.nextLine().toUpperCase(); } if (answer.startsWith("Y")) { System.out.print("New email: "); return scan.nextLine(); } else { return oldEmail; } }
An added complication to this method is that it will only let the user continue if they type in a string starting with either "Y" or "N". If they type in something like "maybe", this code snippet will treat that as an error and ask them again.
Should this method be refactored? At first glance, it seems like the method is already responsible for one single thing, and doesn't contain any obvious redundancy.
As it turns out, we can, if we treat the "select Y or N" part of the method as a distinct subtask:
public static String tryGettingNewEmail(Scanner scan, String oldEmail) { System.out.println("Old email: " + oldEmail); boolean userWantsChange = userSelectedYes(scan, "Change email?"); if (userWantsChange) { System.out.print("New email: "); return scan.nextLine(); } else { return oldEmail; } } public static boolean userSelectedYes(Scanner scan, String prompt) { String answer = ""; while (!answer.startsWith("Y") || !answer.startsWith("N")) { System.out.print(prompt + " (Y/N): "); answer = scan.nextLine().toUpperCase(); } return answer.startswith("Y"); }
Once we make this change, it's much easier to tell what each method is individually
doing. Our tryGettingNewEmail
email is also much more readable since we were
able to take the subproblem of validating user input and split it off into a separate
helper and use readable method and variable names.
Main methods are a summary
Your main method should be a summary of your entire program – like a table of contents. This is an extension of the previous rule.
This rule is really a logical extension of the previous rule, but it bears repeating.
You should resist the urge to try and cram too much code into your main method, since it'll then become too crowded and too difficult to read.
For example, you don't want your main method to look like this:
public static void main(String[] args) { for (int i = 1; i <= 5; i++) { for (int j = 0; j < i; j++) { System.out.print("*"); } System.out.println(); } for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { System.out.print("#"); } System.out.println(); } for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 20; j++) { System.out.print("@"); } System.out.println(); } }
This is the same example as before. A good main method would look like this:
public static void main(String[] args) { drawStaircase(5); drawSquare(5); drawRectangle(5, 20); } // etc
Reading a main method should feel akin to reading a table of contents, and less like reading the entire book.
Be careful not to err to the other extreme. Don't write code that looks like this:
public static void main(String[] args) { doSomethingMinor(); doEverythingElse(); } public static void doSomethingMinor() { // ...etc } public static void doEverything() { // ...etc } // etc
While your main method is technically now summarizing your program, it's a very poor summary. To continue using our metaphor of the book, this is like seeing a table of contents with exactly two entries: "Introduction" and "Rest of Book". While you could technically write a book with such a table of contents, it'd be pretty useless.
Trivial methods
Never write a method that does only a single operation or is composed of only a single component. Instead, just perform that operation directly and skip the middle-man.
This is an example of what a trivial method looks like:
public static void myPrintln(String line) { System.out.println(line); }
This method does only a single thing – print a message. While on the surface,
this method appears to simplify things (now, we don't have to keep typing
System.out
when we want to print things), this would NOT be a good method to
write.
This is because if our method does only one thing, then it's sort of useless, since we could have just done that "one thing" up in the caller. Remember, we want to keep code logically concise and reduce redundancy, but don't really care about keeping code literally concise and doing things solely for the sake of lowering the character count.
This method also does not enhance readability in any appreciable way: the method name does not make it easier to understand what the underlying code is doing.
So, since this method does not enhance readability or reduce redundancy in any meaningful way, we call this a trivial method.
However, do note that not all one-line methods are trivial methods – this is a common misconception many students have. For example, take the following:
public static double pythagoreanTheorem(double sideA, double sideB) { return Math.sqrt(sideA * sideA + sideB * sideB); }
Even though this method is only one line long, it's doing 4 separate operations (two multiplications, one addition, and one square root) so is definitely worthy of being refactored into a helper method.
However, it can sometimes on rare occasion be acceptable to write a method to wrap around an operation which does only a single thing if we gain some other tangible benefit as a result. For example, take the following segment of code:
public int foo() { // ... if (symbol == '~') { // ... } // ... }
Why are we comparing the symbol
variable to a tilde? What is the
significance of the tilde? We could leave an inline comment explaining, or wrap
the comparison in a helper method instead:
public int foo() { // ... if (isStart(symbol)) { // ... } // ... } public boolean isStart(char symbol) { return symbol == '~'; }
This is a trivial method, but we've redeemed it somewhat by giving it a more useful name
which makes understanding the intent of the caller more clear. Contrast this to the initial
println
example – we gain zero clarity by wrapping
System.out.println
in a println
method.
Also note that both versions of the above code would be acceptable. We don't
need to wrap symbol == '~'
in a method, after all. You also shouldn't
go overboard on doing this sort of thing – new semantic name notwithstanding, this is
still a trivial method.