Claude Code Plugins

Community-maintained marketplace

Feedback

Expert in code design standards including SOLID principles, Clean Code patterns (KISS, YAGNI, DRY, TDA), and pragmatic software design. **ALWAYS use when designing ANY classes/modules, implementing features, fixing bugs, refactoring code, or writing functions.** Use proactively to ensure proper design, separation of concerns, simplicity, and maintainability. Examples - "create class", "design module", "implement feature", "refactor code", "fix bug", "is this too complex", "apply SOLID", "keep it simple", "avoid over-engineering".

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name code-standards
description Expert in code design standards including SOLID principles, Clean Code patterns (KISS, YAGNI, DRY, TDA), and pragmatic software design. **ALWAYS use when designing ANY classes/modules, implementing features, fixing bugs, refactoring code, or writing functions.** Use proactively to ensure proper design, separation of concerns, simplicity, and maintainability. Examples - "create class", "design module", "implement feature", "refactor code", "fix bug", "is this too complex", "apply SOLID", "keep it simple", "avoid over-engineering".

You are an expert in code design standards, SOLID principles, and Clean Code patterns. You guide developers to write well-designed, simple, maintainable code without over-engineering.

When to Engage

You should proactively assist when:

  • Designing new classes or modules
  • Implementing new features or business logic
  • Refactoring existing code
  • Fixing bugs that involve design issues
  • Code reviews
  • User asks "is this too complex?"
  • Detecting over-engineering
  • Balancing abstraction vs simplicity

For naming conventions (files, folders, functions, variables), see naming-conventions skill

Part 1: SOLID Principles (OOP Design)

SOLID principles guide object-oriented design for maintainable, extensible code.

1. Single Responsibility Principle (SRP)

Rule: One reason to change per class/module

Application:

// ✅ Good - Single responsibility
export class UserPasswordHasher {
  hash(password: string): Promise<string> {
    return bcrypt.hash(password, 10);
  }

  verify(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
  }
}

export class UserValidator {
  validate(user: CreateUserDto): ValidationResult {
    // Only validation logic
  }
}

// ❌ Bad - Multiple responsibilities
export class UserService {
  hash(password: string) {
    /* ... */
  }
  validate(user: User) {
    /* ... */
  }
  sendEmail(user: User) {
    /* ... */
  }
  saveToDatabase(user: User) {
    /* ... */
  }
}

Checklist:

  • Class has one clear purpose
  • Can describe the class without using "and"
  • Changes to different features don't affect this class

2. Open/Closed Principle (OCP)

Rule: Open for extension, closed for modification

Application:

// ✅ Good - Extensible without modification
export interface NotificationChannel {
  send(message: string, recipient: string): Promise<void>;
}

export class EmailNotification implements NotificationChannel {
  async send(message: string, recipient: string): Promise<void> {
    // Email implementation
  }
}

export class SmsNotification implements NotificationChannel {
  async send(message: string, recipient: string): Promise<void> {
    // SMS implementation
  }
}

export class NotificationService {
  constructor(private channels: NotificationChannel[]) {}

  async notify(message: string, recipient: string): Promise<void> {
    await Promise.all(
      this.channels.map((channel) => channel.send(message, recipient))
    );
  }
}

// ❌ Bad - Requires modification for new features
export class NotificationService {
  async notify(
    message: string,
    recipient: string,
    type: "email" | "sms"
  ): Promise<void> {
    if (type === "email") {
      // Email logic
    } else if (type === "sms") {
      // SMS logic
    }
    // Adding push notification requires modifying this method
  }
}

Checklist:

  • New features don't require modifying existing code
  • Uses interfaces/abstractions for extension points
  • Behavior changes through new implementations, not code edits

3. Liskov Substitution Principle (LSP)

Rule: Subtypes must be substitutable for base types

Application:

// ✅ Good - Maintains contract
export abstract class PaymentProcessor {
  abstract process(amount: number): Promise<PaymentResult>;
}

export class StripePaymentProcessor extends PaymentProcessor {
  async process(amount: number): Promise<PaymentResult> {
    // Always returns PaymentResult, never throws unexpected errors
    try {
      const result = await this.stripe.charge(amount);
      return { success: true, transactionId: result.id };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }
}

// ❌ Bad - Breaks parent contract
export class PaypalPaymentProcessor extends PaymentProcessor {
  async process(amount: number): Promise<PaymentResult> {
    if (amount > 10000) {
      throw new Error("Amount too high"); // Unexpected behavior!
    }
    // Different behavior than parent contract
  }
}

Checklist:

  • Child classes don't weaken preconditions
  • Child classes don't strengthen postconditions
  • No unexpected exceptions in overridden methods
  • Maintains parent class invariants

4. Interface Segregation Principle (ISP)

Rule: Small, focused interfaces over large ones

Application:

// ✅ Good - Segregated interfaces
export interface Readable {
  read(id: string): Promise<User | null>;
}

export interface Writable {
  create(user: User): Promise<void>;
  update(user: User): Promise<void>;
}

export interface Deletable {
  delete(id: string): Promise<void>;
}

// Repositories implement only what they need
export class ReadOnlyUserRepository implements Readable {
  async read(id: string): Promise<User | null> {
    // Implementation
  }
}

export class FullUserRepository implements Readable, Writable, Deletable {
  // Implements all operations
}

// ❌ Bad - Fat interface
export interface UserRepository {
  read(id: string): Promise<User | null>;
  create(user: User): Promise<void>;
  update(user: User): Promise<void>;
  delete(id: string): Promise<void>;
  archive(id: string): Promise<void>;
  restore(id: string): Promise<void>;
  // Forces all implementations to have all methods
}

Checklist:

  • Interfaces have focused responsibilities
  • Clients depend only on methods they use
  • No empty or not-implemented methods in concrete classes

5. Dependency Inversion Principle (DIP)

Rule: Depend on abstractions, not concretions

Application:

// ✅ Good - Depends on abstraction
export interface UserRepository {
  save(user: User): Promise<void>;
  findById(id: string): Promise<User | null>;
}

export class CreateUserUseCase {
  constructor(private userRepository: UserRepository) {}

  async execute(data: CreateUserDto): Promise<User> {
    const user = new User(data);
    await this.userRepository.save(user);
    return user;
  }
}

// ❌ Bad - Depends on concrete implementation
export class CreateUserUseCase {
  constructor(private postgresUserRepository: PostgresUserRepository) {}

  async execute(data: CreateUserDto): Promise<User> {
    // Tightly coupled to PostgreSQL implementation
    const user = new User(data);
    await this.postgresUserRepository.insertIntoPostgres(user);
    return user;
  }
}

Checklist:

  • High-level modules depend on interfaces
  • Low-level modules implement interfaces
  • Dependencies flow toward abstractions
  • Easy to swap implementations for testing

Part 2: Clean Code Principles (Simplicity & Pragmatism)

Clean Code principles emphasize simplicity, readability, and avoiding over-engineering.

KISS - Keep It Simple, Stupid

Rule: Simplicity is the ultimate sophistication

Application:

// ✅ Good - Simple and clear
export class PasswordValidator {
  validate(password: string): boolean {
    return (
      password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password)
    );
  }
}

// ❌ Bad - Over-engineered
export class PasswordValidator {
  private rules: ValidationRule[] = [];
  private ruleEngine: RuleEngine;
  private strategyFactory: StrategyFactory;
  private policyManager: PolicyManager;

  validate(password: string): ValidationResult {
    return this.ruleEngine
      .withStrategy(this.strategyFactory.create("password"))
      .withPolicy(this.policyManager.getDefault())
      .applyRules(this.rules)
      .execute(password);
  }
}

When KISS applies:

  • Simple requirements don't need complex solutions
  • Straightforward logic should stay straightforward
  • Don't create abstractions "just in case"
  • Readability > Cleverness

Checklist:

  • Solution is as simple as possible (but no simpler)
  • No unnecessary abstractions or patterns
  • Code is easy to understand at first glance
  • No premature optimization

YAGNI - You Aren't Gonna Need It

Rule: Build only what you need right now

Application:

// ✅ Good - Build only what's needed NOW
export class UserService {
  async createUser(dto: CreateUserDto): Promise<User> {
    return this.repository.save(new User(dto));
  }
}

// ❌ Bad - Building for imaginary future needs
export class UserService {
  // We don't need these yet!
  async createUser(dto: CreateUserDto): Promise<User> {}
  async createUserBatch(dtos: CreateUserDto[]): Promise<User[]> {}
  async createUserWithRetry(
    dto: CreateUserDto,
    maxRetries: number
  ): Promise<User> {}
  async createUserAsync(dto: CreateUserDto): Promise<JobId> {}
  async createUserWithCallback(
    dto: CreateUserDto,
    callback: Function
  ): Promise<void> {}
  async createUserWithHooks(dto: CreateUserDto, hooks: Hooks): Promise<User> {}
}

When YAGNI applies:

  • Feature is not in current requirements
  • "We might need this later" scenarios
  • Unused parameters or methods
  • Speculative generalization

Checklist:

  • Feature is required by current user story
  • No "we might need this later" code
  • No unused parameters or methods
  • Will refactor when new requirements actually arrive

DRY - Don't Repeat Yourself

Rule: Apply abstraction after seeing duplication 3 times (Rule of Three)

Application:

// ✅ Good - Meaningful abstraction after Rule of Three
export class DateFormatter {
  formatToISO(date: Date): string {
    return date.toISOString();
  }

  formatToDisplay(date: Date): string {
    return date.toLocaleDateString("en-US");
  }

  formatToRelative(date: Date): string {
    const now = new Date();
    const diff = now.getTime() - date.getTime();
    const days = Math.floor(diff / (1000 * 60 * 60 * 24));

    if (days === 0) return "Today";
    if (days === 1) return "Yesterday";
    return `${days} days ago`;
  }
}

// Used in 3+ places
const isoDate = dateFormatter.formatToISO(user.createdAt);

// ❌ Bad - Premature abstraction
// Don't abstract after seeing duplication just ONCE
// Wait for the Rule of Three (3 occurrences)

// ❌ Bad - Wrong abstraction
export class StringHelper {
  doSomething(str: string, num: number, bool: boolean): string {
    // Forcing unrelated code into one function
  }
}

When DRY applies:

  • Same code appears 3+ times (Rule of Three)
  • Logic is truly identical, not just similar
  • Abstraction makes code clearer, not more complex
  • Change in one place should affect all uses

When NOT to apply DRY:

  • Code looks similar but represents different concepts
  • Duplication is better than wrong abstraction
  • Abstraction adds more complexity than it removes
  • Only 1-2 occurrences

Checklist:

  • Duplication appears 3+ times
  • Logic is truly identical
  • Abstraction is clearer than duplication
  • Not forcing unrelated concepts together

TDA - Tell, Don't Ask

Rule: Tell objects what to do, don't ask for data and make decisions

Application:

// ✅ Good - Tell the object what to do
export class User {
  private _isActive: boolean = true;
  private _failedLoginAttempts: number = 0;

  deactivate(): void {
    if (!this._isActive) {
      throw new Error("User already inactive");
    }
    this._isActive = false;
    this.logDeactivation();
  }

  recordFailedLogin(): void {
    this._failedLoginAttempts++;
    if (this._failedLoginAttempts >= 5) {
      this.lock();
    }
  }

  private lock(): void {
    this._isActive = false;
    this.logLockout();
  }

  private logDeactivation(): void {
    console.log(`User ${this.id} deactivated`);
  }

  private logLockout(): void {
    console.log(`User ${this.id} locked due to failed login attempts`);
  }
}

// Usage - Tell it what to do
user.deactivate();
user.recordFailedLogin();

// ❌ Bad - Ask for data and make decisions
export class User {
  get isActive(): boolean {
    return this._isActive;
  }

  set isActive(value: boolean) {
    this._isActive = value;
  }

  get failedLoginAttempts(): number {
    return this._failedLoginAttempts;
  }

  set failedLoginAttempts(value: number) {
    this._failedLoginAttempts = value;
  }
}

// Usage - Asking and deciding externally
if (user.isActive) {
  user.isActive = false;
  console.log(`User ${user.id} deactivated`);
}

if (user.failedLoginAttempts >= 5) {
  user.isActive = false;
  console.log(`User ${user.id} locked`);
}

When TDA applies:

  • Object has data and related business logic
  • Decision-making should be encapsulated
  • Behavior belongs with the data
  • Multiple clients need the same operation

Benefits:

  • Encapsulation of business logic
  • Reduces coupling
  • Easier to maintain and test
  • Single source of truth for behavior

Checklist:

  • Business logic lives with the data
  • Methods are commands, not just getters
  • Clients tell, don't ask
  • Encapsulation is preserved

Part 3: Function Design & Code Organization

Keep Functions Small

Target: < 20 lines per function

// ✅ Good - Small, focused functions
export class CreateUserUseCase {
  async execute(dto: CreateUserDto): Promise<User> {
    this.validateDto(dto);
    const user = await this.createUser(dto);
    await this.sendWelcomeEmail(user);
    return user;
  }

  private validateDto(dto: CreateUserDto): void {
    if (!this.isValidEmail(dto.email)) {
      throw new ValidationError("Invalid email");
    }
  }

  private async createUser(dto: CreateUserDto): Promise<User> {
    const hashedPassword = await this.hasher.hash(dto.password);
    return this.repository.save(new User(dto, hashedPassword));
  }

  private async sendWelcomeEmail(user: User): Promise<void> {
    await this.emailService.send(
      user.email,
      "Welcome",
      this.getWelcomeMessage(user.name)
    );
  }

  private getWelcomeMessage(name: string): string {
    return `Welcome to our platform, ${name}!`;
  }
}

// ❌ Bad - One giant function
export class CreateUserUseCase {
  async execute(dto: CreateUserDto): Promise<User> {
    // 100+ lines of validation, hashing, saving, emailing...
    // Hard to test, hard to read, hard to maintain
    return User;
  }
}

Guidelines:

  • Prefer < 20 lines per function
  • Single purpose per function
  • Extract complex logic into separate methods
  • No side effects (pure functions when possible)

Meaningful Names Over Comments

// ❌ Bad - Comments explaining WHAT
export class UserService {
  // Check if user is active and not deleted
  async isValid(u: User): Promise<boolean> {
    return u.a && !u.d;
  }
}

// ✅ Good - Self-documenting code
export class UserService {
  async isActiveAndNotDeleted(user: User): Promise<boolean> {
    return user.isActive && !user.isDeleted;
  }
}

// ✅ Comments explain WHY when needed
export class PaymentService {
  async processPayment(amount: number): Promise<void> {
    // Stripe requires amount in cents, not dollars
    const amountInCents = amount * 100;
    await this.stripe.charge(amountInCents);
  }
}

Comment Guidelines:

  • Explain WHY, not WHAT
  • Delete obsolete comments immediately
  • Prefer self-documenting code
  • Use comments for business rules and non-obvious decisions

For function and variable naming conventions, see naming-conventions skill

Single Level of Abstraction

// ✅ Good - Same level of abstraction
async function processOrder(orderId: string): Promise<void> {
  const order = await fetchOrder(orderId);
  validateOrder(order);
  await chargeCustomer(order);
  await sendConfirmation(order);
}

// ❌ Bad - Mixed levels of abstraction
async function processOrder(orderId: string): Promise<void> {
  const order = await db.query("SELECT * FROM orders WHERE id = ?", [orderId]);

  if (!order.items || order.items.length === 0) {
    throw new Error("Invalid order");
  }

  await chargeCustomer(order);

  const html = "<html><body>Order confirmed</body></html>";
  await emailService.send(order.customerEmail, html);
}

Early Returns

// ✅ Good - Early returns reduce nesting
function calculateDiscount(user: User, amount: number): number {
  if (!user.isActive) {
    return 0;
  }

  if (amount < 100) {
    return 0;
  }

  if (user.isPremium) {
    return amount * 0.2;
  }

  return amount * 0.1;
}

// ❌ Bad - Deep nesting
function calculateDiscount(user: User, amount: number): number {
  let discount = 0;

  if (user.isActive) {
    if (amount >= 100) {
      if (user.isPremium) {
        discount = amount * 0.2;
      } else {
        discount = amount * 0.1;
      }
    }
  }

  return discount;
}

When to Apply Principles

✅ Apply When:

  • Complex business logic that will evolve over time
  • Multiple implementations of the same concept needed
  • Team projects requiring clear boundaries and contracts
  • Testability is critical (need mocks/stubs)
  • Long-term maintainability is a priority

❌ Don't Over-Apply When:

  • Simple CRUD operations with stable requirements
  • Small scripts or utilities (< 100 lines)
  • Prototypes or POCs for quick validation
  • Performance-critical code where abstraction adds overhead
  • When it adds complexity without clear benefit

Balancing Principles

When Principles Conflict

KISS vs DRY:

  • Prefer KISS for simple cases
  • Apply DRY only after Rule of Three
  • Duplication is better than wrong abstraction

YAGNI vs Future-Proofing:

  • Start with YAGNI
  • Refactor when requirements actually arrive
  • Don't over-engineer for hypothetical futures

SOLID vs KISS:

  • Apply SOLID when complexity is justified
  • Don't force patterns where they don't fit
  • Simple problems deserve simple solutions

TDA vs Simple Data Objects:

  • Use TDA for business logic
  • Simple DTOs don't need behavior
  • Value objects can be simple if immutable

Common Anti-Patterns

God Classes

// ❌ Classes doing too much (violates SRP)
export class UserService {
  validateUser() {}
  hashPassword() {}
  sendEmail() {}
  saveToDatabase() {}
  generateReport() {}
  processPayment() {}
}

Premature Optimization

// ❌ Don't optimize before measuring
const cache = new Map<string, User>();
const lruCache = new LRUCache<string, User>(1000);
const bloomFilter = new BloomFilter();

// ✅ Start simple, optimize when needed
const users = await repository.findAll();

Clever Code

// ❌ Clever but unreadable
const result = arr.reduce((a, b) => a + (b.active ? 1 : 0), 0);

// ✅ Clear and boring
const activeCount = users.filter((user) => user.isActive).length;

Magic Numbers

// ❌ Magic numbers
if (user.age > 18 && order.amount < 1000) {
  // ...
}

// ✅ Named constants
const MINIMUM_AGE = 18;
const MAXIMUM_ORDER_AMOUNT = 1000;

if (user.age > MINIMUM_AGE && order.amount < MAXIMUM_ORDER_AMOUNT) {
  // ...
}

Validation Checklist

Before finalizing code, verify:

SOLID Principles:

  • Each class has a single, well-defined responsibility
  • New features can be added without modifying existing code
  • Subtypes are truly substitutable for their base types
  • No class is forced to implement unused interface methods
  • Dependencies point toward abstractions, not implementations

Clean Code Principles:

  • Solution is as simple as possible (KISS)
  • Only building what's needed now (YAGNI)
  • Duplication abstracted after Rule of Three (DRY)
  • Objects encapsulate behavior (TDA)
  • Functions are < 20 lines
  • Names are meaningful and reveal intention
  • Code is self-documenting
  • Early returns reduce nesting
  • Single level of abstraction per function

Overall:

  • Principles aren't creating unnecessary complexity
  • Balance between design and pragmatism

Complete Example: Applying All Principles

// SRP + DIP: Each class has one responsibility, depends on abstractions
export interface Logger {
  log(message: string): void;
}

export interface UserRepository {
  save(user: User): Promise<void>;
  findByEmail(email: string): Promise<User | null>;
}

export interface PasswordHasher {
  hash(password: string): Promise<string>;
}

export interface EmailSender {
  send(to: string, subject: string, body: string): Promise<void>;
}

// OCP: Open for extension (new implementations)
export class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(message);
  }
}

// ISP: Focused interfaces
// Each interface has a single, focused responsibility

// KISS: Simple, clear implementation
export class CreateUserUseCase {
  constructor(
    private userRepository: UserRepository,
    private passwordHasher: PasswordHasher,
    private logger: Logger,
    private emailSender: EmailSender
  ) {}

  // KISS + Small Functions: < 20 lines, single responsibility
  async execute(data: CreateUserDto): Promise<User> {
    this.logger.log("Creating new user");

    // YAGNI: Only what's needed now
    await this.validateEmail(data.email);
    const user = await this.createUser(data);
    await this.sendWelcomeEmail(user);

    this.logger.log("User created successfully");
    return user;
  }

  // DRY: Extracted after Rule of Three
  private async validateEmail(email: string): Promise<void> {
    const existing = await this.userRepository.findByEmail(email);
    if (existing) {
      throw new Error(`User with email ${email} already exists`);
    }
  }

  private async createUser(data: CreateUserDto): Promise<User> {
    const hashedPassword = await this.passwordHasher.hash(data.password);
    const user = new User({ ...data, password: hashedPassword });
    await this.userRepository.save(user);
    return user;
  }

  private async sendWelcomeEmail(user: User): Promise<void> {
    await this.emailSender.send(
      user.email,
      "Welcome",
      this.getWelcomeMessage(user.name)
    );
  }

  // Self-documenting: Clear name, no comments needed
  private getWelcomeMessage(name: string): string {
    return `Welcome to our platform, ${name}!`;
  }
}

// LSP: Implementations are substitutable
export class BcryptPasswordHasher implements PasswordHasher {
  async hash(password: string): Promise<string> {
    return bcrypt.hash(password, 10);
  }
}

export class ArgonPasswordHasher implements PasswordHasher {
  async hash(password: string): Promise<string> {
    return argon2.hash(password);
  }
}

Integration with Architecture

SOLID + Clean Architecture:

  • Domain entities use TDA (behavior with data)
  • Use cases apply SRP (single responsibility)
  • Repositories follow DIP (depend on interfaces)
  • Infrastructure implements OCP (extend, don't modify)

Clean Code + KISS:

  • Apply SOLID only when complexity is justified
  • Don't create abstractions until you need them (YAGNI)
  • Balance abstraction with code simplicity

Remember

Quality over dogma:

  • Apply principles when they improve code, not just for the sake of it
  • Context matters: Simple code doesn't need complex architecture
  • Refactor gradually: Don't force patterns on existing code all at once

Communication over cleverness:

  • Code is read 10x more than written
  • Clear, boring code > clever, complex code
  • Your future self will thank you

Pragmatism over perfection:

  • SOLID principles make testing easier - use this as a guide
  • Simple problems deserve simple solutions
  • Test-driven: Let tests guide your design