The UserService has grown to 500 lines with 8 responsibilities. Time to clean it up.
โฑ ~20 min ยท Hands-on
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.
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
Refactoring is real work. It goes into the sprint backlog as stories with estimates.
/agile-story-create
| Story | Points | Priority |
|---|---|---|
| REFAC-01: Split UserService into focused services | 5 | High |
| REFAC-02: Remove duplicated email validation | 3 | Medium |
| REFAC-03: Increase test coverage to 80%+ | 3 | Medium |
Total: 11 story points of tech debt work. This sprint, we tackle REFAC-01.
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
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"
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.
Move all validation methods out of UserService into a dedicated class.
class UserService {
// ... 23 methods, 8 responsibilities
void validateEmail(String email) { ... }
void validatePassword(String pw) { ... }
void validateUsername(String name) { ... }
// ... everything else
}
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
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
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
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.
Every refactoring commit uses the refactor type and describes the structural change. No behavioral changes allowed.
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.
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
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
| Metric | Before | After | Change |
|---|---|---|---|
| Lines (UserService) | 500 | 120 | -76% |
| Responsibilities | 8 | 3 | -62% |
| Cyclomatic Complexity | 24 | 8 | -67% |
| Methods (UserService) | 23 | 8 | -65% |
| Test Coverage | 62% | 85% | +23% |
| Deepest Nesting | 5 | 2 | -60% |
Three new services (UserValidator, UserNotificationService, UserPermissionService) each with a single responsibility, full test coverage, and clear boundaries.
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.
/agile-explore-audit to quantify tech debt with hard numbers/agile-story-create to track refactoring as real sprint work/agile-code-branch to create a dedicated refactoring branch/agile-code-refactor with Tidy First approach โ one extraction at a time/agile-code-ci after EVERY transformation to catch breaks immediately/agile-code-commit with structural-only commits (refactor(scope): description)/agile-code-review verifying no behavioral changes leaked in/agile-code-merge to merge the approved refactoring back to developWhat must be true after EVERY refactoring transformation?