| name | clean-code |
| description | Clean code principles covering naming conventions, function design, SOLID principles, DRY vs WET tradeoffs, code organization, complexity metrics, and code review through a clean code lens.
Use when the user asks about clean code, clean code best practices, or needs guidance on clean code implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"best-practices clean-code guide","category":"software-engineering","subcategory":"languages-runtimes","depends":"","disclaimer":"none","difficulty":"intermediate"} |
Clean Code
You are an expert in clean code principles. Write code that is readable, maintainable, and intentional. Clean code reads like well-written prose. Every name, function, and module should reveal its purpose without requiring comments to explain it.
Naming Conventions
The Rules of Good Names
- Names reveal intent. A reader should understand what a variable holds, what a function does, or what a class represents without reading the implementation.
d = 7
lst = []
temp = get()
elapsed_days = 7
flagged_accounts = []
active_user = get_authenticated_user()
- Names are pronounceable. If you cannot say it in conversation, rename it.
Date genymdhms;
int pdcnt;
Date generationTimestamp;
int pastDueCount;
- Names are searchable. Single-letter names and magic numbers are invisible to search.
if (days > 7) { ... }
const MAX_INACTIVE_DAYS = 7;
if (days > MAX_INACTIVE_DAYS) { ... }
-
Avoid abbreviations unless universally understood (HTTP, URL, ID, DB).
-
Use consistent vocabulary. Pick one word per concept and stick with it. Do not use get, get, get, and load interchangeably in the same codebase.
Naming by Type
| Type | Convention | Examples |
|---|
| Boolean | Phrase as question | isActive, hasPermission, canEdit, shouldRetry |
| Function | Verb + noun | calculateTotal, sendEmail, validateInput |
| Predicate function | is/has/can | isExpired(), hasAccess(), canProceed() |
| Collection | Plural noun | users, orderItems, activeConnections |
| Count | _count or num_ | retryCount, numAttempts |
| Class | Noun | UserRepository, PaymentProcessor, OrderValidator |
| Interface | Adjective or noun | Serializable, Repository, EventHandler |
| Constant | UPPER_SNAKE_CASE | MAX_RETRIES, DEFAULT_TIMEOUT_MS |
Naming Anti-Patterns
- Meaningless prefixes:
IUserService, AbstractBaseFactory. Let the language features speak.
- Type in name:
userList, nameString. The type system handles this.
- Noise words:
data, info, manager, handler, processor. These add length without meaning.
- Negative booleans:
isNotReady, disableFeature. Use positive names: isReady, featureEnabled.
Function Design
Functions Should Be Small
A function should do one thing, do it completely, and do it only. Target 5-15 lines. If a function has sections (separated by blank lines or comments), each section is a candidate for extraction.
Functions Should Have One Level of Abstraction
def process_order(order):
validate_order(order)
conn = psycopg2.connect(host="db", port=5432, dbname="orders")
cursor = conn.cursor()
cursor.execute("INSERT INTO orders (id, total) VALUES (%s, %s)", (order.id, order.total))
conn.commit()
send_confirmation(order)
def process_order(order):
validate_order(order)
save_order(order)
send_confirmation(order)
Function Arguments
- 0 arguments (niladic): Best.
- 1 argument (monadic): Good. Common forms: transformation (
parse(input)), query (isValid(email)), event (onUserCreated(user)).
- 2 arguments (dyadic): Acceptable. Ensure the order is intuitive (
assertEquals(expected, actual)).
- 3 arguments (triadic): Should be rare. Consider introducing a parameter object.
- 4+ arguments: Refactor. Use a configuration object or builder.
Pure Functions
Prefer pure functions: same input always produces same output, no side effects.
def calculate_discount(price: float, discount_percent: float) -> float:
return price * (1 - discount_percent / 100)
def apply_discount(order):
discount = get_global_discount()
order.total -= order.total * discount
log(f"Applied discount to {order.id}")
Command-Query Separation
Functions should either do something (command) or answer something (query), not both.
boolean set(String attribute, String value);
if (set("username", "john")) { ... }
boolean attributeExists(String attribute);
void setAttribute(String attribute, String value);
if (attributeExists("username")) {
setAttribute("username", "john");
}
SOLID Principles
S - Single Responsibility Principle
A class should have one, and only one, reason to change.
class UserService {
authenticate(credentials) { ... }
validateEmail(email) { ... }
saveToDatabase(user) { ... }
sendWelcomeEmail(user) { ... }
}
class AuthenticationService { authenticate(credentials) { ... } }
class UserValidator { validateEmail(email) { ... } }
class UserRepository { save(user) { ... } }
class NotificationService { sendWelcomeEmail(user) { ... } }
O - Open/Closed Principle
Software entities should be open for extension, closed for modification.
class AreaCalculator:
def calculate(self, shape):
if isinstance(shape, Circle):
return math.pi * shape.radius ** 2
elif isinstance(shape, Rectangle):
return shape.width * shape.height
class Shape(Protocol):
def area(self) -> float: ...
class Circle:
def __init__(self, radius): self.radius = radius
def area(self) -> float: return math.pi * self.radius ** 2
class Rectangle:
def __init__(self, width, height): self.width, self.height = width, height
def area(self) -> float: return self.width * .height
:
(): .base, .height = base, height
() -> : * .base * .height
L - Liskov Substitution Principle
Objects of a superclass should be replaceable with objects of a subclass without altering program correctness.
class Rectangle:
def set_width(self, w): self.width = w
def set_height(self, h): self.height = h
class Square(Rectangle):
def set_width(self, w): self.width = self.height = w
def set_height(self, h): self.width = self.height = h
class Shape(Protocol):
def area(self) -> float: ...
class Rectangle:
def __init__(self, width, height): ...
def area(self): return self.width * self.height
class Square:
def __init__(): ...
(): .side **
I - Interface Segregation Principle
Clients should not be forced to depend on interfaces they do not use.
interface Machine {
print(doc: Document): void;
fax(doc: Document): void;
scan(doc: Document): Image;
}
interface Printer { print(doc: Document): void; }
interface Fax { fax(doc: Document): void; }
interface Scanner { scan(doc: Document): Image; }
class SimplePrinter implements Printer {
print(doc: Document) { ... }
}
class MultiFunctionDevice implements Printer, Fax, Scanner {
print(doc: Document) { ... }
fax() { ... }
() { ... }
}
D - Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions.
class OrderService:
def __init__(self):
self.db = MySQLDatabase()
class OrderService:
def __init__(self, repository: OrderRepository):
self.repository = repository
db = PostgresOrderRepository(connection_string)
service = OrderService(db)
DRY vs WET Tradeoffs
DRY (Don't Repeat Yourself)
Eliminate duplication of knowledge (not just code). If a business rule is expressed in two places, it will inevitably diverge.
When DRY Goes Wrong
def format_thing(thing, type):
if type == "user":
return f"{thing.first_name} {thing.last_name}"
elif type == "product":
return f"{thing.name} - ${thing.price}"
elif type == "order":
return f"Order #{thing.id}"
This function couples three unrelated formatters. When user formatting changes, you risk breaking product formatting.
WET (Write Everything Twice) Rule
Allow duplication until you have 3+ instances. Then abstract. This prevents premature abstraction.
def validate_user_email(email): ...
def validate_contact_email(email): ...
def validate_email(email): ...
The Abstraction Test
Before extracting shared code, ask: If one caller needs a change, would ALL callers need the same change?
- Yes: Extract the shared code (real duplication).
- No: The similarity is coincidental. Keep separate (accidental duplication).
Code Organization
File Structure Principles
- Group by feature, not by type (prefer
user/controller.ts, user/model.ts over controllers/user.ts, models/user.ts).
- Put related code close together. Functions that call each other should be in the same file or adjacent files.
- Newspaper metaphor: High-level functions at the top, low-level details at the bottom. Readers scan top-down.
- One concept per file. A file with 3 unrelated classes should be 3 files.
Vertical Formatting
- Caller above callee. A function should be defined below the function that calls it.
- Related concepts close together. Do not separate related functions with unrelated ones.
- Blank lines between concepts. Group related statements. Separate logical sections.
Complexity Metrics
Cyclomatic Complexity
Count the number of independent paths through a function.
def process(order):
if order.is_valid:
if order.total > 100:
apply_discount(order)
elif order.is_member:
apply_member_discount(order)
for item in order.items:
if item.needs_shipping:
schedule_shipping(item)
else:
raise InvalidOrderError()
Targets:
- 1-5: Simple, low risk.
- 6-10: Moderate, consider refactoring.
- 11-20: Complex, refactor.
- 21+: Untestable. Refactor immediately.
Cognitive Complexity
Measures how hard code is to understand (Sonar metric). Penalizes nesting more heavily than branching.
Halstead Metrics
- Program length: Total number of operators and operands.
- Vocabulary: Number of distinct operators and operands.
- Difficulty: How error-prone the code is.
Clean Code Checklist for Review
When reviewing code through a clean code lens:
Comments
Good Comments
adjusted_time = timestamp + LEAP_SECOND_OFFSET
tax = calculate_tax(subtotal)
discount = calculate_discount(subtotal)
Bad Comments (Replace with Better Code)
i += 1
if (condition) {
...
...
...
} // end if condition
The Best Comment is No Comment
If you feel the need to comment, first try to express the same information through:
- A better variable name.
- A better function name.
- Extracting a well-named function.
- Using a well-named constant.
If the code still needs a comment after trying all four, write the comment. Explain why, not what.
When to Use
Use this skill when:
- Designing or implementing clean code solutions
- Reviewing or improving existing clean code approaches
- Making architectural or implementation decisions about clean code
- Learning clean code patterns and best practices
- Troubleshooting clean code-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# Clean Code Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement clean code for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended clean code approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When clean code must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities