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.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/data/enforce-modularity/SKILL.md
source content

Modularity 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:

  1. Search for existing functionality

    • Check
      shared/utils/
    • Look in related features
    • Search for similar patterns
  2. Extract common logic

    • Duplicated in 2+ places? Extract it
    • Function > 20 lines and reusable? Extract it
    • Pattern could benefit others? Extract it
  3. 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