| name | code-quality-analyzer |
| description | Detect code smells, DRY violations, and refactoring opportunities across Angular codebase. Use when identifying duplicate code, reducing code duplication, extracting base classes, centralizing logic, detecting shotgun surgery patterns, or improving code maintainability. Do NOT use for linting/ESLint configuration, code formatting, performance profiling, or security vulnerability scanning. |
| triggers | ["duplicate code","code smell","DRY","refactor","code duplication","extract base class","centralize","shotgun surgery","code quality","clean up","messy code","maintainability","long method"] |
| negative_triggers | ["ESLint","linting","formatting","Prettier","performance","profiling","security","vulnerability","Python","backend"] |
Code Quality Analyzer Skill
Expert guidance for identifying code smells, analyzing refactoring opportunities, and applying best practices to improve code maintainability and reduce duplication.
Code Smell Detection
1. Duplicate Code
Indicator: Same pattern appears 3+ times across files
Impact: Maintenance burden increases, bugs must be fixed multiple places
class PhotoService {
getPhotos() {
return this.http.get<Photo[]>(url).pipe(
catchError(error => {
console.error('API error:', error);
this.snackBar.open('Failed to load', 'OK');
return throwError(() => error);
})
);
}
}
class BaseApiService {
protected handleApiError<T>(message: string): (error: any) => Observable<T> {
return (error: any) => {
console.error('API error:', error);
this.snackBar.open(message, 'OK');
return throwError(() => error);
};
}
}
class PhotoService extends BaseApiService {
getPhotos() {
return this.http.get<Photo[]>(url).pipe(catchError(this.handleApiError('Failed to load photos')));
}
}
2. Shotgun Surgery
Indicator: Change in one location requires changes in many places
Solution: Centralize related logic
gallery.ts: score.toFixed(1) + '/10'
photo-card.ts: score.toFixed(1) + '/10'
photo-detail.ts: score.toFixed(1) + '/10'
@Pipe({ name: 'score', standalone: true })
class ScorePipe implements PipeTransform {
transform(score: number): string {
return score.toFixed(1) + '/10';
}
}
3. Long Methods
Indicator: Method/function > 50 lines
Solution: Extract sub-methods with clear names
method() {
}
private validateInput(): boolean { }
private loadData(): Observable<Data> { }
private transformData(data: Data): TransformedData { }
private saveToCache(data: TransformedData): void { }
private updateUI(data: TransformedData): void { }
public execute(): void {
if (!this.validateInput()) return;
this.loadData()
.pipe(
map(data => this.transformData(data)),
tap(data => this.saveToCache(data)),
tap(data => this.updateUI(data))
).subscribe();
}
4. Feature Envy
Indicator: Method uses more data from another class than its own
Solution: Move method to that class
class GalleryComponent {
private getPhotoSummary(photo: Photo) {
return `${photo.filename} - ${photo.aggregate.toFixed(1)} (${photo.category})`;
}
}
class Photo {
getSummary(): string {
return `${this.filename} - ${this.aggregate.toFixed(1)} (${this.category})`;
}
}
5. Data Clumps
Indicator: Same 3+ parameters passed together across methods
Solution: Create parameter object
fetchPhotos(category: string, minScore: number, sortBy: string, page: number) { }
countPhotos(category: string, minScore: number, sortBy: string, page: number) { }
exportPhotos(category: string, minScore: number, sortBy: string, page: number) { }
interface PhotoFilter {
category: string;
minScore: number;
sortBy: string;
page: number;
}
fetchPhotos(filter: PhotoFilter) { }
countPhotos(filter: PhotoFilter) { }
exportPhotos(filter: PhotoFilter) { }
DRY Violation Detection
Pattern Recognition
Search for:
- Line-for-line identical blocks (5+ lines appearing 3+ times)
- Conceptually identical patterns (different variable names but same logic)
- Copy-pasted methods with minor modifications
- Repeated validation/transformation logic
Quick Detection Commands
grep -r "catchError" --include="*.service.ts" client/src/app/ | wc -l
grep -r "pipe(" client/src/app/ --include="*.service.ts" | \
grep -c "tap\|map\|catchError"
grep -r "signal<" --include="*.ts" client/src/app/ | wc -l
Refactoring Decision Tree
Is the code duplicated?
- YES -> 5+ line blocks in 3+ places?
- Base class (behavior inheritance)
- Utility function (stateless logic)
- Injectable service (stateful behavior)
Is it a cross-cutting concern?
- YES (error handling, logging, caching):
- Base service class or HTTP interceptor
- Angular pipe for display formatting
Does one class know too much about another?
- YES: Move responsibility to appropriate class
Are parameters always passed together?
- YES: Create parameter object or data class
Refactoring Patterns
Pattern 1: Base Class Extraction
Use when: Multiple classes share behavior, have same parent interface
class BaseApiService {
protected http = inject(HttpClient);
protected snackBar = inject(MatSnackBar);
protected handleError<T>(message: string): (error: any) => Observable<T> {
return (error) => {
console.error(`API error:`, error);
this.snackBar.open(message, 'OK', { duration: 3000 });
return throwError(() => error);
};
}
}
Pattern 2: Utility Function Extraction
Use when: Stateless transformation logic, calculation, formatting
export function formatScore(score: number, maxScore = 10): string {
return `${score.toFixed(1)}/${maxScore}`;
}
export function formatPercentage(value: number): string {
return `${(value * 100).toFixed(0)}%`;
}
displayScore = computed(() => formatScore(this.$photo().aggregate));
Pattern 3: Injectable Service Extraction
Use when: Stateful logic, caching, complex operations, needs dependencies
@Injectable({ providedIn: 'root' })
class PhotoCacheService {
private cache = new Map<string, Photo[]>();
get(key: string, factory: () => Observable<Photo[]>): Observable<Photo[]> {
if (this.cache.has(key)) {
return of(this.cache.get(key)!);
}
return factory().pipe(
tap(data => this.cache.set(key, data))
);
}
invalidate(key?: string): void {
key ? this.cache.delete(key) : this.cache.clear();
}
}
Refactoring Verification
After refactoring, verify:
- No increase in file count (or justified increase)
npx ng build compiles cleanly from client/
- No change in public API (backward compatible)
- Code is simpler, not more complex
- Duplicated code removed (not just moved)
When NOT to Refactor
- Code is about to be deleted anyway
- Refactoring would break public API
- Code is isolated/rarely changed
- Not truly duplicated (coincidental similarity)
Examples
User says: "Find duplicated error handling across services"
- Search:
grep -r "catchError" --include="*.service.ts" client/src/app/ -- find 8 occurrences
- Analyze: 6 of 8 use identical error notification pattern
- Report: "6 services duplicate error handling -> extract to
BaseApiService.handleError()"
- Propose: Base class with shared
catchError operator
- Verify: All child classes extend base, build succeeds
User says: "This code feels messy, help me clean it up"
- Read the file -- identify code smells (long methods, data clumps, feature envy)
- Categorize: 2 long methods (>50 lines), 1 data clump (filter params)
- Propose refactoring with decision tree: stateless -> utility, stateful -> service
- Apply changes, verify no public API breakage
Common Issues
| Issue | Cause | Solution |
|---|
| Refactoring breaks child classes | Base class method signature changed | Check ALL child classes before modifying base |
| "False positive" duplication | Coincidentally similar but semantically different code | Only refactor truly duplicated logic, not coincidental similarity |
| Over-abstraction | Extracting a helper used only once | Only extract when pattern appears 3+ times |
| Refactoring breaks tests | Mocks tied to old implementation | Update test mocks to match new structure |