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 从公开仓库中收录这些内容。

举报或申请下架