Solid Principles

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

作者 thebushidocollective19caa51d5fd1無授權條款198 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫4 週前更新

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

AI 產生的概覽

指導在 Elixir 與 TypeScript 的模組、函式與元件設計中套用 SOLID 設計原則。

功能
此技能提供套用 SOLID 五大原則的參考指引:單一職責、開放封閉、里氏替換、介面隔離與依賴反轉。它以 Elixir 與 TypeScript 的反例與正例說明每項原則,並為每項原則附上一個自檢問題。它也包含撰寫程式前、實作期間與程式碼審查時的套用清單,並列出程式庫中常見的違規情況。
適用情境
在設計需要可維護、具彈性架構的模組、函式或元件時,於實作階段使用。它也適合在程式碼審查中檢查職責分離、擴充點、契約履行、介面聚焦與依賴抽象。
執行需求
不需要指令碼或套件,僅為純說明性參考。代理需要讀取、編輯、grep 與 glob 工具,以便在程式庫中查閱並套用這些指引。

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.

來源與署名

來源:thebushidocollective/han位於plugins/core/skills/solid-principles提交19caa51

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架