Solid Principles

thebushidocollective/han/plugins/core/skills/solid-principles

by thebushidocollective19caa51d5fd1No license198 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 4 weeks ago

Use during implementation when designing modules, functions, and components requiring SOLID principles for maintainable, flexible architecture.

AI-generated overview

Guides applying SOLID design principles to modules, functions, and components in Elixir and TypeScript.

What it does
This skill provides reference guidance for applying the five SOLID principles: single responsibility, open/closed, Liskov substitution, interface segregation, and dependency inversion. It illustrates each principle with bad and good code patterns in Elixir and TypeScript, plus a self-check question for each. It also includes an application checklist for before coding, during implementation, and during code review, and lists common violations found in codebases.
When to use it
Use it during implementation when designing modules, functions, or components that need maintainable, flexible architecture. It is also useful during code review to check responsibility separation, extension points, contract honoring, interface focus, and dependency abstraction.
Requirements
No scripts or packages are required; it is an instructions-only reference. The agent needs read, edit, grep, and glob tools to consult and apply the guidance in a codebase.

SOLID Principles

Apply SOLID design principles for maintainable, flexible code architecture.

The Five Principles

1. Single Responsibility Principle (SRP)

A module should have one, and only one, reason to change

Elixir Pattern

elixir
# BAD - Multiple responsibilitiesdefmodule UserManager do  def create_user(attrs) do    # Creates user    # Sends welcome email    # Logs to analytics    # Updates cache  endend
# GOOD - Single responsibilitydefmodule User do  def create(attrs), do: Repo.insert(changeset(attrs))end
defmodule UserNotifier do  def send_welcome_email(user), do: # email logicend
defmodule UserAnalytics do  def track_signup(user), do: # analytics logicend

TypeScript Pattern

typescript
// BAD - Multiple responsibilitiesclass UserComponent {  render() { /* UI */ }  fetchData() { /* API */ }  formatDate() { /* Formatting */ }  validateInput() { /* Validation */ }}
// GOOD - Single responsibilityfunction UserProfile({ user }: Props) {  return <View>{/* UI only */}</View>;}
function useUserData(id: string) {  // Data fetching only}
function formatUserDate(date: Date): string {  // Formatting only}

Ask yourself: "What is the ONE thing this module does?"

2. Open/Closed Principle (OCP)

Software entities should be open for extension, closed for modification.

Elixir Pattern (Behaviours)

elixir
# Define interfacedefmodule PaymentProvider do  @callback process_payment(amount :: Money.t(), token :: String.t()) ::    {:ok, transaction :: map()} | {:error, reason :: String.t()}end
# Implementations extend without modifyingdefmodule StripeProvider do  @behaviour PaymentProvider  def process_payment(amount, token), do: # Stripe logicend
defmodule PayPalProvider do  @behaviour PaymentProvider  def process_payment(amount, token), do: # PayPal logicend
# Usage - add new providers without changing this codedef charge(provider_module, amount, token) do  provider_module.process_payment(amount, token)end

TypeScript Pattern (Composition)

typescript
// BAD - Requires modification for new typesfunction renderItem(item: Item) {  if (item.type === 'gig') {    return <TaskCard />;  } else if (item.type === 'shift') {    return <WorkPeriodCard />;  }  // Have to modify this function for new types}
// GOOD - Extension through propsinterface CardRenderer {  (item: Item): ReactElement;}
const renderers: Record<string, CardRenderer> = {  gig: (item) => <TaskCard gig={item} />,  shift: (item) => <WorkPeriodCard shift={item} />,  // Add new types here without modifying renderItem};
function renderItem(item: Item) {  const renderer = renderers[item.type];  return renderer ? renderer(item) : <DefaultCard item={item} />;}

Ask yourself: "Can I add new functionality without changing existing code?"

3. Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types

Elixir Pattern (LSP)

elixir
# BAD - Violates LSP (raises when base type would return)defmodule PaymentCalculator do  def calculate_total(items) when length(items) > 0 do    Enum.sum(items)  end  # Missing clause - raises on empty listend
# GOOD - Honors contractdefmodule PaymentCalculator do  def calculate_total(items) when is_list(items) do    Enum.sum(items)  # Returns 0 for empty list  endend

TypeScript Pattern (LSP)

typescript
// BAD - Violates LSPclass Bird {  fly(): void { /* flies */ }}
class Penguin extends Bird {  fly(): void {    throw new Error('Penguins cannot fly');  // Breaks contract  }}
// GOOD - Correct abstractioninterface Bird {  move(): void;}
class FlyingBird implements Bird {  move(): void { this.fly(); }  private fly(): void { /* flies */ }}
class SwimmingBird implements Bird {  move(): void { this.swim(); }  private swim(): void { /* swims */ }}

Ask yourself: "Can I replace this with its parent/interface without breaking behavior?"

4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they don't use.

Elixir Pattern (ISP)

elixir
# BAD - Fat interfacedefmodule User do  @callback work() :: :ok  @callback take_break() :: :ok  @callback eat_lunch() :: :ok  @callback clock_in() :: :ok  @callback clock_out() :: :ok  # Not all users need all theseend
# GOOD - Segregated interfacesdefmodule Workable do  @callback work() :: :okend
defmodule Breakable do  @callback take_break() :: :okend
defmodule TimeTrackable do  @callback clock_in() :: :ok  @callback clock_out() :: :okend
# Implement only what you needdefmodule ContractUser do  @behaviour Workable  def work(), do: :ok  # No time tracking neededend

TypeScript Pattern (ISP)

typescript
// BAD - Fat interfaceinterface User {  work(): void;  takeBreak(): void;  clockIn(): void;  clockOut(): void;  receiveBenefits(): void;  // Not all users need all methods}
// GOOD - Segregated interfacesinterface Workable {  work(): void;}
interface TimeTrackable {  clockIn(): void;  clockOut(): void;}
interface BenefitsEligible {  receiveBenefits(): void;}
// Compose only what you needtype FullTimeUser = Workable & TimeTrackable & BenefitsEligible;type ContractUser = Workable & TimeTrackable;type TaskUser = Workable;

Ask yourself: "Does this interface force implementations to define unused methods?"

5. Dependency Inversion Principle (DIP)

Depend on abstractions, not concretions

Elixir Pattern (DIP)

elixir
# BAD - Direct dependency on implementationdefmodule UserService do  def create_user(attrs) do    PostgresRepo.insert(attrs)  # Tightly coupled  endend
# GOOD - Depend on abstractiondefmodule UserService do  def create_user(attrs, repo \\ YourApp.Repo) do    repo.insert(attrs)  # Can inject any Repo implementation  endend
# Even better - use behaviourdefmodule UserService do  @callback create_user(attrs :: map()) :: {:ok, User.t()} | {:error, term()}end
defmodule PostgresUserService do  @behaviour UserService  def create_user(attrs), do: Repo.insert(User.changeset(attrs))end
# Application config determines implementationconfig :yourapp, :user_service, PostgresUserService

TypeScript Pattern (DIP)

typescript
// BAD - Direct dependencyclass UserManager {  private api = new StripeAPI();  // Tightly coupled
  async processPayment(amount: number) {    return this.api.charge(amount);  }}
// GOOD - Depend on abstractioninterface PaymentAPI {  charge(amount: number): Promise<Transaction>;}
class UserManager {  constructor(private paymentAPI: PaymentAPI) {}  // Injected
  async processPayment(amount: number) {    return this.paymentAPI.charge(amount);  }}
// Usageconst stripeAPI: PaymentAPI = new StripeAPI();const manager = new UserManager(stripeAPI);

Ask yourself: "Can I swap implementations without changing dependent code?"

Application Checklist

Before writing new code

  • Identify the single responsibility
  • Design for extension points (behaviours, interfaces)
  • Define abstractions before implementations
  • Keep interfaces minimal and focused

During implementation

  • Each module has ONE reason to change (SRP)
  • New features extend, don't modify (OCP)
  • Implementations honor contracts (LSP)
  • Interfaces are minimal (ISP)
  • Dependencies are injected/configurable (DIP)

During code review

  • Are responsibilities clearly separated?
  • Can we add features without modifying existing code?
  • Do all implementations fulfill their contracts?
  • Are interfaces focused and minimal?
  • Are dependencies abstracted?

Common Violations in Codebase

SRP Violation

  • GraphQL resolvers that also contain business logic (use command handlers)
  • Components that fetch data AND render (use hooks + presentation components)

OCP Violation

  • Long if/else or case statements for types (use behaviours/polymorphism)
  • Hardcoded provider logic (use dependency injection)

LSP Violation

  • Raising exceptions in implementations when base would return nil/error tuple
  • Changing return types between implementations

ISP Violation

  • Fat GraphQL types requiring all fields (use fragments)
  • Monolithic component props (split into focused interfaces)

DIP Violation

  • Direct calls to external services (wrap in behaviours)
  • Hardcoded Repo calls (inject repository)

Integration with Existing Skills

Works with

  • boy-scout-rule: Apply SOLID when improving code
  • test-driven-development: Write tests for each responsibility
  • elixir-code-quality-enforcer: Credo enforces some SOLID principles
  • typescript-code-quality-enforcer: TypeScript interfaces support ISP/DIP

Remember

SOLID is about managing dependencies and responsibilities, not about creating more code.

Good design emerges from applying these principles pragmatically, not dogmatically.

Source and attribution

Source:thebushidocollective/haninplugins/core/skills/solid-principlesat commit19caa51

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal