| name | code-refactoring |
| description | Safe code refactoring guide for gnwebsite project. Use when refactoring components, removing duplication, improving code structure, or simplifying complex functions. Covers test-driven refactoring, incremental changes, extract function/composable patterns, and rollback procedures. Ensures refactoring preserves behavior while improving code quality. |
Code Refactoring Guide
Step-by-step guide for safely refactoring code without breaking functionality in the gnwebsite fullstack project.
When to Use This Skill
Use when:
- User asks "How do I refactor this code?" or "Can I simplify this?"
- Reducing code duplication (DRY violations)
- Simplifying complex functions (>50 lines)
- Extracting reusable logic to composables
- Improving code structure or naming
- Addressing technical debt
- User mentions: "refactor", "clean up", "simplify", "reduce duplication"
Do NOT use for:
- Style preferences without measurable benefit
- Just before deadlines
- Code without existing tests
- Changes that alter behavior (that's feature work, not refactoring)
Decision Tree: Proposal or Direct Refactoring?
Need to refactor?
│
├─ Breaking changes? (API contracts, schemas, core patterns)
│ └─ YES → Create OpenSpec proposal
│
├─ Just reorganizing code? (internal structure, no external impact)
│ └─ YES → Follow safe refactoring steps
│
└─ Unsure?
└─ Create proposal (better safe than sorry!)
Prerequisites (CRITICAL)
NEVER refactor without tests!
cd frontend && npm run test:run -- --coverage
cd backend && docker exec backend pytest --cov
Minimum requirements:
Phase 1: Preparation
Step 1: Document Current Behavior
Create temporary documentation:
# Refactoring: [Component Name]
## Current Behavior
- Input: X
- Output: Y
- Side effects: Z
- Edge cases: A, B, C
## Existing Tests
- test_case_1: normal flow
- test_case_2: error handling
- test_case_3: edge case
## Success Criteria
After refactor: all tests pass, same behavior
Step 2: Create Safety Backup
git checkout -b backup-before-refactor
git checkout -b refactor-my-feature
Step 3: Create Refactoring Checklist
## Refactoring Checklist
### Before
- [ ] All existing tests pass
- [ ] Coverage documented
- [ ] Behavior documented
- [ ] Backup branch created
### During
- [ ] ONE change at a time
- [ ] Run tests after EACH change
- [ ] Commit after EACH success
### After
- [ ] All tests still pass
- [ ] No console errors
- [ ] Manual testing complete
- [ ] Performance unchanged/better
- [ ] Documentation updated
Phase 2: Refactoring Patterns
Pattern A: Extract Function (Reduce Complexity)
When: Function >50 lines or multiple responsibilities
Before:
async function processArticle(article: Article) {
if (!article.title) throw new Error('Title required')
if (!article.content) throw new Error('Content required')
const images = []
for (const img of article.images || []) {
const url = img.image?.fileUrl || img.image?.file_url
if (url) images.push({ url, caption: img.caption })
}
const response = await api.create({ title, content, images })
return response
}
After:
async function processArticle(article: Article) {
validateArticle(article)
const images = processImages(article.images)
return await saveArticle(article, images)
}
function validateArticle(article: Article) {
if (!article.title) throw new Error('Title required')
if (!article.content) throw new Error('Content required')
if (article.title.length > 200) throw new Error('Title too long')
}
function processImages(images?: ArticleImage[]) {
return (images || [])
.map(img => ({ url: getImageUrl(img.image), caption: img.caption }))
.filter(img => img.url && img.url !== '/placeholder-image.png')
}
async function saveArticle(article: Article, images: ProcessedImage[]) {
return await api.create({
title: article.title,
content: article.content,
images
})
}
Steps:
- Extract ONE function at a time
- Run tests after each extraction
- Commit each success
- Add tests for new functions:
describe('validateArticle', () => {
it('should throw on missing title', () => {
expect(() => validateArticle({ content: 'test' }))
.toThrow('Title required')
})
})
describe('processImages', () => {
it('should extract URLs', () => {
const imgs = [{ image: { fileUrl: 'test.jpg' }, caption: 'Test' }]
expect(processImages(imgs)).toEqual([{ url: 'test.jpg', caption: 'Test' }])
})
it('should filter placeholders', () => {
expect(processImages([{ image: null }])).toEqual([])
})
})
Pattern B: Extract Composable (Reuse Logic)
When: Same logic duplicated across 3+ components
Before (duplicated in BlogView, ArticleView, CategoryView):
<script setup>
const items = ref([])
const loading = ref(false)
const error = ref('')
const loadItems = async () => {
loading.value = true
try {
const response = await blogService.getAll()
items.value = response.results
} catch (err) {
error.value = 'Failed to load'
} finally {
loading.value = false
}
}
onMounted(loadItems)
</script>
After:
export function useDataLoader<T>(
loadFn: () => Promise<{ results: T[] }>
) {
const items = ref<T[]>([])
const loading = ref(false)
const error = ref('')
const load = async () => {
loading.value = true
error.value = ''
try {
const response = await loadFn()
items.value = response.results || []
} catch (err) {
error.value = 'Failed to load items'
console.error(err)
} finally {
loading.value = false
}
}
onMounted(load)
return { items, loading, error, reload: load }
}
<!-- All components now -->
<script setup>
import { useDataLoader } from '@/composables/useDataLoader'
const { items, loading, error, reload } = useDataLoader(() =>
blogService.getAll()
)
</script>
Steps:
- Create composable
- Write composable tests
- Migrate ONE component
- Test that component
- Commit
- Repeat for remaining components
Pattern C: Consolidate Styles
When: Same CSS in 3+ components
Before (duplicated in 6 form components):
<style scoped>
.form-group { margin-bottom: 1.5rem; }
.form-control { width: 100%; padding: 0.75rem; }
.btn-primary { background: #007bff; color: white; }
</style>
After:
.form-group { margin-bottom: 1.5rem; }
.form-control { width: 100%; padding: 0.75rem; }
.btn-primary { background: #007bff; color: white; }
<!-- Components keep only unique styles -->
<style scoped>
.special-field { /* component-specific */ }
</style>
Steps:
- Extract to shared CSS file
- Import in main.ts/App.vue
- Remove from ONE component
- Visual test
- Commit
- Repeat for remaining
Pattern D: Replace with Utility
When: Same calculation in 5+ places
Before (in 5 files):
const imageUrl = img.image?.fileUrl || img.image?.file_url || '/placeholder-image.png'
After:
export function extractImageUrl(
imageData: any,
fallback = '/placeholder-image.png'
): string {
if (!imageData) return fallback
return imageData.fileUrl || imageData.file_url || fallback
}
const imageUrl = extractImageUrl(img.image)
Steps:
- Create utility
- Write comprehensive tests
- Replace in ONE location
- Test
- Commit
- Repeat for each location
Phase 3: Incremental Execution
CRITICAL WORKFLOW: One change → Test → Commit
git checkout -b refactor-my-feature
npm run test:run
git add .
git commit -m "refactor: extract validateArticle function
- Moved validation from processArticle
- All tests passing
- No behavior changes"
npm run test:run
git commit -m "refactor: extract processImages"
Testing After EVERY Change
npm run test:run
npm run type-check
npm run test:patterns
npm run dev
git reset --hard HEAD
Phase 4: Validation
Comprehensive Testing Checklist
Automated:
Manual:
Code Quality:
Performance Verification
npm run build
npm run build
Phase 5: Documentation
Update Project Docs
If new patterns introduced:
CODEBASE_ESSENTIALS.md:
- **Image URL extraction:** Always use `extractImageUrl()` from `@/utils/imageData`. Prevents silent failures.
CODEBASE_CHANGELOG.md:
### Session: Refactor Image URL Handling (Jan 13, 2026)
**Goal**: Eliminate duplicated image URL logic
**Changes**:
- Created `extractImageUrl()` utility
- Replaced 12 instances
- Added tests (8 unit, 6 integration)
**Impact**:
- Reduced duplication by ~80 lines
- Improved testability
**Validation**:
- ✅ All 227 tests pass
- ✅ No behavior changes
- ✅ Build size unchanged
**Commit**: abc123
Anti-Patterns (DON'T DO THIS)
❌ Big Bang Refactor
git commit -m "refactor: everything"
git commit -m "refactor: extract validation"
git commit -m "refactor: extract image processing"
❌ Refactor Without Tests
function refactoredFunction() {
}
test('refactoredFunction works', () => { ... })
function refactoredFunction() { ... }
❌ Change Behavior
function validateArticle(article: Article) {
if (!article.title) throw new Error('Title required')
if (!article.excerpt) throw new Error('Excerpt required')
}
function validateArticle(article: Article) {
if (!article.title) throw new Error('Title required')
}
❌ Premature Optimization
❌ Refactor Under Pressure
// WRONG: "Production deploy tomorrow, let me refactor today!"
// CORRECT: Refactor when you have time to test properly
Common Scenarios
Scenario 1: Component Too Large (>300 lines)
Fix:
- Extract child components
- Extract composables for logic
- Extract utilities for helpers
- ONE responsibility per file
Scenario 2: Duplicated Code (3+ places)
Fix:
- Identify common pattern
- Extract to utility/composable
- Write tests
- Replace one by one
- Delete duplicates
Scenario 3: Hard to Test
Fix:
- Identify dependencies
- Extract to parameters
- Make functions pure
- Write tests with mocks
Scenario 4: Unclear Naming
Fix:
- Rename ONE identifier
- Use IDE refactor (F2 in VS Code)
- Run tests
- Commit
- Repeat
Emergency Rollback
If refactoring breaks something:
git revert HEAD
git checkout backup-before-refactor
git checkout -b refactor-my-feature-v2
git stash
npm run test:run
git stash pop
git reset --hard origin/development
Success Metrics
Refactoring succeeds when:
✅ All tests pass (no behavior changes)
✅ Code more readable (clear improvement)
✅ Complexity reduced (fewer lines, simpler logic)
✅ Duplication removed (DRY)
✅ Test coverage maintained/improved
✅ Performance unchanged/better
✅ No regressions (manual testing confirms)
Key Commands
npm run test:run -- --coverage
docker exec backend pytest --cov
git checkout -b backup-before-refactor
npm run test:run
npm run type-check
npm run test:patterns
docker exec backend pytest -v
git commit -m "refactor: [change]"
npm run build
npm run dev
git push origin refactor-my-feature
Examples
Example 1: Extract Function
git commit -m "refactor: extract validateArticle"
git commit -m "refactor: extract processImages"
git commit -m "refactor: simplify processArticle"
Example 2: Extract Composable
git commit -m "refactor: add useDataLoader composable"
git commit -m "refactor: BlogView uses useDataLoader"
git commit -m "refactor: ArticleView uses useDataLoader"
git commit -m "refactor: CategoryView uses useDataLoader"
When to Stop
Stop refactoring when:
- Tests start failing frequently (too aggressive)
- Code is "good enough" (perfect is enemy of done)
- Deadline approaching (commit what you have)
- No measurable benefit (diminishing returns)
- You're just tweaking style (not improving structure)
Related Resources