Claude-skill-registry-data minimal-abstractions
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/minimal-abstractions" ~/.claude/skills/majiayu000-claude-skill-registry-data-minimal-abstractions && rm -rf "$T"
data/minimal-abstractions/SKILL.mdMinimal Abstractions
Quick Start
Before adding a new abstraction (interface, abstract class, wrapper, layer), ask:
- Does a similar abstraction already exist in this project?
- Are there 2+ concrete use cases requiring this abstraction RIGHT NOW?
- Is the complexity cost justified by actual flexibility needs?
If any answer is "no" or "maybe" → Don't add it. Use the simplest solution that works.
Table of Contents
- When to Use This Skill
- What This Skill Does
- Core Philosophy
- Abstraction Evaluation Checklist
- Detection Patterns (Red Flags)
- Examples: Good vs Over-Engineered
- Integration with Architecture Validation
- Expected Outcomes
- Red Flags to Avoid
When to Use This Skill
Explicit Triggers (User asks):
- "Is this abstraction necessary?"
- "Are we over-engineering this?"
- "Too many layers in this code"
- "Simplify this architecture"
- "Reduce complexity"
- "Do we need this interface/wrapper/layer?"
- "Should we use [design pattern]?"
Implicit Triggers (Autonomous invocation):
- Reviewing pull requests with new interfaces/abstract classes
- Architecture proposals introducing new layers
- Code reviews where new patterns are introduced
- Refactoring tasks aimed at simplification
- When a new abstraction is proposed and only one implementation exists
Debugging/Problem Triggers:
- Maintenance burden is high due to abstraction complexity
- Team members confused by excessive indirection
- Tests are difficult to write due to layering
- Simple changes require touching multiple abstraction layers
What This Skill Does
This skill provides a systematic framework for:
- Evaluating whether new abstractions are warranted
- Detecting over-engineering and unnecessary complexity
- Guiding developers to use existing project abstractions
- Preventing abstraction proliferation
- Simplifying code by removing unneeded layers
Instructions
When evaluating an abstraction:
- Search for existing abstractions - Use Grep/Read to find similar patterns in codebase
- Apply the evaluation checklist - Run through all 5 questions in section 4
- Check for red flags - Scan code for patterns in section 5
- Recommend simpler alternative - Provide concrete code example
- Document decision - Explain why abstraction is/isn't needed
Core Philosophy
The Abstraction Principle
Abstractions are not free - every interface, wrapper, layer, or pattern adds:
- Cognitive load (developers must understand the abstraction)
- Maintenance burden (more code to test, debug, refactor)
- Indirection cost (harder to trace execution flow)
- Rigidity (abstractions create assumptions that resist change)
When abstractions ARE valuable:
- Multiple concrete implementations exist or are planned imminently
- Decoupling is critical (e.g., domain from infrastructure in Clean Architecture)
- Testability requires dependency injection
- Third-party integrations need isolation
When abstractions are NOT valuable:
- "We might need it someday" (YAGNI - You Aren't Gonna Need It)
- "It's a best practice" (without understanding why)
- Only one implementation exists and no others are planned
- Wrapping for wrapping's sake
Prefer Existing Abstractions
Before creating a new abstraction:
- Search the codebase for similar patterns
- Extend or adapt existing abstractions
- Reuse project-standard patterns (e.g., Repository, Service, Handler)
- Only create new abstractions when existing ones truly don't fit
Abstraction Evaluation Checklist
Use this checklist before adding ANY new abstraction (interface, abstract class, wrapper, layer):
Question 1: Does this abstraction already exist?
- Searched codebase for similar interfaces/abstractions
- Checked project architecture docs for standard patterns
- Reviewed existing layers (domain, application, infrastructure)
- Considered extending existing abstractions instead
Action: If similar abstraction exists → Use it. Don't create a new one.
Question 2: Do we have 2+ concrete implementations RIGHT NOW?
- Count current implementations (not hypothetical future ones)
- Verify implementations are actually different (not just copy-paste)
- Check if "multiple implementations" are really needed
Action: If <2 implementations → Skip the abstraction. Use concrete implementation directly.
Question 3: Is there a concrete business/technical requirement?
- Can you name the specific requirement driving this abstraction?
- Is the requirement current (not speculative)?
- Would removing this abstraction make the code harder to maintain?
Action: If requirement is speculative → Don't build it yet. Wait for actual need.
Question 4: What is the complexity cost?
- Count files touched to add this abstraction
- Estimate lines of code added (interface + implementations + tests)
- Consider cognitive load for new team members
- Evaluate impact on debugging and tracing
Action: If cost > benefit → Simplify. Use direct solution.
Question 5: Can we solve this with simpler patterns?
- Would a simple function work instead of an interface?
- Could dependency injection handle this without abstraction?
- Is this trying to solve a problem we don't have?
Action: If simpler solution exists → Use it.
Detection Patterns (Red Flags)
Red Flag 1: Lonely Interface
# ❌ Over-engineered: Interface with only one implementation class DataProcessor(Protocol): def process(self, data: dict) -> dict: ... class JsonDataProcessor: # Only implementation def process(self, data: dict) -> dict: return transform_json(data)
Why it's a red flag: No actual polymorphism. The interface adds indirection without benefit.
Better approach:
# ✅ Simple: Direct implementation def process_json_data(data: dict) -> dict: return transform_json(data)
Red Flag 2: Wrapper with No Value
# ❌ Over-engineered: Wrapper that just forwards calls class DatabaseWrapper: def __init__(self, db: Database): self._db = db def query(self, sql: str) -> list: return self._db.query(sql) # Just forwarding
Why it's a red flag: No transformation, validation, or added behavior. Pure indirection.
Better approach: Use the database directly or add actual value (caching, retry, validation).
Red Flag 3: Layer for Layer's Sake
# ❌ Over-engineered: Unnecessary service layer class UserRepository: # Already exists def get_user(self, id: int) -> User: ... class UserService: # Adds nothing def __init__(self, repo: UserRepository): self.repo = repo def get_user(self, id: int) -> User: return self.repo.get_user(id) # Just forwarding
Why it's a red flag: Service layer adds no business logic, validation, or orchestration.
Better approach: Use repository directly until business logic is needed.
Red Flag 4: Pattern for Pattern's Sake
# ❌ Over-engineered: Factory for single type class UserFactory: @staticmethod def create_user(name: str, email: str) -> User: return User(name=name, email=email)
Why it's a red flag: Factory pattern used without variation or complexity justification.
Better approach:
# ✅ Simple: Direct construction user = User(name="Alice", email="alice@example.com")
Red Flag 5: Premature Generalization
# ❌ Over-engineered: Generic solution for specific problem class ConfigLoader(Generic[T]): def load(self, source: str, parser: Parser[T]) -> T: ... class JsonParser(Parser[dict]): ... class YamlParser(Parser[dict]): ...
Why it's a red flag: Generic abstraction built before knowing actual requirements.
Better approach: Start with simple JSON config loader. Generalize when second format is needed.
Examples: Good vs Over-Engineered
Example 1: Repository Pattern
Over-Engineered:
# Unnecessary: Abstract repository + generic base + implementation class Repository(Protocol, Generic[T]): def get(self, id: int) -> T: ... def save(self, entity: T) -> None: ... class BaseRepository(Generic[T]): # Generic base def validate(self, entity: T) -> bool: ... class UserRepository(BaseRepository[User]): # Concrete def get(self, id: int) -> User: ... def save(self, user: User) -> None: ...
Right-Sized:
# Clean Architecture: Protocol in domain, implementation in infrastructure # domain/repositories.py class UserRepository(Protocol): # Interface for dependency inversion def get_user(self, id: int) -> User: ... def save_user(self, user: User) -> None: ... # infrastructure/repositories.py class SqlUserRepository: # Concrete implementation def get_user(self, id: int) -> User: ... def save_user(self, user: User) -> None: ...
Example 2: Service Layer
Over-Engineered:
# Unnecessary: Service that just forwards to repository class UserService: def __init__(self, repo: UserRepository): self.repo = repo def get_user(self, id: int) -> User: return self.repo.get_user(id) # No business logic!
Right-Sized:
# Use repository directly until business logic emerges class AuthenticationHandler: def __init__(self, user_repo: UserRepository): self.user_repo = user_repo def authenticate(self, email: str, password: str) -> Result[User, AuthError]: user = self.user_repo.get_user_by_email(email) if not user: return Err(AuthError.USER_NOT_FOUND) if not verify_password(password, user.password_hash): return Err(AuthError.INVALID_PASSWORD) return Ok(user)
Example 3: Reusing Existing Abstractions
Over-Engineered:
# Project already has Repository pattern # Adding NEW abstraction for similar purpose: class DataAccessLayer(Protocol): # Duplicates Repository! def fetch(self, id: int) -> Entity: ... def persist(self, entity: Entity) -> None: ...
Right-Sized:
# Use existing Repository pattern class ProductRepository(Protocol): # Follows project convention def get_product(self, id: int) -> Product: ... def save_product(self, product: Product) -> None: ...
Integration with Architecture Validation
This skill complements existing architecture validation skills:
Use with:
- Check layer boundaries while avoiding unnecessary layersarchitecture-validate-architecture
- Ensure layers are necessary and well-justifiedarchitecture-validate-layer-boundaries
- Evaluate abstractions during PR reviewquality-code-review
Integration pattern:
- Run architecture validation to check existing patterns
- Use minimal-abstractions to evaluate NEW abstractions
- Ensure new code follows project patterns (don't reinvent)
Expected Outcomes
Successful Simplification
Before:
src/ ├── domain/ │ ├── interfaces/user_repository.py │ ├── interfaces/user_service.py │ ├── interfaces/user_validator.py ├── application/ │ ├── services/user_service.py (forwards to repo) │ ├── validators/user_validator.py (just calls validate()) ├── infrastructure/ │ ├── repositories/user_repository.py
After (applying minimal-abstractions):
src/ ├── domain/ │ ├── repositories.py (UserRepository protocol) │ ├── models.py (User with validation) ├── application/ │ ├── handlers.py (CreateUserHandler with actual business logic) ├── infrastructure/ │ ├── repositories.py (SqlUserRepository)
Metrics:
- 40% fewer files
- 60% less indirection
- Same functionality
- Clearer execution paths
Validation Output
Abstraction Evaluation: ProductService ✅ Checklist Results: ❌ Does abstraction already exist? YES - Repository pattern exists ❌ 2+ implementations? NO - Only one service planned ❌ Concrete requirement? NO - "We might need microservices later" ⚠️ Complexity cost: +3 files, +200 LOC, +2 layers indirection ✅ Simpler solution exists? YES - Use repository + handler directly Recommendation: SKIP THIS ABSTRACTION - Use existing ProductRepository - Add business logic to ProductHandler - Wait for concrete multi-service requirement before abstracting
Red Flags to Avoid
Anti-Patterns
- ❌ "We might need it later" (YAGNI violation)
- ❌ Creating interfaces with only one implementation
- ❌ Wrappers that just forward calls without adding value
- ❌ Service layers that add no business logic
- ❌ Generic solutions for specific problems
- ❌ Design patterns used without understanding why
- ❌ Creating new abstractions when project patterns exist
Good Practices
- ✅ Use existing project abstractions first
- ✅ Wait for 2+ concrete implementations before abstracting
- ✅ Justify every layer with concrete requirements
- ✅ Prefer simple, direct solutions
- ✅ Question every new abstraction
- ✅ Measure complexity cost vs benefit
- ✅ Remove abstractions when they're no longer needed
Notes
Key Principle: Every abstraction must justify its existence with concrete, current requirements - not hypothetical future needs.
Balance: This skill advocates for minimal abstractions, but respects architectural patterns when they provide real value (e.g., Clean Architecture's dependency inversion).
When in doubt: Start simple. Add abstractions when pain points emerge, not before.