Skip to content
maintainability logo

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

FactorKey QuestionSeverity
ReadabilityUnderstandable in 6 months?Critical if high cognitive load
Change toleranceChanges localized?Improvement
ExtensibilityAdd without modifying?Improvement
Hidden assumptionsConstraints explicit?Critical if causes bugs
DebuggabilityFailures traceable?Critical if silent
Intent documentation"Why" captured?Improvement

Anti-Patterns

Anti-PatternProblemInstead
Clever one-linersRequires mental parsingNamed intermediate steps
Scattered logicChanges touch many filesCentralize related behavior
Hardcoded valuesConfiguration changes require code changesUse config objects
Growing if/​elif chainsEvery new type modifies existing codeRegistry or protocol pattern
Bare except:Swallows all errors including KeyboardInterruptCatch specific exceptions
Silent failuresNo trace when things go wrongLog errors with context
Comments saying "what"Restates codeExplain "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:

  1. New developer test: Could someone unfamiliar fix a bug in this?
  2. 2am test: Could you debug this at 2am with incomplete logs?
  3. Feature request test: Could you add a related feature without rewriting?
  4. 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