Evaluates and prevents unnecessary abstractions by analyzing interfaces, layers, and patterns against concrete requirements...
Before adding a new abstraction (interface, abstract class, wrapper, layer), ask:
If any answer is "no" or "maybe" ā Don't add it. Use the simplest solution that works.
Explicit Triggers (User asks):
Implicit Triggers (Autonomous invocation):
Debugging/Problem Triggers:
This skill provides a systematic framework for:
When evaluating an abstraction:
Abstractions are not free - every interface, wrapper, layer, or pattern adds:
When abstractions ARE valuable:
When abstractions are NOT valuable:
Before creating a new abstraction:
Use this checklist before adding ANY new abstraction (interface, abstract class, wrapper, layer):
Action: If similar abstraction exists ā Use it. Don't create a new one.
Action: If <2 implementations ā Skip the abstraction. Use concrete implementation directly.
Action: If requirement is speculative ā Don't build it yet. Wait for actual need.
Action: If cost > benefit ā Simplify. Use direct solution.
Action: If simpler solution exists ā Use it.
# ā 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)
# ā 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).
# ā 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.
# ā 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")
# ā 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.
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: ...
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)
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: ...
This skill complements existing architecture validation skills:
Use with:
architecture-validate-architecture - Check layer boundaries while avoiding unnecessary layersarchitecture-validate-layer-boundaries - Ensure layers are necessary and well-justifiedquality-code-review - Evaluate abstractions during PR reviewIntegration pattern:
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:
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
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.