agentleFS
Khuirul-HudaKhuirul-Huda/clean-principles/skills/solid-principles/SKILL.md

solid-principles

Skill0 starsChanged 29 days agoMITView on GitHub

Use when designing classes, modules, or functions — especially when code feels hard to extend, test, or change without breaking other parts.

11 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Files
SKILL.md

SOLID Principles

Five principles for writing maintainable, extensible object-oriented code.

Quick Reference

PrincipleRuleSmell it fixes
S — Single ResponsibilityOne class = one reason to changeGod classes, methods doing too much
O — Open/ClosedOpen for extension, closed for modificationSwitch/if-chains that grow with features
L — Liskov SubstitutionSubtypes must be substitutable for base typesSubclass that breaks parent's contract
I — Interface SegregationNo client should depend on methods it doesn't useFat interfaces with unimplemented methods
D — Dependency InversionDepend on abstractions, not concretionsHard-coded dependencies, untestable code

S — Single Responsibility

One reason to change = one actor (user, admin, system) that can demand changes.

# ❌ BAD: User class handles data + persistence + email
class User:
    def get_name(self): ...
    def save_to_db(self): ...       # DB concern
    def send_welcome_email(self): ...  # Email concern

# ✅ GOOD: Separate concerns
class User:
    def get_name(self): ...

class UserRepository:
    def save(self, user: User): ...

class UserMailer:
    def send_welcome(self, user: User): ...

O — Open/Closed

Extend behavior by adding new code, not modifying existing code.

# ❌ BAD: Add new shape? Edit this function.
def area(shape):
    if shape.type == "circle": return π * shape.r**2
    if shape.type == "rect": return shape.w * shape.h

# ✅ GOOD: Add new shape? Just add a new class.
class Circle:
    def area(self): return π * self.r**2

class Rectangle:
    def area(self): return self.w * self.h

L — Liskov Substitution

If S extends T, you can use S wherever T is expected — same behavior, no surprises.

# ❌ BAD: Square breaks Rectangle's contract
class Rectangle:
    def set_width(self, w): self.width = w
    def set_height(self, h): self.height = h

class Square(Rectangle):
    def set_width(self, w): self.width = self.height = w  # Surprise!

# ✅ GOOD: Don't inherit Rectangle — use a shared abstraction
class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

class Circle(Shape):
    def area(self) -> float: return π * self.r ** 2  # No surprises

class Square(Shape):
    def area(self) -> float: return self.side ** 2   # No surprises

# Any caller using Shape works with Circle, Square, or any future Shape
def print_area(shape: Shape):
    print(shape.area())  # Substitution holds — no broken contracts

I — Interface Segregation

Split fat interfaces into focused ones. Clients only depend on what they use.

// ❌ BAD: Printer forced to implement fax
interface Machine {
  print(): void;
  scan(): void;
  fax(): void;  // Not all machines have this
}

// ✅ GOOD: Focused interfaces
interface Printer { print(): void; }
interface Scanner { scan(): void; }
interface FaxMachine { fax(): void; }

D — Dependency Inversion

High-level modules define the interface. Low-level modules implement it.

# ❌ BAD: OrderService directly depends on MySQLDatabase
class OrderService:
    def __init__(self):
        self.db = MySQLDatabase()  # Hard dependency

# ✅ GOOD: Depend on abstraction, inject the implementation
class OrderService:
    def __init__(self, db: DatabaseInterface):
        self.db = db  # Testable, swappable

When NOT to Apply SOLID

SOLID adds structure — don't add structure before you need it.

SituationGuidance
Script or utility < ~200 linesSkip it — SOLID adds boilerplate without benefit
Only one concrete implementationDon't apply DIP/OCP yet (YAGNI wins)
Splitting a class makes it harder to readSRP is served, but clarity isn't — reconsider
Prototype / proof of conceptWrite it dirty first, refactor when it survives

Rule: Apply SOLID when the codebase grows and pain appears — not from line 1.

Common Mistakes

MistakeFix
Splitting by file, not by responsibilityAsk "who would request this change?"
Making everything extensible upfrontApply OCP only when you see the need (YAGNI)
Deep inheritance hierarchiesPrefer composition over inheritance
One mega-interfaceSplit by client usage
new ConcreteClass() inside business logicUse constructor injection

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.