Spring Service Refactoring
Overview
Refactor Java/Spring backend code conservatively. Keep external behavior stable, make entry methods read like clean process orchestration, and move detail work into smaller units with clear responsibilities.
Before reading a large Java file in full, run scripts/review_java_file.py <file> [--method name] to generate a compact structural summary. Use that artifact first, then return to the source file only for the lines that actually need judgment.
Before reviewing a wide Java diff, run scripts/review_git_diff.py --repo <repo> to compress changed files into a smaller review artifact and focus follow-up reading on the risky hunks.
Workflow Decision
digraph refactor_flow {
rankdir=LR;
start [label="Large or unclear method?", shape=diamond];
apply [label="Use this skill", shape=box];
contract [label="Need to change external input/output,\npublic signature, or exception contract?", shape=diamond];
ask [label="Stop and declare the change first", shape=box];
safe [label="Proceed with internal refactoring", shape=box];
review [label="Run post-refactor review", shape=box];
start -> apply [label="yes"];
apply -> contract;
contract -> ask [label="yes"];
contract -> safe [label="no"];
safe -> review;
}
Core Rules
- Treat controller APIs, external DTOs, public service signatures, return structures, and observable exception semantics as stable by default.
- If a good refactor requires changing external input or output, stop and declare that explicitly before making the change.
- Keep entry methods focused on process orchestration, not low-level details.
- Enforce clear naming as a hard rule. Method, class, context, and variable names must express responsibility and business meaning directly.
- Enforce single responsibility as a hard rule. If a method or helper spans multiple workflow stages without a strong reason, keep refactoring.
- Add comments deliberately. The refactored code must contain comments that explain stage boundaries, non-obvious business intent, or why a sequence matters.
- Treat resource lifecycle as part of behavior. Lock release, thread-pool shutdown, stream or client close, and temporary context cleanup must remain explicit and verifiable after refactoring.
- Release a resource only when acquisition or initialization is known to have succeeded. Do not keep or introduce unconditional cleanup that can run on an unacquired lock, unopened resource, or uninitialized executor.
- Prefer small, local, reversible changes over broad rewrites.
- Refactor in this order: split process stages and boundaries, remove real duplication, optimize performance last.
Entry Method Pattern
Shape each entry method into clean stages whenever the code allows:
- Condition validation
- Parameter construction
- Business processing
- Post-processing
Interpret the stages like this:
Condition validation: argument checks, state checks, permission checks, idempotency checks, business preconditions.
Parameter construction: build query conditions, domain objects, command objects, DTO conversions, downstream call arguments.
Business processing: query, decide, write, update relations, call collaborators, enforce transaction semantics.
Post-processing: assemble response, trigger async logging, send notifications, publish events, cleanup.
The entry method should read like a workflow. Heavy branching, data reshaping, persistence detail, and formatting logic should usually move into private methods or focused collaborators.
For stateful service methods that involve locks, Redis, database writes, or auto-triggered side effects, interpret the stages more strictly:
Condition validation: caller identity, input count, task existence, feature switches, lock acquisition preconditions.
Parameter construction: cache keys, context objects, filtered task lists, derived counters, transition inputs.
Business processing: load current state, validate transition legality, update cache or DB state, execute the core state change.
Post-processing: reward claiming, async notifications, cache invalidation, expiration refresh, response assembly, lock-safe cleanup.
Do not let one helper mix state loading, transition decision, persistence, and post-side-effect dispatch unless the sequence is trivial.
Refactoring Order
1. Split the flow first
When one method mixes validation, object assembly, repository access, writes, and response building, extract stage methods before inventing abstractions.
Good direction:
public Result submit(SubmitCommand command) {
validateSubmit(command);
SubmitContext context = buildSubmitContext(command);
SubmitResult result = executeSubmit(context);
return buildSubmitResponse(result);
}
2. Clarify responsibilities
Look for responsibilities that should not live in the same method or class:
- validation vs business decision
- object conversion vs domain processing
- repository orchestration vs response assembly
- synchronous core flow vs async side effects
If a responsibility has its own vocabulary and rules, give it a named method or a small collaborator.
Clear names are part of the design, not polish:
- prefer
validateTaskRequest, buildTaskContext, executeRewardClaim, afterTaskClaim
- reject
handle, process, common, helper, util
- reject local names like
data, obj, result, tmp when a business name is available
3. Remove real duplication
Only extract shared helpers when duplication is stable across multiple call sites. Do not create common abstractions just because two code blocks look similar once.
4. Optimize last
Consider performance changes only after the flow is readable, or when the user explicitly asks for optimization. Prioritize obvious backend issues:
- repeated queries in one request path
- redundant DTO conversions
- repeated collection scans
- unnecessary remote calls
Do not change transaction timing or side-effect order just to make the code look cleaner.
Practical Moves
- Run
scripts/review_java_file.py first on large files to compress method structure, side effects, and obvious cleanup risks before deeper analysis.
- Run
scripts/review_git_diff.py first on large Java diffs to identify contract drift, cleanup regressions, and high-risk changes before manual review.
- Extract
validateXxx, buildXxx, executeXxx, afterXxx, toXxxResponse style methods when they reflect real stages.
- Rename ambiguous methods and variables as part of the refactor. Do not keep vague names just to minimize diff size.
- Add a short comment before each major stage in an entry method when the workflow is not obvious at a glance.
- Add comments for business rules, transaction-sensitive ordering, and side effects that would be easy to misunderstand from code alone.
- Introduce a transition context object for stateful workflows that pass lock state, cache state, counters, or filtered task sets through multiple steps.
- Introduce a small context object when many local variables are passed through the same workflow.
- Move reusable validation or assembly logic down into dedicated helpers or services only after it is proven to repeat.
- Separate "query then decide then write" into explicit steps so state transitions are easy to review.
- Isolate lock handling so acquisition, guarded execution, and safe release are easy to verify by inspection.
- Keep resource acquisition and release paired in a structure that is obvious on read, such as
try/finally, try-with-resources, or a small dedicated wrapper with explicit cleanup.
- Guard cleanup with the real acquisition result when required. For example, only
unlock after a successful tryLock, and only shutdown resources that were actually created.
- Isolate cache expiration and invalidation rules from the main state transition when they are policy, not core business logic.
- Pull async logging, notifications, and audit writes out of the core happy path when behavior stays equivalent.
- Preserve transaction boundaries unless the user approves a change.
Comment Template
Use concise comments that explain stage intent, not line-by-line syntax.
public Result submit(SubmitCommand command) {
// 1. Validate request and business preconditions before any state change.
validateSubmit(command);
// 2. Build query and command context used by the main workflow.
SubmitContext context = buildSubmitContext(command);
// 3. Execute the core flow: query current state, decide, then persist updates.
SubmitResult result = executeSubmit(context);
// 4. Assemble the response and trigger post-processing side effects.
return buildSubmitResponse(result);
}
Add extra comments when needed for:
- why a query must happen before a write
- why a side effect stays synchronous or can move async
- why a transaction boundary must stay where it is
- why a lock, cache invalidation, or expiration update must happen at a specific point
- why a resource must be released in
finally, shutdown hook, or equivalent cleanup path
- why cleanup is conditional on successful acquisition or initialization
- why a duplicated block is intentionally not abstracted yet
Stateful Workflow Pattern
Use a stricter orchestration style for service methods that mix lock control, cache state, persistence, and automatic reward or side-effect triggers.
public void completeTask(TaskCommand command) {
// 1. Validate request and confirm the method is allowed to proceed.
validateTaskCommand(command);
// 2. Build workflow context, including lock key and current user task state.
TaskWorkflowContext context = buildTaskWorkflowContext(command);
// 3. Execute the state transition under lock and persist the new state.
executeTaskTransition(context);
// 4. Trigger follow-up actions such as reward claiming or cache refresh.
afterTaskTransition(context);
}
When using this pattern:
- Keep lock acquisition and release visible.
- Keep resource cleanup visible and paired with acquisition.
- Keep conditional cleanup visible when release depends on
acquired, initialized, or similar flags.
- Keep state transition rules together.
- Keep post-actions separate from the transition itself.
- Keep cache TTL refresh and popup or tips logic out of the middle of transition code when possible.
Resource Cleanup Pattern
When refactoring Java service methods, make cleanup correctness obvious in code shape.
Good:
boolean acquired = false;
RLock lock = redissonClient.getLock(lockKey);
try {
acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!acquired) {
return;
}
executeLockedWorkflow();
} finally {
if (acquired) {
lock.unlock();
}
}
Bad:
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (acquired) {
executeLockedWorkflow();
}
} finally {
lock.unlock();
}
Use the same principle for thread pools, streams, temporary auth context, and client resources: cleanup must match actual acquisition or initialization.
Post-Refactor Review
Do not stop after the code compiles. Review the changed entry method and the diff as a separate gate.
Review in this order:
- Re-read the entry method top-down and confirm it now reads as workflow orchestration.
- Check that each extracted method belongs to one stage: validation, parameter construction, business processing, or post-processing.
- Confirm no extracted helper still mixes query, write, response assembly, and async side effects without a good reason.
- Confirm method and variable names now make responsibility obvious without reading internals line by line.
- Review the diff for contract drift: public signatures, DTO fields, return shape, exception behavior, transaction timing, and side-effect order.
- Review lock handling, state transition ordering, cache invalidation, and expiration updates for hidden behavior drift.
- Review resource cleanup paths: lock release, thread-pool shutdown, context restore, stream close, and client cleanup must still happen on success and failure paths.
- Confirm cleanup is conditional where necessary: no unconditional
unlock, close, or shutdown on resources that may not have been acquired.
- Reject vague abstractions added only for reuse appearance, especially utility classes or helpers with weak names.
If the review finds a mixed-stage helper or hidden behavior change, refactor again before claiming the work is done.
Stop Conditions
Stop and surface the issue before editing when any of these are true:
- external request or response contract must change
- public method signature must change
- exception semantics visible to callers must change
- transaction boundary or side-effect timing is unclear
- a proposed abstraction is shared by only one unstable use case
Common Mistakes
- Hiding a messy method inside one new helper instead of splitting the workflow stages.
- Keeping a vague name after refactoring, such as
handle, process, data, or result, when the code now has a clear responsibility.
- Extracting shared utilities too early and creating vague
CommonUtil style code.
- Mixing response assembly back into business processing after the method was split.
- Leaving the refactored flow without comments, forcing readers to reverse-engineer stage intent from implementation details.
- Leaving lock release, cache invalidation, or TTL refresh buried in the middle of business logic so the true state transition is hard to verify.
- Splitting code in a way that hides or weakens cleanup guarantees, such as moving
unlock, shutdown, or context restore out of an obvious finally path.
- Releasing a lock, thread pool, stream, or context unconditionally even though acquisition or initialization may have failed.
- Quietly changing return data, nullability, or thrown exceptions during "refactor only" work.
- Moving logging or event publishing without checking whether callers rely on timing.
Verification
- Run the post-refactor review before finalizing the change.
- Read the entry method top-down and confirm each line represents one stage of the workflow.
- Check that validation, parameter construction, processing, and post-processing are visibly separated.
- Confirm comments exist for major stages and for any non-obvious rule, ordering dependency, or side effect.
- Confirm lock acquisition and release, cache mutation, persistence, and post-actions remain in a defensible order.
- Confirm resource release remains correct on all paths: success, early return, exception, and retry or timeout paths.
- Confirm conditional cleanup still matches reality: release only what was actually acquired or initialized.
- Confirm public signatures, DTO fields, return shape, and externally visible exceptions are unchanged.
- Run targeted tests for the touched path. Add tests first when behavior is not already protected.
- Use
references/checklist.md when the refactor spans multiple methods or classes.
1---2name: spring-refactor3description: Use when refactoring Java or Spring backend files with oversized methods, mixed responsibilities, tangled validation/build/query/write/post-processing logic, or unclear service boundaries, especially when external request and response contracts must remain stable unless a change is declared first.4---56# Spring Service Refactoring78## Overview910Refactor Java/Spring backend code conservatively. Keep external behavior stable, make entry methods read like clean process orchestration, and move detail work into smaller units with clear responsibilities.1112Before reading a large Java file in full, run `scripts/review_java_file.py <file> [--method name]` to generate a compact structural summary. Use that artifact first, then return to the source file only for the lines that actually need judgment.1314Before reviewing a wide Java diff, run `scripts/review_git_diff.py --repo <repo>` to compress changed files into a smaller review artifact and focus follow-up reading on the risky hunks.1516## Workflow Decision1718```dot19digraph refactor_flow {20 rankdir=LR;21 start [label="Large or unclear method?", shape=diamond];22 apply [label="Use this skill", shape=box];23 contract [label="Need to change external input/output,\npublic signature, or exception contract?", shape=diamond];24 ask [label="Stop and declare the change first", shape=box];25 safe [label="Proceed with internal refactoring", shape=box];26 review [label="Run post-refactor review", shape=box];2728 start -> apply [label="yes"];29 apply -> contract;30 contract -> ask [label="yes"];31 contract -> safe [label="no"];32 safe -> review;33}34```3536## Core Rules3738- Treat controller APIs, external DTOs, public service signatures, return structures, and observable exception semantics as stable by default.39- If a good refactor requires changing external input or output, stop and declare that explicitly before making the change.40- Keep entry methods focused on process orchestration, not low-level details.41- Enforce clear naming as a hard rule. Method, class, context, and variable names must express responsibility and business meaning directly.42- Enforce single responsibility as a hard rule. If a method or helper spans multiple workflow stages without a strong reason, keep refactoring.43- Add comments deliberately. The refactored code must contain comments that explain stage boundaries, non-obvious business intent, or why a sequence matters.44- Treat resource lifecycle as part of behavior. Lock release, thread-pool shutdown, stream or client close, and temporary context cleanup must remain explicit and verifiable after refactoring.45- Release a resource only when acquisition or initialization is known to have succeeded. Do not keep or introduce unconditional cleanup that can run on an unacquired lock, unopened resource, or uninitialized executor.46- Prefer small, local, reversible changes over broad rewrites.47- Refactor in this order: split process stages and boundaries, remove real duplication, optimize performance last.4849## Entry Method Pattern5051Shape each entry method into clean stages whenever the code allows:52531. Condition validation542. Parameter construction553. Business processing564. Post-processing5758Interpret the stages like this:5960- `Condition validation`: argument checks, state checks, permission checks, idempotency checks, business preconditions.61- `Parameter construction`: build query conditions, domain objects, command objects, DTO conversions, downstream call arguments.62- `Business processing`: query, decide, write, update relations, call collaborators, enforce transaction semantics.63- `Post-processing`: assemble response, trigger async logging, send notifications, publish events, cleanup.6465The entry method should read like a workflow. Heavy branching, data reshaping, persistence detail, and formatting logic should usually move into private methods or focused collaborators.6667For stateful service methods that involve locks, Redis, database writes, or auto-triggered side effects, interpret the stages more strictly:6869- `Condition validation`: caller identity, input count, task existence, feature switches, lock acquisition preconditions.70- `Parameter construction`: cache keys, context objects, filtered task lists, derived counters, transition inputs.71- `Business processing`: load current state, validate transition legality, update cache or DB state, execute the core state change.72- `Post-processing`: reward claiming, async notifications, cache invalidation, expiration refresh, response assembly, lock-safe cleanup.7374Do not let one helper mix state loading, transition decision, persistence, and post-side-effect dispatch unless the sequence is trivial.7576## Refactoring Order7778### 1. Split the flow first7980When one method mixes validation, object assembly, repository access, writes, and response building, extract stage methods before inventing abstractions.8182Good direction:8384```java85public Result submit(SubmitCommand command) {86 validateSubmit(command);87 SubmitContext context = buildSubmitContext(command);88 SubmitResult result = executeSubmit(context);89 return buildSubmitResponse(result);90}91```9293### 2. Clarify responsibilities9495Look for responsibilities that should not live in the same method or class:9697- validation vs business decision98- object conversion vs domain processing99- repository orchestration vs response assembly100- synchronous core flow vs async side effects101102If a responsibility has its own vocabulary and rules, give it a named method or a small collaborator.103104Clear names are part of the design, not polish:105106- prefer `validateTaskRequest`, `buildTaskContext`, `executeRewardClaim`, `afterTaskClaim`107- reject `handle`, `process`, `common`, `helper`, `util`108- reject local names like `data`, `obj`, `result`, `tmp` when a business name is available109110### 3. Remove real duplication111112Only extract shared helpers when duplication is stable across multiple call sites. Do not create common abstractions just because two code blocks look similar once.113114### 4. Optimize last115116Consider performance changes only after the flow is readable, or when the user explicitly asks for optimization. Prioritize obvious backend issues:117118- repeated queries in one request path119- redundant DTO conversions120- repeated collection scans121- unnecessary remote calls122123Do not change transaction timing or side-effect order just to make the code look cleaner.124125## Practical Moves126127- Run `scripts/review_java_file.py` first on large files to compress method structure, side effects, and obvious cleanup risks before deeper analysis.128- Run `scripts/review_git_diff.py` first on large Java diffs to identify contract drift, cleanup regressions, and high-risk changes before manual review.129- Extract `validateXxx`, `buildXxx`, `executeXxx`, `afterXxx`, `toXxxResponse` style methods when they reflect real stages.130- Rename ambiguous methods and variables as part of the refactor. Do not keep vague names just to minimize diff size.131- Add a short comment before each major stage in an entry method when the workflow is not obvious at a glance.132- Add comments for business rules, transaction-sensitive ordering, and side effects that would be easy to misunderstand from code alone.133- Introduce a transition context object for stateful workflows that pass lock state, cache state, counters, or filtered task sets through multiple steps.134- Introduce a small context object when many local variables are passed through the same workflow.135- Move reusable validation or assembly logic down into dedicated helpers or services only after it is proven to repeat.136- Separate "query then decide then write" into explicit steps so state transitions are easy to review.137- Isolate lock handling so acquisition, guarded execution, and safe release are easy to verify by inspection.138- Keep resource acquisition and release paired in a structure that is obvious on read, such as `try/finally`, `try-with-resources`, or a small dedicated wrapper with explicit cleanup.139- Guard cleanup with the real acquisition result when required. For example, only `unlock` after a successful `tryLock`, and only `shutdown` resources that were actually created.140- Isolate cache expiration and invalidation rules from the main state transition when they are policy, not core business logic.141- Pull async logging, notifications, and audit writes out of the core happy path when behavior stays equivalent.142- Preserve transaction boundaries unless the user approves a change.143144## Comment Template145146Use concise comments that explain stage intent, not line-by-line syntax.147148```java149public Result submit(SubmitCommand command) {150 // 1. Validate request and business preconditions before any state change.151 validateSubmit(command);152153 // 2. Build query and command context used by the main workflow.154 SubmitContext context = buildSubmitContext(command);155156 // 3. Execute the core flow: query current state, decide, then persist updates.157 SubmitResult result = executeSubmit(context);158159 // 4. Assemble the response and trigger post-processing side effects.160 return buildSubmitResponse(result);161}162```163164Add extra comments when needed for:165166- why a query must happen before a write167- why a side effect stays synchronous or can move async168- why a transaction boundary must stay where it is169- why a lock, cache invalidation, or expiration update must happen at a specific point170- why a resource must be released in `finally`, shutdown hook, or equivalent cleanup path171- why cleanup is conditional on successful acquisition or initialization172- why a duplicated block is intentionally not abstracted yet173174## Stateful Workflow Pattern175176Use a stricter orchestration style for service methods that mix lock control, cache state, persistence, and automatic reward or side-effect triggers.177178```java179public void completeTask(TaskCommand command) {180 // 1. Validate request and confirm the method is allowed to proceed.181 validateTaskCommand(command);182183 // 2. Build workflow context, including lock key and current user task state.184 TaskWorkflowContext context = buildTaskWorkflowContext(command);185186 // 3. Execute the state transition under lock and persist the new state.187 executeTaskTransition(context);188189 // 4. Trigger follow-up actions such as reward claiming or cache refresh.190 afterTaskTransition(context);191}192```193194When using this pattern:195196- Keep lock acquisition and release visible.197- Keep resource cleanup visible and paired with acquisition.198- Keep conditional cleanup visible when release depends on `acquired`, `initialized`, or similar flags.199- Keep state transition rules together.200- Keep post-actions separate from the transition itself.201- Keep cache TTL refresh and popup or tips logic out of the middle of transition code when possible.202203## Resource Cleanup Pattern204205When refactoring Java service methods, make cleanup correctness obvious in code shape.206207Good:208209```java210boolean acquired = false;211RLock lock = redissonClient.getLock(lockKey);212try {213 acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);214 if (!acquired) {215 return;216 }217218 executeLockedWorkflow();219} finally {220 if (acquired) {221 lock.unlock();222 }223}224```225226Bad:227228```java229RLock lock = redissonClient.getLock(lockKey);230try {231 boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);232 if (acquired) {233 executeLockedWorkflow();234 }235} finally {236 lock.unlock();237}238```239240Use the same principle for thread pools, streams, temporary auth context, and client resources: cleanup must match actual acquisition or initialization.241242## Post-Refactor Review243244Do not stop after the code compiles. Review the changed entry method and the diff as a separate gate.245246Review in this order:2472481. Re-read the entry method top-down and confirm it now reads as workflow orchestration.2492. Check that each extracted method belongs to one stage: validation, parameter construction, business processing, or post-processing.2503. Confirm no extracted helper still mixes query, write, response assembly, and async side effects without a good reason.2514. Confirm method and variable names now make responsibility obvious without reading internals line by line.2525. Review the diff for contract drift: public signatures, DTO fields, return shape, exception behavior, transaction timing, and side-effect order.2536. Review lock handling, state transition ordering, cache invalidation, and expiration updates for hidden behavior drift.2547. Review resource cleanup paths: lock release, thread-pool shutdown, context restore, stream close, and client cleanup must still happen on success and failure paths.2558. Confirm cleanup is conditional where necessary: no unconditional `unlock`, `close`, or `shutdown` on resources that may not have been acquired.2569. Reject vague abstractions added only for reuse appearance, especially utility classes or helpers with weak names.257258If the review finds a mixed-stage helper or hidden behavior change, refactor again before claiming the work is done.259260## Stop Conditions261262Stop and surface the issue before editing when any of these are true:263264- external request or response contract must change265- public method signature must change266- exception semantics visible to callers must change267- transaction boundary or side-effect timing is unclear268- a proposed abstraction is shared by only one unstable use case269270## Common Mistakes271272- Hiding a messy method inside one new helper instead of splitting the workflow stages.273- Keeping a vague name after refactoring, such as `handle`, `process`, `data`, or `result`, when the code now has a clear responsibility.274- Extracting shared utilities too early and creating vague `CommonUtil` style code.275- Mixing response assembly back into business processing after the method was split.276- Leaving the refactored flow without comments, forcing readers to reverse-engineer stage intent from implementation details.277- Leaving lock release, cache invalidation, or TTL refresh buried in the middle of business logic so the true state transition is hard to verify.278- Splitting code in a way that hides or weakens cleanup guarantees, such as moving `unlock`, `shutdown`, or context restore out of an obvious finally path.279- Releasing a lock, thread pool, stream, or context unconditionally even though acquisition or initialization may have failed.280- Quietly changing return data, nullability, or thrown exceptions during "refactor only" work.281- Moving logging or event publishing without checking whether callers rely on timing.282283## Verification284285- Run the post-refactor review before finalizing the change.286- Read the entry method top-down and confirm each line represents one stage of the workflow.287- Check that validation, parameter construction, processing, and post-processing are visibly separated.288- Confirm comments exist for major stages and for any non-obvious rule, ordering dependency, or side effect.289- Confirm lock acquisition and release, cache mutation, persistence, and post-actions remain in a defensible order.290- Confirm resource release remains correct on all paths: success, early return, exception, and retry or timeout paths.291- Confirm conditional cleanup still matches reality: release only what was actually acquired or initialized.292- Confirm public signatures, DTO fields, return shape, and externally visible exceptions are unchanged.293- Run targeted tests for the touched path. Add tests first when behavior is not already protected.294- Use `references/checklist.md` when the refactor spans multiple methods or classes.