Claude-skill-registry enforce-modularity
Apply Single Responsibility Principle when writing code. Keep modules focused, cohesive, and loosely coupled. Guide splitting classes with too many methods. Ensure code is reusable and testable.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/enforce-modularity" ~/.claude/skills/majiayu000-claude-skill-registry-enforce-modularity && rm -rf "$T"
skills/data/enforce-modularity/SKILL.mdModularity Standards
CRITICAL: Write modular, focused, maintainable code. Each module should do ONE thing well.
Why This Exists
Monolithic code:
- Hard to understand
- Hard to test
- Hard to maintain
- Hard to reuse
- Easy to break
Modular code is easier to understand, test, maintain, reuse, and evolve.
When to Apply
Apply modularity when:
- Creating new files/classes/functions
- Refactoring existing code
- Reviewing code quality
- Planning architecture
How It Works
1. Single Responsibility Principle (SRP)
Each file, class, and function should do ONE thing well.
File Level
- Each file represents ONE concept/responsibility
- File name clearly indicates single purpose
- If you can't name it without "and" or "or", split it
Class Level
- Each class has ONE reason to change
- Maximum 7-10 public methods per class
- Private methods don't count toward limit
Function Level
- Each function does ONE task
- Maximum 20-30 lines per function
- Maximum 3-4 parameters per function
- If needs many params, group into object
2. High Cohesion
Functions in a module work on related data.
Good cohesion:
- ✅ All code in file serves module's single purpose
- ✅ Functions work on same data structures
- ✅ Changes to one part don't cascade
Bad cohesion:
- ❌ Unrelated functions grouped together
- ❌ File does multiple unrelated things
- ❌ Changes require editing multiple sections
3. Low Coupling
Modules depend on interfaces, not implementations.
Good coupling:
- ✅ Depend on abstractions (interfaces)
- ✅ Easy to swap implementations
- ✅ Changes don't cascade to other modules
Bad coupling:
- ❌ Direct dependencies on concrete classes
- ❌ Hard to test in isolation
- ❌ Changes break other modules
Examples
Good: Single Responsibility Per Service
// ✅ UserService.ts - User management only class UserService { createUser() {} updateUser() {} deleteUser() {} findUserById() {} } // ✅ EmailService.ts - Email only class EmailService { sendWelcomeEmail() {} sendPasswordResetEmail() {} sendNotification() {} } // ✅ TokenService.ts - Tokens only class TokenService { generateToken() {} verifyToken() {} refreshToken() {} }
Bad: Multiple Responsibilities
// ❌ UserService.ts - Too many responsibilities! class UserService { // User management createUser() {} updateUser() {} // Email (should be EmailService) sendEmail() {} // Logging (should be Logger) logActivity() {} // Validation (should be Validator) validatePassword() {} // Auth (should be TokenService) generateToken() {} }
Good: High Cohesion, Low Coupling
// ✅ Interface for abstraction interface IEmailProvider { send(to: string, subject: string, body: string): Promise<void>; } // ✅ Service depends on interface, not concrete implementation class EmailService { constructor(private provider: IEmailProvider) {} async sendWelcomeEmail(user: User) { await this.provider.send( user.email, 'Welcome!', this.buildWelcomeBody(user) ); } // All email logic together (high cohesion) private buildWelcomeBody(user: User): string { return `Welcome ${user.name}!`; } } // ✅ Provider can be swapped without changing EmailService (low coupling) class SendGridProvider implements IEmailProvider { async send(to: string, subject: string, body: string) { // SendGrid implementation } } class MailgunProvider implements IEmailProvider { async send(to: string, subject: string, body: string) { // Mailgun implementation } }
Good: Testable in Isolation
// ✅ Pure, testable function class PasswordValidator { validateStrength(password: string): boolean { return ( password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password) ); } } // Test is simple test('validates strong password', () => { const validator = new PasswordValidator(); expect(validator.validateStrength('Password123')).toBe(true); });
Bad: Hard to Test
// ❌ Tightly coupled to framework, database, everything class UserController { createUser(req: Request, res: Response) { // Validation mixed in if (!req.body.email) { return res.status(400).json({ error: 'Email required' }); } // Database access mixed in const user = await db.users.create(req.body); // Email sending mixed in await sendgrid.send({ to: user.email, subject: 'Welcome' }); // Response handling mixed in res.json(user); } } // Test requires mocking: HTTP framework, database, sendgrid, etc.
Module Organization
Feature-Based Structure
src/features/auth/ ├── controllers/ # HTTP handlers (max 300 lines) ├── services/ # Business logic (max 300 lines) ├── repositories/ # Data access (max 300 lines) ├── models/ # Domain models (max 200 lines) ├── validators/ # Validation (max 150 lines) ├── types/ # TypeScript types (max 100 lines) └── utils/ # Feature utilities (max 200 lines)
Shared vs Core
shared/ # Used by 2+ features ├── services/ ├── utils/ ├── types/ └── constants/ core/ # Infrastructure (rarely changes) ├── database/ ├── config/ ├── errors/ └── interfaces/
Code Reusability
Before writing ANY code:
-
Search for existing functionality
- Check
shared/utils/ - Look in related features
- Search for similar patterns
- Check
-
Extract common logic
- Duplicated in 2+ places? Extract it
- Function > 20 lines and reusable? Extract it
- Pattern could benefit others? Extract it
-
Choose location based on usage:
Used by ONE feature: feature/utils/ Used by 2+ features: shared/utils/ Used by ALL features: core/utils/
Example: Extracting Common Logic
// ❌ BEFORE: Duplicated in multiple files // UserController.ts async createUser(req, res) { if (!req.body.email || !req.body.email.includes('@')) { return res.status(400).json({ error: 'Invalid email' }); } // ... } // PostController.ts async createPost(req, res) { if (!req.body.email || !req.body.email.includes('@')) { return res.status(400).json({ error: 'Invalid email' }); } // ... } // ✅ AFTER: Extracted to shared utility // shared/utils/validation.ts export function validateEmail(email: string): boolean { return email && email.includes('@') && email.length > 3; } // Now used everywhere import { validateEmail } from '@/shared/utils/validation'; if (!validateEmail(req.body.email)) { return res.status(400).json({ error: 'Invalid email' }); }
Splitting Large Modules
When to Split
Split when:
- File approaching 400 lines
- Class has > 10 public methods
- Function > 30 lines
- Multiple responsibilities detected
- Hard to test in isolation
How to Split
Strategy 1: Extract Utilities
// ❌ BEFORE: UserService.ts (550 lines) class UserService { validateEmail() { /* 50 lines */ } hashPassword() { /* 50 lines */ } generateToken() { /* 50 lines */ } createUser() { /* 400 lines */ } } // ✅ AFTER: Split utilities // utils/emailValidation.ts export function validateEmail() {} // utils/passwordUtils.ts export function hashPassword() {} // utils/tokenGenerator.ts export function generateToken() {} // UserService.ts (400 lines) import { validateEmail } from '../utils/emailValidation'; import { hashPassword } from '../utils/passwordUtils'; class UserService { createUser() { /* uses imported utilities */ } }
Strategy 2: Extract Sub-Services
// ❌ BEFORE: UserService.ts (600 lines) class UserService { // User CRUD (200 lines) // Password management (200 lines) // Profile management (200 lines) } // ✅ AFTER: Split by domain // UserService.ts (200 lines) class UserService { createUser() {} updateUser() {} deleteUser() {} } // UserPasswordService.ts (200 lines) class UserPasswordService { changePassword() {} resetPassword() {} } // UserProfileService.ts (200 lines) class UserProfileService { updateProfile() {} uploadAvatar() {} }
Enforcement
The code-quality-auditor will reject:
- ❌ Files > 500 lines
- ❌ Classes with > 10 public methods
- ❌ Functions > 30 lines
- ❌ Functions with > 4 parameters
- ❌ Multiple responsibilities in one file
- ❌ Duplicated code (DRY violations)
- ❌ Tight coupling to implementations
Quick Checklist
- Each file has single responsibility
- File name clearly describes purpose
- Classes have < 10 public methods
- Functions are < 30 lines
- Functions have < 4 parameters
- No code duplication
- Modules are loosely coupled (interfaces)
- Code is testable in isolation
- Reusable logic extracted to utilities