Maintainability Assessment
maintainability
Maintainability assessment criteria for code review. Apply when evaluating code for readability, change tolerance, hidden assumptions, and debuggability.
SKILL.md
Full skill instructions
Maintainability Assessment
Evaluate whether code will be easy to understand, modify, extend, and debug over time.
Quick Reference
| Factor | Key Question | Severity |
|---|---|---|
| Readability | Understandable in 6 months? | Critical if high cognitive load |
| Change tolerance | Changes localized? | Improvement |
| Extensibility | Add without modifying? | Improvement |
| Hidden assumptions | Constraints explicit? | Critical if causes bugs |
| Debuggability | Failures traceable? | Critical if silent |
| Intent documentation | "Why" captured? | Improvement |
Anti-Patterns
| Anti-Pattern | Problem | Instead |
|---|---|---|
| Clever one-liners | Requires mental parsing | Named intermediate steps |
| Scattered logic | Changes touch many files | Centralize related behavior |
| Hardcoded values | Configuration changes require code changes | Use config objects |
| Growing if/elif chains | Every new type modifies existing code | Registry or protocol pattern |
Bare except: | Swallows all errors including KeyboardInterrupt | Catch specific exceptions |
| Silent failures | No trace when things go wrong | Log errors with context |
| Comments saying "what" | Restates code | Explain "why" |
Readability
Check: Can someone understand this in 6 months?
# HIGH COGNITIVE LOAD: Too much at once
def process(data):
return {k: sum(x['value'] for x in v if x.get('active', True))
for k, v in groupby(sorted(filter(lambda x: x['status'] != 'deleted',
data), key=lambda x: x['category']), key=lambda x: x['category'])}
# READABLE: Named intermediate steps
def process(data):
active_items = [x for x in data if x['status'] != 'deleted']
sorted_items = sorted(active_items, key=lambda x: x['category'])
result = {}
for category, items in groupby(sorted_items, key=lambda x: x['category']):
result[category] = sum(x['value'] for x in items if x.get('active', True))
return result
Heuristics
- Can you explain what a function does in one sentence?
- Would a new team member understand this without asking questions?
- Are variable names descriptive enough to skip comments?
Severity
- Critical: Understanding requires significant mental effort
- Improvement: Some parts require careful reading
- Nitpick: Minor clarity improvements possible
Change Tolerance
Check: How many places need modification for typical changes?
# BRITTLE: User display logic scattered
# views.py
def user_profile(user):
return f"{user.first_name} {user.last_name}"
# emails.py
def format_recipient(user):
return f"{user.first_name} {user.last_name} <{user.email}>"
# Adding middle name requires 2+ file changes
# RESILIENT: Centralized
class User:
@property
def display_name(self) -> str:
return f"{self.first_name} {self.last_name}"
@property
def email_display(self) -> str:
return f"{self.display_name} <{self.email}>"
Severity
- Critical: Typical changes require 5+ file modifications
- Improvement: 2-4 files need coordinated changes
- Nitpick: Changes generally localized but could be cleaner
Extensibility
Check: Can new behavior be added without modifying existing code?
# CLOSED: Adding new type requires modifying existing code
def process_payment(payment):
if payment.type == "credit_card":
process_credit_card(payment)
elif payment.type == "paypal":
process_paypal(payment)
elif payment.type == "crypto": # had to add this
process_crypto(payment)
# OPEN: New types don't touch existing code
class PaymentProcessor(Protocol):
def process(self, payment: Payment) -> Result: ...
PROCESSORS: dict[str, PaymentProcessor] = {
"credit_card": CreditCardProcessor(),
"paypal": PayPalProcessor(),
}
def process_payment(payment):
return PROCESSORS[payment.type].process(payment)
Severity
- Improvement: Extending requires modifying existing functions/classes
- Nitpick: Extension points exist but could be cleaner
Hidden Assumptions
Check: Are constraints and expectations explicit?
# HIDDEN: items is never empty
def get_average(items):
return sum(items) / len(items) # ZeroDivisionError
# EXPLICIT: Documented and enforced
def get_average(items: list[float]) -> float:
"""Calculate average. Raises ValueError if items is empty."""
if not items:
raise ValueError("Cannot calculate average of empty list")
return sum(items) / len(items)
Severity
- Critical: Hidden assumptions will cause production bugs
- Improvement: Assumptions reasonable but not documented
- Nitpick: Assumptions obvious from context
Debuggability
Check: When things fail, is it clear why?
# BAD: Silent failure
def update_user(user_id, data):
user = get_user(user_id)
if user:
user.update(data)
# if user doesn't exist... nothing happens
# BAD: Exception swallowing
try:
result = complex_operation()
except Exception:
pass
# GOOD: Specific error with context
def update_user(user_id, data):
user = get_user(user_id)
if user is None:
raise UserNotFoundError(f"No user with id {user_id}")
user.update(data)
# GOOD: Logged with structured context
def process_batch(items):
for item in items:
try:
process(item)
except Exception as e:
logger.error(f"Failed to process item {item.id}: {e}",
extra={"item_id": item.id, "item_type": item.type})
Severity
- Critical: Failures are silent or swallowed
- Improvement: Errors lack context for debugging
- Nitpick: Errors could be slightly more informative
Intent Documentation
Check: Is the "why" captured, not just the "what"?
# USELESS: Restates the code
x = x + 1 # increment x
# USEFUL: Explains why
x = x + 1 # account for 0-indexing in the API response
# WITHOUT CONTEXT: What is this magic?
if (n & (n - 1)) == 0 and n != 0:
...
# WITH CONTEXT: Clear purpose
# Check if n is a power of 2 (only one bit set)
if (n & (n - 1)) == 0 and n != 0:
...
Severity
- Improvement: Complex logic lacks explanation
- Nitpick: Documentation could be clearer but code is understandable
Quick Smell Test
Answer these for any significant code change:
- New developer test: Could someone unfamiliar fix a bug in this?
- 2am test: Could you debug this at 2am with incomplete logs?
- Feature request test: Could you add a related feature without rewriting?
- Handoff test: Could you explain this to a colleague in 5 minutes?
If any answer is "no", the code likely has maintainability issues worth flagging.
Convention Reference
When flagging maintainability issues in reviews, reference this skill:
**Convention**: See `maintainability` skill: Debuggability
