Untangling Java, Part 2: How to Refactor Spaghetti Code (Step by Step)
TL;DR:
If your main() method is doing all the work, you’re one feature away from chaos. In this beginner-friendly walkthrough, we break a messy Java login script into reusable, easy-to-read pieces—no frameworks or OOP required (yet).
🍝 Quick Recap from Part 1
In Part 1, we met a working Java login app that was tangled, repetitive, and hard to grow.
Here’s the spaghetti:
// SpaghettiLogin.java
public class SpaghettiLogin {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String user, pass;
System.out.println("Enter username:");
user = scanner.nextLine();
if (user.equals("admin")) {
System.out.println("Enter password:");
pass = scanner.nextLine();
if (pass.equals("1234")) {
System.out.println("Access granted.");
System.out.println("Enter new username:");
user = scanner.nextLine();
if (!user.isEmpty()) {
System.out.println("Username updated to: " + user);
} else {
System.out.println("Username can't be empty.");
}
} else {
System.out.println("Wrong password.");
}
} else {
System.out.println("User not found.");
}
}
}
It runs. But it’s:
- Hard to test
- Annoying to modify
- A nightmare to debug once it grows
Today, we clean it up.
🧼 The Refactoring Plan
You don’t need frameworks, advanced Java, or design patterns to write cleaner code. We’ll start with a few small wins:
What we’re aiming for:
- Smaller methods
- Meaningful names
- Less nesting
- Logic that’s easier to reuse, test, and read
🪓 Step-by-Step Refactor
🔹 Step 1: Extract Input & Output
We’re repeating System.out.println() and scanner.nextLine() all over the place. Let’s fix that.
public static String getInput(Scanner scanner, String prompt) {
System.out.println(prompt);
return scanner.nextLine();
}
public static void print(String message) {
System.out.println(message);
}
Why it matters:
Cleaner code. Easier to change later. Want to log to a file or sanitize input? Now you only change it once.
🔹 Step 2: Handle Login Logic Separately
We’ll move the username and password checks into tiny, purpose-driven methods:
public static boolean isValidUser(String username) {
return username.equals("admin");
}
public static boolean isValidPassword(String password) {
return password.equals("1234");
}
Why it matters:
Easy to test. Clear to read. If you want to switch to database validation later, you only update one place.
🔹 Step 3: Make Username Update Its Own Thing
This chunk was buried deep in the main() method before. Now, it’s isolated:
public static void updateUsername(Scanner scanner) {
String newUsername = getInput(scanner, "Enter new username:");
if (!newUsername.isEmpty()) {
print("Username updated to: " + newUsername);
} else {
print("Username can't be empty.");
}
}
Why it matters:
Logic is reusable and testable. It also avoids overwriting the login username by mistake.
💻 The Clean Version: CleanLogin.java
Here’s the cleaned-up, fully refactored version:
import java.util.Scanner;
public class CleanLogin {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Clear and readable — first action: prompt for username
String username = getInput(scanner, "Enter username:");
// Separated logic — checks are done using clean helper methods
if (isValidUser(username)) {
String password = getInput(scanner, "Enter password:");
if (isValidPassword(password)) {
print("Access granted.");
// Update logic is isolated — no more deep nesting
updateUsername(scanner);
} else {
print("Wrong password.");
}
} else {
print("User not found.");
}
scanner.close();
}
// 🧹 Extracted method: handles prompting and reading input
// No repeated Scanner or println logic inside main()
public static String getInput(Scanner scanner, String prompt) {
System.out.println(prompt);
return scanner.nextLine();
}
// 🧹 Extracted method: handles printing
// If we wanted to log instead of print, change it here once
public static void print(String message) {
System.out.println(message);
}
// 🧠 Separated validation logic
// No hardcoded checks buried in if-else blocks anymore
public static boolean isValidUser(String username) {
return username.equals("admin");
}
// 🧠 Also testable and self-explanatory — follows same structure as above
public static boolean isValidPassword(String password) {
return password.equals("1234");
}
// ✨ All logic related to username update is now encapsulated here
// Clear responsibilities = no spaghetti
public static void updateUsername(Scanner scanner) {
String newUsername = getInput(scanner, "Enter new username:");
if (!newUsername.isEmpty()) {
print("Username updated to: " + newUsername);
} else {
print("Username can't be empty.");
}
}
}
🔍 What Improved?
Let’s compare:
https://medium.com/media/be127b40bb7364859e2286079d95bbe1/href
💡 What You Learn Here
- You don’t need OOP or frameworks to write cleaner Java.
- Breaking code into smaller, named methods = clarity.
- Your code is for humans first, machines second.
🔜 Coming Up in Part 3: Java Meets OOP
Next time, we’ll introduce a basic User class, and see how even a little object-oriented structure can help manage state, encapsulate logic, and future-proof your code.
We’ll walk through:
- Creating a User class
- Moving validation into it
- Connecting it with the logic we just refactored
🏁 Final Thought
Bad code runs. Good code survives change.
Clean code doesn’t mean writing fewer bugs — it means writing code that’s easier to fix, explain, and grow. That’s how you level up, one refactor at a time.
Untangling Java, Part 2: How to Refactor Spaghetti Code (Step by Step) was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.
This post first appeared on Read More