Skip to content
clean-architecture-principles logo

Clean Architecture Principles

clean-architecture-principles

Enforce clean architecture principles including DRY, YAGNI, KISS, Law of Demeter, and Separation of Concerns. Warns when complexity creeps in and questions unnecessary features. Use when designing architecture, refactoring, code review, or when user mentions architecture, design patterns, complex...

SKILL.md

Full skill instructions

Clean Architecture Principles

Core architectural principles focused on simplicity, maintainability, and avoiding unnecessary complexity.

Philosophy: Simplicity Over Complexity

If a feature is getting complicated, approach the user about it.

Warn when:

  • Engineering becomes complex or complicated
  • Multiple approaches exist (suggest simpler one)
  • Potential issues are detected

Format:

WARNING: This feature is getting complicated. Here's a simpler approach...

Core Principles

1. DRY (Don't Repeat Yourself)

Code duplication means failure.

Bad - Duplication:

// Creating user
async function createUser(data: UserInput) {
  const user = {
    id: generateId(),
    ...data,
    createdAt: new Date(),
    updatedAt: new Date()
  };
  await db.users.insert(user);
  await sendEmail(user.email, 'Welcome!');
  await logEvent('user_created', user.id);
  return user;
}

// Creating admin - DUPLICATED LOGIC
async function createAdmin(data: AdminInput) {
  const admin = {
    id: generateId(),
    ...data,
    createdAt: new Date(),
    updatedAt: new Date(),
    role: 'admin'
  };
  await db.users.insert(admin);
  await sendEmail(admin.email, 'Welcome Admin!');
  await logEvent('admin_created', admin.id);
  return admin;
}

Good - DRY:

// Shared logic extracted
async function createUserAccount(
  data: UserInput,
  role: 'user' | 'admin' = 'user'
) {
  const account = {
    id: generateId(),
    ...data,
    role,
    createdAt: new Date(),
    updatedAt: new Date()
  };

  await db.users.insert(account);
  await sendEmail(account.email, getWelcomeMessage(role));
  await logEvent(`${role}_created`, account.id);

  return account;
}

// Simple wrappers
const createUser = (data: UserInput) => createUserAccount(data, 'user');
const createAdmin = (data: AdminInput) => createUserAccount(data, 'admin');

2. YAGNI (You Ain't Gonna Need It)

Speculation gets rejected.

Bad - Building features "just in case":

interface UserService {
  // Currently needed
  getUser(id: string): Promise<User>;

  // Speculative - might need someday
  getAllUsers(): Promise<User[]>;
  searchUsers(query: string): Promise<User[]>;
  batchUpdateUsers(ids: string[], updates: Partial<User>): Promise<void>;
  exportUsersToCSV(): Promise<string>;
  importUsersFromCSV(csv: string): Promise<void>;
  archiveOldUsers(days: number): Promise<number>;
}

Good - Only what's needed now:

interface UserService {
  // Only what's actually needed right now
  getUser(id: string): Promise<User>;
}

// Add methods ONLY when requirements emerge

Question everything:

  • "Is this really needed?"
  • "Will we use this now?"
  • "Can we add it later if needed?"

3. KISS (Keep It Simple, Stupid)

Complexity means rewrite.

Bad - Over-engineered:

// Complex factory pattern for simple task
interface INotificationStrategy {
  send(message: string): Promise<void>;
}

class EmailNotificationStrategy implements INotificationStrategy {
  async send(message: string): Promise<void> {
    await emailService.send(message);
  }
}

class SMSNotificationStrategy implements INotificationStrategy {
  async send(message: string): Promise<void> {
    await smsService.send(message);
  }
}

class NotificationContext {
  constructor(private strategy: INotificationStrategy) {}

  async notify(message: string): Promise<void> {
    await this.strategy.send(message);
  }
}

class NotificationFactory {
  static create(type: 'email' | 'sms'): NotificationContext {
    const strategy = type === 'email'
      ? new EmailNotificationStrategy()
      : new SMSNotificationStrategy();
    return new NotificationContext(strategy);
  }
}

// Usage - overly complex
const notifier = NotificationFactory.create('email');
await notifier.notify('Hello');

Good - Simple:

// Simple function - does the same thing
async function sendNotification(message: string, type: 'email' | 'sms') {
  if (type === 'email') {
    await emailService.send(message);
  } else {
    await smsService.send(message);
  }
}

// Usage - clear and simple
await sendNotification('Hello', 'email');

4. Minimalism

Bloat means delete.

Bad - Bloated with unused features:

class User {
  // Actually used
  id: string;
  name: string;
  email: string;

  // Unused - delete these
  nickname?: string;
  favoriteColor?: string;
  zodiacSign?: string;
  lucky Numbers?: number[];
  bio?: string;
  website?: string;
  socialLinks?: Record<string, string>;
}

Good - Only what's needed:

class User {
  id: string;
  name: string;
  email: string;
}

// Add fields only when actually needed

5. Law of Demeter (Principle of Least Knowledge)

Coupling means refactor.

Bad - Too much coupling:

// Reaching through multiple objects
function displayUserCity(user: User) {
  const city = user.profile.address.location.city.name;
  console.log(city);
}

// If any intermediate object changes, this breaks

Good - Tell, don't ask:

// User provides what's needed
class User {
  getUserCity(): string {
    return this.profile.address.getCity();
  }
}

function displayUserCity(user: User) {
  const city = user.getUserCity();
  console.log(city);
}

// Or even simpler
function displayUserCity(city: string) {
  console.log(city);
}

6. SoC (Separation of Concerns)

Mixed concerns means split.

Bad - Mixed concerns:

// One function doing everything
async function handleUserRegistration(formData: FormData) {
  // Validation
  if (!formData.email.includes('@')) throw new Error('Invalid email');
  if (formData.password.length < 8) throw new Error('Password too short');

  // Business logic
  const hashedPassword = await bcrypt.hash(formData.password, 10);
  const user = {
    id: generateId(),
    email: formData.email,
    password: hashedPassword,
    createdAt: new Date()
  };

  // Data access
  await db.users.insert(user);

  // Email sending
  await sendEmail(user.email, 'Welcome!');

  // Analytics
  await analytics.track('user_registered', { userId: user.id });

  // UI update
  showNotification('Registration successful!');

  return user;
}

Good - Separated concerns:

// Validation layer
function validateRegistration(data: FormData): void {
  if (!data.email.includes('@')) throw new Error('Invalid email');
  if (data.password.length < 8) throw new Error('Password too short');
}

// Business logic layer
async function createUser(data: FormData): Promise<User> {
  const hashedPassword = await bcrypt.hash(data.password, 10);
  return {
    id: generateId(),
    email: data.email,
    password: hashedPassword,
    createdAt: new Date()
  };
}

// Data access layer
async function saveUser(user: User): Promise<void> {
  await db.users.insert(user);
}

// Notification layer
async function notifyNewUser(user: User): Promise<void> {
  await sendEmail(user.email, 'Welcome!');
  await analytics.track('user_registered', { userId: user.id });
}

// Orchestration - clean and clear
async function handleUserRegistration(formData: FormData) {
  validateRegistration(formData);
  const user = await createUser(formData);
  await saveUser(user);
  await notifyNewUser(user);
  showNotification('Registration successful!');
  return user;
}

Filipino Wisdom Integration

"Wag mong isama kung hindi mo kailangan"

Apply to every line of code.

Questions to ask:

  • Do we really need this line?
  • Is this variable actually used?
  • Can we remove this abstraction?
  • Is this dependency necessary?

Examples:

Bad:

// Including things not needed
import { useEffect, useState, useCallback, useMemo, useRef } from 'react';

function SimpleComponent() {
  const [count, setCount] = useState(0);

  // useEffect, useCallback, useMemo, useRef not used - remove them
  return <div>{count}</​div>;
}

Good:

// Only what's needed
import { useState } from 'react';

function SimpleComponent() {
  const [count, setCount] = useState(0);
  return <div>{count}</​div>;
}

Clarity Beats Cleverness

Every single time.

Bad - Clever but unclear:

// "Clever" one-liner
const result = users.reduce((a, u) => ({...a, [u.id]: u.posts.filter(p => p.likes > 10).map(p => p.title)}), {});

Good - Clear and understandable:

// Clear multi-step process
const result: Record<string, string[]> = {};

for (const user of users) {
  const popularPosts = user.posts.filter(post => post.likes > 10);
  const titles = popularPosts.map(post => post.title);
  result[user.id] = titles;
}

Warning System

Always warn when complexity creeps in:

WARNING: This authentication flow is getting complicated with 5+ steps.

Simpler approach:
1. Use existing authentication library (e.g., NextAuth.js)
2. Reduces custom code by 80%
3. Battle-tested security
4. Maintained by community

Current approach requires:
- Custom JWT handling
- Manual token refresh
- Session management
- Security auditing

Recommendation: Use established solution instead.

Questioning Requirements

Never just accept requirements blindly.

Example conversation:

User: "Add a feature where users can export their data to XML, JSON, CSV, and YAML formats"

Response:
"Let me question this requirement:

1. Which format will users actually use?
2. Can we start with just JSON (most common)?
3. Add other formats only if users request them?

Starting simple:
- Implement JSON export first
- Monitor usage
- Add other formats if needed

This approach:
✅ Delivers value faster
✅ Avoids building unused features
✅ Easier to maintain
✅ Can always add more later

Does JSON-only work for the initial release?"

Complexity Detection Checklist

Warn if you detect:

  • More than 3 levels of nesting
  • Functions longer than 50 lines
  • More than 5 function parameters
  • Deeply nested object access (user.a.b.c.d.e)
  • Multiple concerns in one function
  • Duplicate code in 3+ places
  • Speculative features not in requirements
  • Over-engineered patterns for simple tasks
  • Unnecessary abstractions or layers
  • Complex logic that could be simplified

Refactoring Triggers

Refactor when:

  1. Code Duplication (DRY violation)

    • Same logic in multiple places
    • Copy-paste code
  2. High Coupling (Law of Demeter violation)

    • Reaching through multiple objects
    • Changes ripple across many files
  3. Mixed Concerns (SoC violation)

    • One function doing multiple things
    • Hard to understand or test
  4. Unnecessary Complexity (KISS violation)

    • Over-engineered solutions
    • Complex patterns for simple problems
  5. Speculative Code (YAGNI violation)

    • Unused features or functions
    • "Just in case" code

Architecture Decision Template

Before building a feature, ask:

## Feature: [Name]

### Is this really needed?
- [ ] Yes, it's in current requirements
- [ ] No, it's speculative

### Can we make it simpler?
Current approach: [Describe]
Simpler approach: [Describe]

### Does it follow principles?
- [ ] DRY - No code duplication
- [ ] YAGNI - Not speculative
- [ ] KISS - Simple, not complex
- [ ] SoC - Concerns separated
- [ ] Minimalism - Only what's needed

### Warning flags:
- Complexity level: [Low/​Medium/​High]
- Dependencies added: [Number]
- Lines of code: [Estimate]

### Recommendation:
[Approve / Simplify / Reject with reasons]

Summary: Keep It Simple

Golden Rules:

  1. Question Everything

    • "Is this really needed?"
    • "Can we make it simpler?"
  2. Avoid Complexity

    • Simple solutions over clever ones
    • Warn when complexity creeps in
  3. Follow Principles

    • DRY - Don't repeat yourself
    • YAGNI - You ain't gonna need it
    • KISS - Keep it simple
    • Minimalism - Only what's needed
    • Law of Demeter - Minimal coupling
    • SoC - Separate concerns
  4. Filipino Wisdom

    • "Wag mong isama kung hindi mo kailangan"
    • Apply to every line
  5. Clarity Always Wins

    • Clear code beats clever code
    • Every single time

Remember: The best architecture is the simplest one that solves the actual problem. Don't build what you might need—build what you need now.

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View