TUTORIAL 07

Refactor Technical Debt

The UserService has grown to 500 lines with 8 responsibilities. Time to clean it up.

โฑ ~20 min ยท Hands-on

/agile-explore-audit /agile-story-create /agile-code-branch /agile-code-refactor /agile-code-ci /agile-code-commit /agile-code-review /agile-code-merge
โš ๏ธ The Situation

UserService.java started as a clean, 80-line class. Over 6 sprints, every user-related feature got bolted onto it. Now it's 500 lines, has 8 distinct responsibilities, and every change risks breaking something else. The team dreads touching it.

Step 1: Audit the Code

Before refactoring, quantify the problem. Run /agile-explore-audit to get hard numbers.

/agile-explore-audit scope="code" target="UserService"
Code Audit โ€” UserService.java
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

๐Ÿ“ Size
   Lines of code: 500
   Methods: 23
   Dependencies injected: 7

๐ŸŽฏ Single Responsibility (SRP) โ€” VIOLATION
   Detected 8 responsibility clusters:
   1. User CRUD (create, read, update, delete)
   2. Password management (hash, reset, validate)
   3. Email validation (format, uniqueness, domain)
   4. Permission checks (hasRole, canAccess, isAdmin)
   5. Notification dispatch (welcome email, password reset email)
   6. Profile image handling (upload, resize, delete)
   7. Audit logging (logAction, getHistory)
   8. Session management (createSession, invalidate)

๐Ÿ“Š Complexity
   Cyclomatic complexity: 24 (threshold: 10)
   Deepest nesting: 5 levels
   Longest method: 67 lines (updateUserProfile)

๐Ÿ” Duplication
   3 duplicated blocks detected:
   - Email validation logic (lines 89-102, 234-247, 401-414)

๐Ÿงช Test Coverage
   Coverage: 62% (threshold: 80%)
   Uncovered: notification dispatch, session management

โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
Risk: HIGH โ€” Refactoring recommended before adding features

Step 2: Create Refactoring Stories

Refactoring is real work. It goes into the sprint backlog as stories with estimates.

/agile-story-create

Stories Created

StoryPointsPriority
REFAC-01: Split UserService into focused services5High
REFAC-02: Remove duplicated email validation3Medium
REFAC-03: Increase test coverage to 80%+3Medium

Total: 11 story points of tech debt work. This sprint, we tackle REFAC-01.

Step 3: Create the Refactoring Branch

Create a feature branch for the refactoring work using /agile-code-branch.

/agile-code-branch
โ†’ Created branch: refactor/REFAC-01-split-user-service from develop

Step 4: Refactor with Tidy First

Run /agile-code-refactor and follow the Tidy First approach: one small structural transformation at a time, each followed by a commit and a green test suite.

/agile-code-refactor target="UserService" approach="tidy-first"
Critical Rule

NEVER mix structural and behavioral changes in one commit. A refactoring commit changes how code is organized, not what it does. If tests change behavior, that's a separate commit.

Transformation 1: Extract UserValidator

Move all validation methods out of UserService into a dedicated class.

โŒ Before โ€” UserService.java (500 lines)
class UserService {
  // ... 23 methods, 8 responsibilities
  void validateEmail(String email) { ... }
  void validatePassword(String pw) { ... }
  void validateUsername(String name) { ... }
  // ... everything else
}
โœ… After โ€” Extracted
class UserValidator {
  void validateEmail(String email) { ... }
  void validatePassword(String pw) { ... }
  void validateUsername(String name) { ... }
}

class UserService {
  private final UserValidator validator;
  // ... remaining methods
}
/agile-code-ci
Unit Tests ........... โœ… 247/247 passing
Integration Tests .... โœ… 18/18 passing
All tests GREEN โœ…
/agile-code-commit
refactor(user): extract UserValidator from UserService

Transformation 2: Extract UserNotificationService

Move notification dispatch methods (welcome email, password reset email) into their own service.

class UserNotificationService {
  void sendWelcomeEmail(User user) { ... }
  void sendPasswordResetEmail(User user, String token) { ... }
  void sendAccountLockedEmail(User user) { ... }
}

class UserService {
  private final UserValidator validator;
  private final UserNotificationService notifications;
  // ... remaining methods
}
/agile-code-ci
Unit Tests ........... โœ… 247/247 passing
Integration Tests .... โœ… 18/18 passing
All tests GREEN โœ…
/agile-code-commit
refactor(user): extract UserNotificationService from UserService

Transformation 3: Extract UserPermissionService

Move permission and role-checking methods into a dedicated service.

class UserPermissionService {
  boolean hasRole(User user, Role role) { ... }
  boolean canAccess(User user, Resource resource) { ... }
  boolean isAdmin(User user) { ... }
}

class UserService {
  private final UserValidator validator;
  private final UserNotificationService notifications;
  private final UserPermissionService permissions;
  // ... now only CRUD + password + profile + audit + session
}
/agile-code-ci
Unit Tests ........... โœ… 247/247 passing
Integration Tests .... โœ… 18/18 passing
All tests GREEN โœ…
/agile-code-commit
refactor(user): extract UserPermissionService from UserService

Step 5: CI After EACH Transformation

โœ… The Golden Rule

After every single extraction, run /agile-code-ci. If tests break, you know exactly which transformation caused it โ€” the one you just did. This is why we commit after each step, not at the end.

๐Ÿ”ง
Extract one responsibility
โš™๏ธ
/agile-code-ci tests green?
๐Ÿ’พ
/agile-code-commit structural only
๐Ÿ”
Repeat next responsibility

Step 6: Commit Convention

Every refactoring commit uses the refactor type and describes the structural change. No behavioral changes allowed.

Commit History (3 clean commits)

abc1234 refactor(user): extract UserValidator from UserService
def5678 refactor(user): extract UserNotificationService from UserService
ghi9012 refactor(user): extract UserPermissionService from UserService

Each commit is independently revertible. If any extraction causes problems downstream, you can revert just that one.

Step 7: Code Review

Submit the refactoring PR for review.

/agile-code-review
Review โ€” refactor(user): split UserService into focused services
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

Reviewer: @lead
Checklist:
  โœ… No behavioral changes (same inputs โ†’ same outputs)
  โœ… SOLID principles improved (SRP compliance)
  โœ… All 247 tests pass โ€” no test modifications needed
  โœ… Each commit is atomic and independently revertible
  โœ… Dependency injection wired correctly

Comment: "Clean extraction. UserService is now focused on CRUD.
  Consider extracting session management next sprint."

Verdict: โœ… APPROVED

Step 8: Merge the Refactoring Branch

After approval, merge the refactoring branch back to develop using /agile-code-merge.

/agile-code-merge
Merge โ€” refactor/REFAC-01-split-user-service โ†’ develop
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
Strategy: --no-ff (preserves branch history)
Result .................. โœ… merged to develop
Cleanup ................. โœ… branch deleted
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
โœ… Refactoring branch merged and cleaned up

Results: Before vs. After

MetricBeforeAfterChange
Lines (UserService)500120-76%
Responsibilities83-62%
Cyclomatic Complexity248-67%
Methods (UserService)238-65%
Test Coverage62%85%+23%
Deepest Nesting52-60%

Three new services (UserValidator, UserNotificationService, UserPermissionService) each with a single responsibility, full test coverage, and clear boundaries.

Remaining Work

UserService still has 3 responsibilities (CRUD, password management, profile images). Stories REFAC-02 and REFAC-03 are in the backlog for the next sprint. Refactoring is incremental โ€” you don't have to fix everything at once.

The Tidy First Principle

What You Practiced

Knowledge Check

What must be true after EVERY refactoring transformation?