Agent Skill
2/7/2026

writing-code

ALWAYS use when writing or refactoring code. Loads foundational sub-skills and enforces engineering principles for correctness, maintainability, and clarity.

C
cyarie
0GitHub Stars
1Views
npx skills add cyarie/cyarie-claude-plugin

SKILL.md

Namewriting-code
DescriptionALWAYS use when writing or refactoring code. Loads foundational sub-skills and enforces engineering principles for correctness, maintainability, and clarity.

name: writing-code description: ALWAYS use when writing or refactoring code. Loads foundational sub-skills and enforces engineering principles for correctness, maintainability, and clarity.

Writing Code

Overview

Writing code succeeds when we ensure correctness, readability, and maintainability. This skill enforces foundational engineering principles and coordinates sub-skills for specific contexts. The goal: code that is correct by construction, not by accident.

Required Sub-Skills

Always load these when writing or refactoring code:

SkillPurpose
defensive-codingMulti-layer validation, observability, exception handling
designing-softwareProperty discovery, architecture decisions, SOLID principles
howto-program-functionally-ishSeparate pure logic from side effects (Gather → Process → Persist)

Conditional Sub-Skills

Load these when the context applies:

SkillWhen to Load
howto-code-in-pythonWriting or reviewing Python code
writing-useful-testsWriting tests, reviewing test code, discussing testing strategy

Core Engineering Principles

Correctness Over Convenience

Model the complete problem space. Shortcuts create bugs. Edge cases ignored during development become incidents in production.

  • Plan for all states, including error states
  • Handle all edge cases explicitly
  • Use type systems to make invalid states unrepresentable
  • Catch issues as early as possible — compile time beats runtime, runtime beats production
# Good — type system prevents invalid states
@dataclass
class OrderStatus:
    pass

@dataclass
class Pending(OrderStatus):
    created_at: datetime

@dataclass
class Shipped(OrderStatus):
    shipped_at: datetime
    tracking_number: str  # Required for shipped orders

@dataclass
class Cancelled(OrderStatus):
    cancelled_at: datetime
    reason: str  # Required for cancellations
# Bad — stringly-typed, invalid states possible
@dataclass
class Order:
    status: str  # "pending", "shipped", "cancelled"
    tracking_number: str | None  # Can be None even when shipped
    cancellation_reason: str | None  # Can be None even when cancelled

Never Assume

Verify, don't guess. When uncertain about requirements, existing code, or system behavior:

  • Ask clarifying questions rather than making assumptions
  • Explore the codebase to understand existing patterns
  • Iterate with feedback rather than building in isolation
  • Document assumptions explicitly when they must be made

Never Simplify Error Handling

Errors are information. Obscuring or glossing over errors makes debugging impossible.

  • Preserve error context through exception chaining
  • Use specific exception types, not generic ones
  • Never swallow exceptions silently
  • Log errors with sufficient context for diagnosis
# Good — preserves context, specific exception
try:
    config = load_config(path)
except FileNotFoundError as e:
    raise ConfigurationError(f"Config file missing: {path}") from e

# Bad — loses context, generic handling
try:
    config = load_config(path)
except Exception:
    config = {}  # Silently use defaults, hide the real problem

Never Use Any to Escape Type Checking

Type systems exist to catch bugs. Using Any (Python), any (TypeScript), or equivalent types to bypass difficult type-checking decisions defeats the purpose.

  • If a type is genuinely dynamic, model it explicitly (unions, generics, protocols)
  • If the type system is fighting you, the design may need reconsideration
  • Any is acceptable only at true system boundaries (e.g., parsing arbitrary JSON from external APIs)
# Good — explicit union for dynamic content
def parse_response(data: dict[str, str | int | list[str]]) -> Response: ...

# Good — generic for reusable containers
def first[T](items: list[T]) -> T | None: ...

# Bad — Any to avoid thinking about types
def process(data: Any) -> Any: ...

Build Incrementally

Small, composable pieces. Large monolithic implementations are hard to test, hard to debug, and hard to change.

  • Build and verify in small increments
  • Each piece should be independently testable
  • Compose complex behavior from simple, well-tested parts
  • Prefer explicit composition over implicit coupling

Never Abstract Early

Wait for patterns to emerge. Premature abstraction creates complexity without benefit.

  • Prefer some duplication over the wrong abstraction
  • Abstract after three similar implementations, not before
  • If an abstraction doesn't simplify, remove it
  • The right time to abstract is when duplication causes maintenance pain
# Good — concrete implementations, duplication is fine for now
def process_user_created_event(event: UserCreatedEvent) -> None:
    logger.info("user_created", user_id=event.user_id)
    db.insert_user(event.user_id, event.email)
    email_service.send_welcome(event.email)

def process_order_placed_event(event: OrderPlacedEvent) -> None:
    logger.info("order_placed", order_id=event.order_id)
    db.insert_order(event.order_id, event.items)
    email_service.send_confirmation(event.customer_email)

# Bad — premature abstraction before patterns are clear
class EventProcessor(ABC):
    @abstractmethod
    def extract_id(self, event: Event) -> str: ...
    @abstractmethod
    def persist(self, event: Event) -> None: ...
    @abstractmethod
    def notify(self, event: Event) -> None: ...

    def process(self, event: Event) -> None:
        logger.info(self.event_type, id=self.extract_id(event))
        self.persist(event)
        self.notify(event)

Document Non-Obvious Decisions

Rationale matters. When making difficult or non-obvious decisions, document:

  • What decision was made
  • Why it was made (alternatives considered, constraints)
  • When it might need revisiting
# Good — documents the non-obvious choice
# We use a list instead of a set here because:
# 1. Order of insertion matters for display
# 2. Items may contain unhashable nested structures
# 3. Typical size is <10 items, so O(n) lookup is acceptable
recent_items: list[Item] = []

File Organization

Descriptive Filenames Over Generic Utilities

Name files by purpose, not category. Generic utility files become dumping grounds.

AvoidPrefer
utils.pystring_formatting.py
helpers.pydate_arithmetic.py
common.pyapi_error_handling.py
misc.pyuser_validation.py

Why this matters:

  • Discoverability — find code by scanning filenames
  • Cohesion — related code stays together
  • Prevents bloat — no 2000-line utils files
  • Clear imports — from user_validation import validate_email

Organize Around Components

Modules should map to logical components, not technical layers.

# Good — organized by component
order_processing/
├── orders.py           # Order domain logic
├── pricing.py          # Price calculations
├── validation.py       # Order validation
└── notifications.py    # Order notifications

# Bad — organized by technical layer
order_processing/
├── models.py           # All models
├── utils.py            # All utilities
├── services.py         # All services
└── validators.py       # All validators

Red Flags

Warning signs that code needs attention:

  • Any or equivalent used to bypass type checking
  • Bare except: or except Exception: pass
  • utils.py or helpers.py exceeding 200 lines
  • Business logic mixed with I/O operations
  • Error messages that don't include relevant context
  • Assumptions about input without validation
  • Single-use abstractions or premature generalization
  • Missing type annotations on public functions
  • Platform-specific code mixed with cross-platform logic

Common Mistakes

MistakeWhy It FailsCorrect Approach
Using Any to fix type errorsHides bugs the type system would catchModel the type explicitly
Catching generic exceptionsMasks unexpected errors, makes debugging impossibleCatch specific exceptions
"Utils" filesBecome unmaintainable dumping groundsName files by purpose
Early abstractionWrong abstraction is worse than duplicationWait for three use cases
Assumptions about inputInvalid data causes bugs deep in call stackValidate at entry points
Swallowing errorsSilent failures are impossible to diagnoseLog and re-raise, or handle explicitly
Building in isolationMisunderstanding requirements wastes effortIterate with feedback
Skipping type annotationsLose compile-time bug detectionAnnotate public interfaces
Technical-layer organizationCode for one feature spread across filesOrganize by component

Anti-Rationalizations

  • "I'll handle that edge case later" — You will not. Handle it now or document it as a known limitation.
  • "The type system is too strict" — The type system is catching a bug. Fix the design.
  • "It's just a small shortcut" — Shortcuts compound. The "small" ones cause production incidents.
  • "I'll refactor when I have time" — You won't have time. Refactor now while context is fresh.
  • "This abstraction will be useful later" — Build for current requirements. Abstract when patterns emerge.
  • "Error handling clutters the code" — Error handling IS the code. The happy path is the easy part.
  • "I know how this system works" — Verify anyway. Systems change, memory fades, edge cases hide.

Summary

  1. Model the complete space. Handle all edge cases; use types to make invalid states unrepresentable.
  2. Never assume. Explore, ask questions, iterate — verify before building.
  3. Preserve error context. Never swallow exceptions; errors are information.
  4. Avoid Any. If the type system is fighting you, reconsider the design.
  5. Abstract after three. Duplication is better than the wrong abstraction.
  6. Name files by purpose. No utils.py; use user_validation.py.
Skills Info
Original Name:writing-codeAuthor:cyarie