Review and Refactor
By the end of this lesson, you'll be able to:
- Read a finished class critically for opportunities to simplify
- Apply a small refactor without changing external behavior
- Explain why review-and-refactor is a normal, expected step, not a sign something was done wrong
Prerequisites: "Handle Errors Gracefully"
The finished class, before refactoring
public class CourseRoster {
private String courseName;
private Integer maxSize;
private List<Student> enrolledStudents = new List<Student>();
public CourseRoster(String courseName, Integer maxSize) {
this.courseName = courseName;
this.maxSize = maxSize;
}
public Boolean enroll(Student student) {
if (student == null) {
return false;
}
for (Student existing : enrolledStudents) {
if (existing.getStudentId() == student.getStudentId()) {
return false;
}
}
if (enrolledStudents.size() >= maxSize) {
return false;
}
enrolledStudents.add(student);
return true;
}
public Integer getRemainingSeats() {
return maxSize - enrolledStudents.size();
}
}
public class CourseRoster {— the class, fields, and constructor — unchanged from the last two lessons. 2-9. Fields and constructor — same as before. 10-25.public Boolean enroll(Student student) { ... }— the exact same logic from "Add Business Rules" and "Handle Errors Gracefully," combined: a null check, a duplicate check, and a capacity check, all inline in one method body. 26-29.public Integer getRemainingSeats() { ... }— unchanged.}— closes the class.
This works correctly — but enroll() is doing three separate jobs (null-check, duplicate-check, capacity-check) inline. That's a candidate for refactoring.
Refactoring: extract a private helper method
public class CourseRoster {
private String courseName;
private Integer maxSize;
private List<Student> enrolledStudents = new List<Student>();
public CourseRoster(String courseName, Integer maxSize) {
this.courseName = courseName;
this.maxSize = maxSize;
}
public Boolean enroll(Student student) {
if (student == null || isAlreadyEnrolled(student) || isFull()) {
return false;
}
enrolledStudents.add(student);
return true;
}
private Boolean isAlreadyEnrolled(Student student) {
for (Student existing : enrolledStudents) {
if (existing.getStudentId() == student.getStudentId()) {
return true;
}
}
return false;
}
private Boolean isFull() {
return enrolledStudents.size() >= maxSize;
}
public Integer getRemainingSeats() {
return maxSize - enrolledStudents.size();
}
}
public class CourseRoster {— the class.private String courseName;— unchanged.private Integer maxSize;— unchanged.private List<Student> enrolledStudents = new List<Student>();— unchanged.- (blank line)
public CourseRoster(String courseName, Integer maxSize) {— unchanged.this.courseName = courseName;— unchanged.this.maxSize = maxSize;— unchanged.}— closes the constructor.- (blank line)
public Boolean enroll(Student student) {— now reads almost like a sentence.if (student == null || isAlreadyEnrolled(student) || isFull()) {— reject if null, already enrolled, or full — each condition delegated to a named method instead of written out inline.return false;— rejects.}— closes theif.enrolledStudents.add(student);— otherwise, add.return true;— reports success.}— closesenroll().- (blank line)
private Boolean isAlreadyEnrolled(Student student) {—private— an internal implementation detail, not part of the class's public contract.for (Student existing : enrolledStudents) {— the same loop as before, now living in its own named method.if (existing.getStudentId() == student.getStudentId()) {— same check.return true;— signals a match.}— closes theif.}— closes the loop.return false;— no match found.}— closes the method.- (blank line)
private Boolean isFull() {— anotherprivateinternal detail.return enrolledStudents.size() >= maxSize;— the same capacity check, now named.}— closes the method.- (blank line)
public Integer getRemainingSeats() {— unchanged, stillpublic.return maxSize - enrolledStudents.size();— unchanged.}— closes the method.}— closes the class.
External behavior hasn't changed at all — every existing call to enroll() behaves identically.
Why this step matters
Getting something working first, then reviewing it with fresh eyes, is the normal shape of real development — not a sign the first version was wrong. A method with three unrelated checks jammed together isn't a bug, but it is harder to read, test, and extend than three named, focused pieces. Refactoring after it works, rather than trying to write the "perfect" version first, keeps each step small and verifiable.
Exercise
Refactor this method into two private helper methods, hasValidName and hasValidEmail, called from a single validate() method.
Show hint
Extract each condition into its own aptly-named private method.
Review and Refactor Quiz
My Notes
Log in to keep private notes on this lesson.
Questions about this lesson
No questions yet — be the first to ask.
Log in to ask a question about this lesson.
Summary
The final lesson steps back and reviews the whole CourseRoster class built across this module, tightening it up without changing how it behaves from the outside.