Object-oriented design principles including object calisthenics, dependency inversion, fail-fast error handling, feature envy detection, and intention-revealing naming. Triggers on: writing new classes or functions, refactoring, code review, 'clean up', method longer than 10 lines, feature envy, primitive obsession, deep nesting.
npx skills add https://github.com/NTCoding/claude-skillz --skill software-design-principles
Professional software design patterns and principles for writing maintainable, well-structured code.
🚨 Fail-fast over silent fallbacks. Never use fallback chains (value ?? backup ?? 'unknown'). If data should exist, validate and throw a clear error.
🚨 Strive for maximum type-safety. No any. No as. Type escape hatches defeat TypeScript's purpose. There's always a type-safe solution.
🚨 Make illegal states unrepresentable. Use discriminated unions, not optional fields. If a state combination shouldn't exist, make the type system forbid it.
🚨 Inject dependencies, don't instantiate. No new SomeService() inside methods. Pass dependencies through constructors.
🚨 Intention-revealing names only. Never use data, utils, helpers, handler, processor. Name things for what they do in the domain.
🚨 No code comments. Comments are a failure to express intent in code. If you need a comment to explain what code does, the code isn't clear enough—refactor it.
🚨 Use Zod for runtime validation. In TypeScript, use Zod schemas for parsing external data, API responses, and user input. Type inference from schemas keeps types and validation in sync.
Well-designed, maintainable code is far more important than getting things done quickly. Every design decision should favor:
Never write comments - write expressive code instead.
Apply object calisthenics principles:
Method uses another class's data more than its own? Move it there.
// ❌ FEATURE ENVY - obsessed with Order's data
class InvoiceGenerator {
generate(order: Order): Invoice {
const total = order.getItems().map(i => i.getPrice() * i.getQuantity()).reduce((a,b) => a+b, 0)
return new Invoice(total + total * order.getTaxRate() + order.calculateShipping())
}
}
// ✅ Move logic to the class it envies
class Order {
calculateTotal(): number { /* uses this.items, this.taxRate */ }
}
class InvoiceGenerator {
generate(order: Order): Invoice { return new Invoice(order.calculateTotal()) }
}
Detection: Count external vs own references. More external? Feature envy.
Don't instantiate dependencies inside methods. Inject them.
// ❌ TIGHT COUPLING
class OrderProcessor {
process(order: Order): void {
const validator = new OrderValidator() // Hard to test/change
const emailer = new EmailService() // Hidden dependency
}
}
// ✅ LOOSE COUPLING
class OrderProcessor {
constructor(private validator: OrderValidator, private emailer: EmailService) {}
process(order: Order): void {
this.validator.isValid(order) // Injected, mockable
this.emailer.send(...) // Explicit dependency
}
}
Scan for: new X() inside methods, static method calls. Extract to constructor.
NEVER use fallback chains:
value ?? backup ?? default ?? 'unknown' // ❌
Validate and throw clear errors instead:
// ❌ SILENT FAILURE - hides problems
return content.eventType ?? content.className ?? 'Unknown'
// ✅ FAIL FAST - immediate, debuggable
if (!content.eventType) {
throw new Error(`Expected 'eventType', got undefined. Keys: [${Object.keys(content)}]`)
}
return content.eventType
Error format: Expected [X]. Got [Y]. Context: [debugging info]
Principle: Use business domain terminology and intention-revealing names. Never use generic programmer jargon.
NEVER use these names:
datautilshelperscommonsharedmanagerhandlerprocessorThese names are meaningless - they tell you nothing about what the code actually does.
Instead of generic names, use specific domain language:
// ❌ GENERIC - meaningless
class DataProcessor {
processData(data: any): any {
const utils = new DataUtils()
return utils.transform(data)
}
}
// ✓ INTENTION-REVEALING - clear purpose
class OrderTotalCalculator {
calculateTotal(order: Order): Money {
return taxCalculator.applyTax(order.subtotal, order.taxRate)
}
}
For classes:
For methods:
For variables:
When you encounter generic names:
Principle: Follow Scott Wlaschin's type-driven approach to domain modeling. Express domain concepts using the type system.
Use types to encode business rules:
// ❌ PRIMITIVE OBSESSION - illegal states possible
interface Order {
status: string // Could be any string
shippedDate: Date | null // Could be set when status != 'shipped'
}
// ✓ TYPE-SAFE - illegal states impossible
type UnconfirmedOrder = { type: 'unconfirmed', items: Item[] }
type ConfirmedOrder = { type: 'confirmed', items: Item[], confirmationNumber: string }
type ShippedOrder = { type: 'shipped', items: Item[], confirmationNumber: string, shippedDate: Date }
type Order = UnconfirmedOrder | ConfirmedOrder | ShippedOrder
STRICTLY FORBIDDEN without explicit user approval:
any typeas type assertions (as unknown as, as any, as SomeType)@ts-ignore / @ts-expect-errorThere is always a better type-safe solution. These make code unsafe and defeat TypeScript's purpose.
// ✓ TYPE-SAFE - validates at compile time
type PositiveNumber = number & { __brand: 'positive' }
function createPositive(value: number): PositiveNumber {
if (value <= 0) {
throw new Error(`Expected positive number, got ${value}`)
}
return value as PositiveNumber
}
// Can only be called with validated positive numbers
function calculateDiscount(price: PositiveNumber, rate: number): Money {
// price is guaranteed positive by type system
}
Principle: Default to immutable data. Mutation is a source of bugs—unexpected changes, race conditions, and difficult debugging.
// MUTABLE - hard to reason about
function processOrder(order: Order): void {
order.status = 'processing' // Mutates input!
order.items.push(freeGift) // Side effect!
}
// Caller has no idea their object changed
const myOrder = getOrder()
processOrder(myOrder)
// myOrder is now different - surprise!
// IMMUTABLE - predictable
function processOrder(order: Order): Order {
return {
...order,
status: 'processing',
items: [...order.items, freeGift]
}
}
// Caller controls what happens
const myOrder = getOrder()
const processedOrder = processOrder(myOrder)
// myOrder unchanged, processedOrder is new
const over let...) over mutationmap/filter/reduce over forEach with mutationPrinciple: Don't build features until they're actually needed. Speculative code is waste—it costs time to write, time to maintain, and is often wrong when requirements become clear.
// YAGNI VIOLATION - over-engineered for "future" needs
interface PaymentProcessor {
process(payment: Payment): Result
refund(payment: Payment): Result
partialRefund(payment: Payment, amount: Money): Result
schedulePayment(payment: Payment, date: Date): Result
recurringPayment(payment: Payment, schedule: Schedule): Result
// ... 10 more methods "we might need"
}
// Only ONE method is actually used today
STOP if you're about to:
?? chains → fail fast with clear error insteadany or as → fix the types, not the symptomsnew X() inside a method → inject through constructordata, utils, handler → use domain languageExecute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping
Comprehensive GitHub code review with AI-powered swarm coordination
Create high-quality git commits: review/stage intended changes, split into logical commits, and write clear commit messages (including Conventional Commits). Use when the user asks to commit, craft a commit message, stage changes, or split work into multiple commits.
Comprehensive truth scoring, code quality verification, and automatic rollback system with 0.95 accuracy threshold for ensuring high-quality agent outputs and codebase reliability.
GitHub CLI (gh) comprehensive reference for repositories, issues, pull requests, Actions, projects, releases, gists, codespaces, organizations, extensions, and all GitHub operations from the command line.
GitHub CLI - manage repositories, issues, pull requests, actions, releases, and more from the command line.
You are a code refactoring expert specializing in clean code principles, SOLID design patterns, and modern software engineering best practices. Analyze and refactor the provided code to improve its quality, maintainability, and performance.
You are a technical debt expert specializing in identifying, quantifying, and prioritizing technical debt in software projects. Analyze the codebase to uncover debt, assess its impact, and create acti
Take ntcoding/software-design-principles from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.