Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add ryanthedev/code-foundations --skill "cc-routine-and-class-design"
Install specific skill from multi-skill repository
# Description
Evaluate routine and class design quality using Code Complete checklists (43 items). Use when designing routines or classes, reviewing class interfaces, choosing between inheritance and containment, or evaluating routine cohesion. Also trigger when tempted to use inheritance as a quick fix under deadline pressure, or when rationalizing 'but it works' for code with deep inheritance or many parameters. Produce severity-tagged reviews (VIOLATION/WARNING/PASS) in CHECKER mode or design decisions in APPLIER mode. Symptoms: vague routine names, >7 parameters, deep inheritance, mixed abstraction levels.
# SKILL.md
name: cc-routine-and-class-design
description: "Evaluate routine and class design quality using Code Complete checklists (43 items). Use when designing routines or classes, reviewing class interfaces, choosing between inheritance and containment, or evaluating routine cohesion. Also trigger when tempted to use inheritance as a quick fix under deadline pressure, or when rationalizing 'but it works' for code with deep inheritance or many parameters. Produce severity-tagged reviews (VIOLATION/WARNING/PASS) in CHECKER mode or design decisions in APPLIER mode. Symptoms: vague routine names, >7 parameters, deep inheritance, mixed abstraction levels."
Skill: cc-routine-and-class-design
STOP - Crisis Invariants (NEVER SKIP)
| Check | Time | Why Non-Negotiable |
|---|---|---|
| LSP Test - "Is 'A is a B' literally true?" | 30 sec | Wrong inheritance creates debugging hell |
| Containment Default - If LSP feels wrong, use containment | 30 sec | Containment is fixable; inheritance requires architecture changes |
| Parameter Count - If >7 parameters, interface is wrong | 15 sec | High parameter count predicts interface errors |
Prerequisites
This skill assumes:
- Object-oriented programming with class-based inheritance
- Mutable state (objects that change over time)
- Synchronous or simple async patterns
For functional programming, prototype-based inheritance, or heavily concurrent code, adapt principles rather than applying literally. See "Pattern-Specific Guidance" section.
When NOT to Use
- Scripting/automation code - One-off scripts don't benefit from class design rigor
- Prototyping phase - When exploring ideas before committing to design (time-box to max 1 week)
- Simple data transfer objects - Pure DTOs without behavior are exempt from ADT requirements (but DTOs with validation, toString, equals are NOT exempt)
- Framework-mandated patterns - When framework REQUIRES inheritance to function (e.g., Android Activity won't work without extending Activity). "Framework supports inheritance" or "framework examples use inheritance" is NOT mandated
- Performance-critical inner loops - Where accessor overhead matters (measure first - must show profiling data)
- Test doubles (mocks, stubs, fakes) - Test code intentionally violates design rules; empty overrides and minimal abstractions are appropriate
Crisis Invariants - NEVER SKIP
These checks are NON-NEGOTIABLE regardless of deadline pressure:
| Check | Time | Why Non-Negotiable |
|---|---|---|
| LSP Test - "Is 'A is a B' literally true?" | 30 sec | Wrong inheritance creates debugging hell that costs MORE time than it saves |
| Containment Default - If LSP feels like "purity theater," use containment | 30 sec | Containment problems are fixable; inheritance problems require architecture changes |
| Parameter Count - If >7 parameters, interface is wrong | 15 sec | High parameter count predicts interface errors |
Why these three? Violations create problems that CANNOT be easily fixed post-crisis. They require architectural changes, not patches.
If you're rationalizing "I'll fix the design after the crisis": Post-crisis "cleanup" rarely happens. Technical debt from wrong inheritance is architectural, not patchable.
Modes
CHECKER
Purpose: Execute design checklists against routines and classes
Triggers:
- "review my class design"
- "check routine quality"
- "audit class interfaces"
- "evaluate cohesion"
Non-Triggers:
- "how should I design this class" -> APPLIER
- "should I use inheritance here" -> APPLIER
- "refactor this routine" -> cc-refactoring-guidance
Checklist: See checklists.md
Output Format:
| Item | Status | Evidence | Location |
|------|--------|----------|----------|
Severity Classification Rubric:
| Severity | Criteria | Example |
|---|---|---|
| VIOLATION | Explicitly fails checklist item | 12 parameters (> 7 limit) |
| VIOLATION | Breaks LSP/encapsulation | Empty override, protected base data |
| WARNING | Near limit, needs justification | 8 parameters, 3-level inheritance |
| WARNING | Subjective concern | Questionable abstraction consistency |
| PASS | Meets or exceeds requirement | Clear cohesion, ≤7 parameters |
Note: Test suite status is IRRELEVANT to checker results. A class can pass all tests and still have VIOLATION-level design issues that predict future maintenance problems.
APPLIER
Purpose: Guide class interface design, inheritance decisions, and routine creation
Triggers:
- "how should I design this class"
- "should I use inheritance or containment"
- "what cohesion type is this routine"
- "how many parameters is too many"
- "should I extract this to a routine"
Non-Triggers:
- "review my class" -> CHECKER
- "check this routine" -> CHECKER
- "optimize this code" -> cc-performance-tuning
Produces: Class interface designs, inheritance/containment decisions, routine signatures, cohesion classifications
Constraints:
- [p.133] Default to containment; only inherit if "is-a" is literally true (LSP)
- [p.143] Inheritance depth: target <3 levels, WARNING at 3, VIOLATION at 4+, SEVERE at 6+
- [p.178] Parameter limit: 7 maximum (8+ is VIOLATION requiring redesign)
- [p.168] Target functional cohesion for routines
- [p.139] Asking "What should this class hide?" drives good design
Parameter Guidelines
Threshold Table:
| Count | Status | Action |
|---|---|---|
| 1-5 | PASS | No action needed |
| 6-7 | PASS | Minor concern, document if unusual |
| 8-9 | WARNING | Justify in code review OR redesign |
| 10+ | VIOLATION | Must redesign - use Parameter Object or split responsibilities |
"About 7" means: 7 is the limit. 8 is over. Count ALL parameters including optional ones with defaults. Variadic args (*args/...) count as 1.
Ordering convention (implies data flow sequence - ORDER MATTERS):
1. Input-only parameters first
2. Input-and-output (modify) parameters second
3. Output-only parameters third
Key Definitions
LSP (Liskov Substitution Principle)
If A inherits from B, then everywhere code uses B, you can substitute A without breaking anything.
Test: Does calling every method on the base class make sense for the derived class?
Inheritance requires BOTH:
1. Semantic test: "A is a B" makes English sense to domain experts
- YES: "Dog is an Animal", "Manager is an Employee"
- NO: "EmployeeCensus is a ListContainer", "UserSession is a Logger"
2. LSP test: Every method of B works correctly when A is substituted
- NO empty overrides (override to do nothing)
- NO exceptions that base class doesn't throw
- NO precondition strengthening
If EITHER fails: use containment instead.
Functional Cohesion
Routine performs one and only one operation.
Name Test: If you need "and" or "then" in the name to describe what the routine does, it has multiple operations.
- PASS: ValidateUserInput(), CalculateTotalPrice(), SendWelcomeEmail()
- FAIL: ValidateAndSaveUser(), ReadFileThenParseJSON(), InitializeAndConnect()
Scope: "One operation" means one action at the routine's declared abstraction level.
- CreateUser() is ONE operation (user creation) even though it involves validation, hashing, insertion
- Those sub-steps are at a LOWER abstraction level
Inheritance vs Containment Decision
Question order rationale: Data-only sharing → containment (simplest). Behavior-only sharing → interface/protocol. Both data and behavior → requires full LSP verification before inheriting.
digraph inheritance_decision {
rankdir=TB;
START [label="How should A relate to B?" shape=doublecircle];
q1 [label="Shares ONLY data?" shape=diamond];
q2 [label="Shares ONLY behavior\n(method signatures)?" shape=diamond];
q3 [label="Shares BOTH data\nand behavior?" shape=diamond];
q4 [label="Is 'A is a B' literally true?\n(LSP: A substitutes for B\neverywhere)" shape=diamond];
contain1 [label="CONTAIN\ncommon object" shape=box];
interface1 [label="USE INTERFACE/PROTOCOL\n(not inheritance)" shape=box];
inherit2 [label="INHERIT" shape=box];
contain2 [label="CONTAIN" shape=box];
none [label="NO RELATION\n(reconsider if relationship needed)" shape=box];
START -> q1;
q1 -> contain1 [label="yes"];
q1 -> q2 [label="no"];
q2 -> interface1 [label="yes"];
q2 -> q3 [label="no"];
q3 -> q4 [label="yes"];
q3 -> none [label="no"];
q4 -> inherit2 [label="yes"];
q4 -> contain2 [label="no"];
}
FINAL CHECK (apply AFTER flowchart determines INHERIT, BEFORE committing to inheritance):
- Depth < 3 levels (definitely < 6)?
- No empty overrides needed?
- All base data private (not protected)?
If ANY answer is NO → use CONTAIN instead.
Cohesion Classification
Classification algorithm: Questions test for highest-quality cohesion first. Stop at first YES answer - that's your classification. This prevents under-classifying (e.g., calling functional cohesion "sequential").
Target: Functional cohesion - routine performs one and only one operation.
digraph cohesion_classification {
rankdir=TB;
START [label="Classify routine cohesion" shape=doublecircle];
q1 [label="Single operation?\n(Name Test passes)" shape=diamond];
q2 [label="Ordered operations\nsharing data step-to-step?" shape=diamond];
q3 [label="Operations share\ndata only?" shape=diamond];
q4 [label="Same-time operations\n(startup/shutdown)?" shape=diamond];
q5 [label="Orchestrates calls\n(not direct work)?" shape=diamond];
q6 [label="Order from external\nrequirement (UI)?" shape=diamond];
q7 [label="Control flag selects\noperation?" shape=diamond];
functional [label="FUNCTIONAL\n[ACCEPT]" shape=box style=filled fillcolor=lightgreen];
sequential [label="SEQUENTIAL\n[ACCEPT w/caution]" shape=box style=filled fillcolor=lightyellow];
communicational [label="COMMUNICATIONAL\n[ACCEPT w/caution]" shape=box style=filled fillcolor=lightyellow];
temporal_ok [label="TEMPORAL\n[ACCEPT]" shape=box style=filled fillcolor=lightgreen];
temporal_fix [label="TEMPORAL\n[FIX: orchestrate]" shape=box style=filled fillcolor=orange];
procedural [label="PROCEDURAL\n[REJECT]" shape=box style=filled fillcolor=lightcoral];
logical [label="LOGICAL\n[REJECT]" shape=box style=filled fillcolor=lightcoral];
coincidental [label="COINCIDENTAL\n[REDESIGN]" shape=box style=filled fillcolor=red fontcolor=white];
START -> q1;
q1 -> functional [label="yes"];
q1 -> q2 [label="no"];
q2 -> sequential [label="yes"];
q2 -> q3 [label="no"];
q3 -> communicational [label="yes"];
q3 -> q4 [label="no"];
q4 -> q5 [label="yes"];
q4 -> q6 [label="no"];
q5 -> temporal_ok [label="yes"];
q5 -> temporal_fix [label="no"];
q6 -> procedural [label="yes"];
q6 -> q7 [label="no"];
q7 -> logical [label="yes"];
q7 -> coincidental [label="no"];
}
Evidence: 50% of highly cohesive routines fault-free vs 18% low cohesion [Card et al. 1986, N=450 routines]. This is MAINTENANCE data, not shipping data - the 50% vs 18% gap appears during modifications.
Cohesion Types Reference
Types listed from BEST (top) to WORST (bottom) quality - ORDER MATTERS:
| Type | Definition | Status | Code Example |
|---|---|---|---|
| Functional | Routine performs one and only one operation | ACCEPT | calculateTax(amount) - returns tax |
| Sequential | Operations share data step-to-step in required order, but don't form complete function | ACCEPT w/caution | parse(input) -> validate(parsed) -> transform(validated) |
| Communicational | Operations use same data but aren't otherwise related | ACCEPT w/caution | printReport(data); emailReport(data) - both use reportData |
| Temporal | Operations combined because done at same time (startup, shutdown) | ACCEPT if orchestrates | startup() calls initDB(), initCache(), initLogger() |
| Procedural | Operations ordered by external requirement (UI flow), not logical relationship | REJECT | showPage1() -> showPage2() -> showPage3() - wizard steps |
| Logical | Control flag selects one of several unrelated operations in big if/case | REJECT | process(mode) with switch between Parse, Validate, Format |
| Coincidental | Operations have no discernible relationship ("chaotic cohesion") | REDESIGN | doStuff() logs, validates, emails, calculates tax |
"ACCEPT w/caution" means:
1. Document in code comment WHY this cohesion type is acceptable here
2. Review whether functional cohesion is achievable with reasonable effort
3. Add TODO if cohesion should improve in future refactoring
"Caution" is not permission to ignore - it's permission with accountability.
Detecting Orchestration Pattern
If description uses these verbs, likely ORCHESTRATION (temporal cohesion OK):
- "orchestrates", "coordinates", "delegates", "dispatches", "routes"
If description uses these verbs, likely DIRECT WORK (check cohesion type):
- "handles", "processes", "performs", "executes", "calculates"
Orchestration Example (GOOD):
def startup():
load_config() # calls another routine
init_database() # calls another routine
start_server() # calls another routine
log_startup_complete()
Direct Work Example (FIX by extracting):
def startup():
config = json.load(open('config.json')) # direct work - extract
db = Database(config['db_url']) # direct work - extract
db.connect() # direct work - extract
server.start(config['port']) # acceptable call
Improving Cohesion
| Type | Improvement Steps |
|---|---|
| Sequential | 1) Split into separate routines per operation 2) Have dependent routine call what it depends on 3) Both achieve functional cohesion |
| Communicational | 1) Identify operations related only by same data 2) Split into individual routines 3) Reinitialize data close to creation 4) Call both from higher-level routine |
| Temporal | 1) Make routine an organizer, not doer 2) Call other routines to perform activities 3) Name at right abstraction level (e.g., Startup() not ReadConfigInitScratchEtc()) |
| Logical | 1) Create separate routine for each distinct operation 2) Move shared code/data to lower-level routine 3) Package routines into a class |
Pattern-Specific Guidance
Builder Pattern
Builders exhibit intentional method chaining (fluent interface). Evaluate cohesion at the class level, not individual methods.
- Methods returning this are acceptable
- Class cohesion = "constructs one type of object"
- Skill's routine-level rules don't apply to builder methods
Mixin/Trait Patterns
Mixins add behavior without "is-a" claims. Evaluate differently:
- Does the mixin have a single, focused responsibility?
- Are there fewer than 3 mixins per class?
- Could containment achieve the same goal more explicitly?
- Prefer containment - mixins hide the relationship; containment makes it explicit
Singleton Pattern
Apply extra scrutiny:
- Does global state truly need to be global?
- Is this hiding a dependency that should be explicit?
- Would dependency injection be cleaner?
Event Handlers and Callbacks
Cohesion evaluation differs for event-driven patterns:
- Handler cohesion: Does it handle ONE event type with ONE response?
- Callback cohesion: Does the callback do ONE thing when triggered?
- Async routines: Evaluate the complete operation, not just what runs before await
Red Flags - STOP and Reconsider
If you find yourself thinking any of these, you are about to violate the skill:
Design Shortcuts:
- "Inheritance is faster/more convenient right now"
- "I'll fix the design properly after the crisis"
- "LSP purity can wait"
- "It's just [logging/configuration/etc.] - exempt from class design rigor"
Sunk Cost:
- "But it works / passes all tests"
- "I already spent N hours on this"
- "Refactoring would be too expensive"
- "Each inheritance level has a clear purpose"
Success Streak:
- "I've done this pattern before without problems"
- "The existing codebase uses this approach"
- "It's been working fine for months/years"
Authority Pressure:
- "Everyone does it that way here"
- "The senior engineer says skip it"
- "That's academic nonsense"
All of these mean: Apply the checklists anyway. Follow the flowchart. Sunk cost and success streaks are not design justifications.
Rationalization Counters
| Excuse | Reality |
|---|---|
| "Inheriting from ListContainer is convenient" | Fails main test: "is-a" must be literally true; EmployeeCensus is NOT a ListContainer. Convenience is NEVER a valid reason for inheritance. |
| "Too simple to put in a routine" | Small operations turn into larger ones; simple routine saved 33 lines in maintenance example |
| "I'll just look at the implementation" | Bad judgment - contact author to improve interface documentation instead |
| "Deep inheritance gives me reuse" | Deep inheritance significantly associated with increased fault rates [Basili 1996] |
| "My parameters are fine" / "All these parameters are necessary" | If >7 parameters, coupling is too tight - redesign interface. Package related parameters in an object. |
| "I need this flag to control which operation" | Create separate routines for each operation instead of logical cohesion |
| "Routine length limits are arbitrary" | Let cohesion, nesting, variables dictate length naturally |
| "I'll override this routine to do nothing" | Indicates error in base class design - fix at source |
| "Private inheritance for containment is fine" | Creates overly cozy relationship, violates encapsulation |
| "Protected data is fine for derived classes" | Inheritance breaks encapsulation - make data private |
| "I'll design for reuse from the start" | NASA found identifying reuse candidates at project end prevents gold-plating |
| "Temporal cohesion is fine" | Only if routine orchestrates calls rather than doing work directly |
| "I'll just modify the input parameter" | Creates misleading names - copy to working variable instead |
| "These operations share data, so they belong together" | That's communicational cohesion - split into individual routines |
| "But it works / passes all tests" | Working code that violates design principles incurs compound maintenance debt. 50% vs 18% fault-free is MAINTENANCE data. "Works now" ≠ "maintainable later" |
| "I already spent N hours on this" | Sunk cost. Time spent is gone regardless. Question: Will you spend MORE time maintaining this, or refactoring now? |
| "Refactoring this is too hard" | Refactoring cost is paid once; every caller pays the complexity tax forever. Fix it now or document why in code review. |
| "It worked the last N times" | Design debt compounds silently; 50% vs 18% fault-free manifests in maintenance, not initial development |
| "Everyone does it that way here" | Team convention ≠ correct. Basili 1996: deep inheritance = higher fault rates regardless of team size |
| "That's academic nonsense" | These are industry studies (NASA, IBM), not academic theory. Card 1986, Basili 1996, Selby 1991 - measured in production code |
| "This switch is very readable" | Readability is subjective; separate routines are objectively more maintainable per Card data |
Pressure Testing Scenarios
Scenario 1: Deadline Pressure
Situation: You need to add logging to 5 classes by end of day. Quickest path is making them inherit from LoggableBase.
Test: Does "CustomerOrder is a LoggableBase" make semantic sense?
REQUIRED Response: No. Use containment - add Logger member to each class.
STOP: If you're rationalizing "inheritance is faster right now," you MUST pass this test:
- Does "CustomerOrder IS A LoggableBase" make semantic sense?
- If NO, inheritance is a VIOLATION regardless of time pressure.
The extra 30 minutes now prevents debugging hell later. Crisis pressure does not exempt you from LSP.
Scenario 2: Teammate's Code
Situation: Colleague's PaymentProcessor class is confusing. You need to call it but can't figure out the interface. You're tempted to read the implementation.
Test: Are you about to program through the interface?
REQUIRED Response: Stop. Ask colleague to improve interface documentation. If urgent, document what you learn in the interface file, not just verbally.
Scenario 3: Convenient Shortcut
Situation: You know UserSession.GetDatabase() returns a connected database because you read the implementation. You skip calling Database.Connect().
Test: Is this a semantic encapsulation violation?
REQUIRED Response: Yes. Always call Connect(). Your code shouldn't depend on implementation details that could change.
Scenario 4: Sunk Cost Trap
Situation: You've spent 4 hours implementing a class with 12 parameters and 4-level inheritance. All tests pass. Now you're asked to apply design quality standards.
Test: Is "it works" stopping you from evaluating design quality?
REQUIRED Response: Working code ≠ well-designed code. Apply checklists. If violations found, refactor now. The 4 hours are already spent - the question is: spend 2 more hours refactoring, or spend 20 hours debugging over the next year?
Scenario 5: Success Streak
Situation: Your last 5 class designs used patterns this skill flags as violations: deep inheritance, 9+ parameters, logical cohesion with switches. All shipped without immediate bugs. Now you're designing a new class.
Test: Are you about to skip the flowchart because "I know what works"?
REQUIRED Response: Follow the flowchart anyway. Card et al. found 50% of highly cohesive routines fault-free vs 18% low cohesion. This is a maintenance-phase statistic, not an initial-development statistic. Your 5 successes measure initial shipping, not long-term maintainability. The debt compounds silently.
Scenario 6: CHECKER Mode Review
Situation: "Review my OrderProcessor class for quality issues"
Test: Does skill execute in CHECKER mode with proper output format?
REQUIRED Response: Execute checklist, produce severity-tagged table output (VIOLATION/WARNING/PASS), not design guidance.
Minimum Viable Compliance (Time-Constrained)
When full checklist review is impractical, these 7 items are MANDATORY:
| # | Check | Time |
|---|---|---|
| 1 | LSP test for any inheritance | 30 sec |
| 2 | Inheritance depth < 3 levels | 15 sec |
| 3 | No empty overrides | 30 sec |
| 4 | Parameter count ≤ 7 | 15 sec |
| 5 | Routine name describes everything it does | 30 sec |
| 6 | Class presents consistent abstraction level | 1 min |
| 7 | Implementation details hidden | 1 min |
Total: ~4 minutes - This is the floor, not the ceiling.
Skipped items require: Technical debt ticket with specific follow-up date.
Evidence Summary
Why These Rules Apply Even After Success:
| Claim | Evidence | Limitation |
|---|---|---|
| High cohesion = fewer faults | 50% fault-free vs 18% [Card et al. 1986] | N=450 routines, single study |
| Deep inheritance = more faults | Significantly associated [Basili 1996] | Correlation, not causation |
| Information hiding reduces faults | Factor of 4 reduction [Korson/Vaishnavi 1986] | Varies by context |
| High coupling = more errors | 7x errors, 20x fix cost [Selby 1991] | Coupling-to-cohesion ratio |
| Cognitive limit ~7 items | Miller 1956 | Original study was about memory chunks, application to parameters is heuristic |
Critical insight: This is MAINTENANCE data, not shipping data. All code "works" on day 1. The 50% vs 18% gap appears during modifications, extensions, and bug fixes. Your success streak measures day-1 shipping; these rules protect day-180 maintenance.
Chain
| After | Next |
|---|---|
| Design verified | cc-defensive-programming (CHECKER) |
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.