
Solid Principles
- 460 installs
- 186 repo stars
- Updated July 19, 2026
- thebushidocollective/han
solid-principles is an agent skill that applies the five SOLID design principles during implementation for developers designing maintainable modules, functions, and components in object-oriented codebases.
About
solid-principles is a thebushidocollective/han agent skill that enforces Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion while designing modules, functions, and components. It is user-invocable false, activating during implementation when architecture needs clearer boundaries, and includes Elixir pattern examples contrasting multi-responsibility modules with focused alternatives. Allowed tools are Read, Edit, Grep, and Glob so agents inspect and refactor existing code rather than generating greenfield scaffolds alone. Developers reach for solid-principles when splitting god objects, introducing interfaces, or reviewing service classes that mix persistence, messaging, and analytics concerns. The skill emphasizes one reason to change per module and dependency inversion through explicit abstractions applicable beyond Elixir to typical backend service code. Each principle section explains failure modes—such as modules that create users, send email, log analytics, and update cache—and shows refactors that isolate responsibilities behind narrow interfaces.
- solid-principles
Solid Principles by the numbers
- 460 all-time installs (skills.sh)
- +28 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #926 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thebushidocollective/han --skill solid-principlesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 460 |
|---|---|
| repo stars | ★ 186 |
| Last updated | July 19, 2026 |
| Repository | thebushidocollective/han ↗ |
How do you apply SOLID principles during implementation?
Use solid-principles for development tasks
Who is it for?
Backend engineers refactoring service classes or modules that mix multiple reasons to change in one file.
Skip if: Greenfield projects needing product requirements, infrastructure provisioning, or language-agnostic algorithm puzzles without design concerns.
When should I use this skill?
The user designs modules, services, or components and needs SOLID guidance on responsibilities, interfaces, or dependency direction.
What you get
Refactored modules with separated responsibilities, interface boundaries, and SOLID-aligned architecture notes.
By the numbers
- Documents all 5 SOLID design principles with implementation patterns
- Includes Elixir code examples for responsibility splitting and module design
Files
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
# BAD - Multiple responsibilities
defmodule UserManager do
def create_user(attrs) do
# Creates user
# Sends welcome email
# Logs to analytics
# Updates cache
end
end
# GOOD - Single responsibility
defmodule User do
def create(attrs), do: Repo.insert(changeset(attrs))
end
defmodule UserNotifier do
def send_welcome_email(user), do: # email logic
end
defmodule UserAnalytics do
def track_signup(user), do: # analytics logic
endTypeScript Pattern
// BAD - Multiple responsibilities
class UserComponent {
render() { /* UI */ }
fetchData() { /* API */ }
formatDate() { /* Formatting */ }
validateInput() { /* Validation */ }
}
// GOOD - Single responsibility
function 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)
# Define interface
defmodule PaymentProvider do
@callback process_payment(amount :: Money.t(), token :: String.t()) ::
{:ok, transaction :: map()} | {:error, reason :: String.t()}
end
# Implementations extend without modifying
defmodule StripeProvider do
@behaviour PaymentProvider
def process_payment(amount, token), do: # Stripe logic
end
defmodule PayPalProvider do
@behaviour PaymentProvider
def process_payment(amount, token), do: # PayPal logic
end
# Usage - add new providers without changing this code
def charge(provider_module, amount, token) do
provider_module.process_payment(amount, token)
endTypeScript Pattern (Composition)
// BAD - Requires modification for new types
function 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 props
interface 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)
# 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 list
end
# GOOD - Honors contract
defmodule PaymentCalculator do
def calculate_total(items) when is_list(items) do
Enum.sum(items) # Returns 0 for empty list
end
endTypeScript Pattern (LSP)
// BAD - Violates LSP
class Bird {
fly(): void { /* flies */ }
}
class Penguin extends Bird {
fly(): void {
throw new Error('Penguins cannot fly'); // Breaks contract
}
}
// GOOD - Correct abstraction
interface 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)
# BAD - Fat interface
defmodule 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 these
end
# GOOD - Segregated interfaces
defmodule Workable do
@callback work() :: :ok
end
defmodule Breakable do
@callback take_break() :: :ok
end
defmodule TimeTrackable do
@callback clock_in() :: :ok
@callback clock_out() :: :ok
end
# Implement only what you need
defmodule ContractUser do
@behaviour Workable
def work(), do: :ok
# No time tracking needed
endTypeScript Pattern (ISP)
// BAD - Fat interface
interface User {
work(): void;
takeBreak(): void;
clockIn(): void;
clockOut(): void;
receiveBenefits(): void;
// Not all users need all methods
}
// GOOD - Segregated interfaces
interface Workable {
work(): void;
}
interface TimeTrackable {
clockIn(): void;
clockOut(): void;
}
interface BenefitsEligible {
receiveBenefits(): void;
}
// Compose only what you need
type 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)
# BAD - Direct dependency on implementation
defmodule UserService do
def create_user(attrs) do
PostgresRepo.insert(attrs) # Tightly coupled
end
end
# GOOD - Depend on abstraction
defmodule UserService do
def create_user(attrs, repo \\ YourApp.Repo) do
repo.insert(attrs) # Can inject any Repo implementation
end
end
# Even better - use behaviour
defmodule 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 implementation
config :yourapp, :user_service, PostgresUserServiceTypeScript Pattern (DIP)
// BAD - Direct dependency
class UserManager {
private api = new StripeAPI(); // Tightly coupled
async processPayment(amount: number) {
return this.api.charge(amount);
}
}
// GOOD - Depend on abstraction
interface PaymentAPI {
charge(amount: number): Promise<Transaction>;
}
class UserManager {
constructor(private paymentAPI: PaymentAPI) {} // Injected
async processPayment(amount: number) {
return this.paymentAPI.charge(amount);
}
}
// Usage
const 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 codetest-driven-development: Write tests for each responsibilityelixir-code-quality-enforcer: Credo enforces some SOLID principlestypescript-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.
Related skills
FAQ
Which SOLID principles does solid-principles cover?
solid-principles documents all five principles—Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion—with implementation guidance and Elixir examples showing how to separate mixed responsibilities.
When does solid-principles activate automatically?
solid-principles is user-invocable false and triggers during implementation when designing modules, functions, or components that need SOLID-aligned maintainability, using Read, Edit, Grep, and Glob to inspect and refactor code.