Claude-skill-registry function-design
Python function design conventions for this codebase. Apply when writing or reviewing functions including signatures, parameters, return types, and async patterns.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/function-design" ~/.claude/skills/majiayu000-claude-skill-registry-function-design && rm -rf "$T"
manifest:
skills/data/function-design/SKILL.mdsource content
Function Design Conventions
Quick Reference
| Principle | Pattern |
|---|---|
| Explicit dependencies | Pass as parameters, no global state |
| Single responsibility | One function = one task |
| Pure when possible | Return new values, don't mutate inputs |
| Guard clauses | Validate early, return/raise immediately |
| Stable return types | Same type regardless of input |
| Errors via exceptions | Reserve for "not found" only |
| Command-query separation | Action OR data, not both |
| Keyword-only args | Use for optional/multiple params |
| No mutable defaults | Use , create inside function |
| Async prefix | for async functions |
Core Principles
Explicit Dependencies
# INCORRECT - hidden dependency on global state _current_user: User | None = None def get_permissions() -> list[str]: return _current_user.permissions # CORRECT - explicit dependency def get_permissions(user: User) -> list[str]: return user.permissions
Single Responsibility
# INCORRECT - does two things def validate_and_save_user(user: User) -> bool: if not user.email or "@" not in user.email: return False db.save(user) return True # CORRECT - separate concerns def is_valid_email(email: str) -> bool: return bool(email and "@" in email) def save_user(user: User) -> None: if not is_valid_email(user.email): raise ValidationError("Invalid email address") db.save(user)
Pure Functions When Possible
# INCORRECT - modifies input def normalize_scores(scores: list[float]) -> None: max_score = max(scores) for i in range(len(scores)): scores[i] /= max_score # CORRECT - returns new value def normalize_scores(scores: list[float]) -> list[float]: max_score = max(scores) return [s / max_score for s in scores]
Guard Clauses (Return Early)
# INCORRECT - deeply nested def process_order(order: Order | None) -> Receipt: if order is not None: if order.items: if order.payment_verified: return generate_receipt(order) else: raise PaymentError("Payment not verified") else: raise ValidationError("Order has no items") else: raise ValidationError("Order is required") # CORRECT - guard clauses def process_order(order: Order | None) -> Receipt: if order is None: raise ValidationError("Order is required") if not order.items: raise ValidationError("Order has no items") if not order.payment_verified: raise PaymentError("Payment not verified") return generate_receipt(order)
Return Type Stability
# INCORRECT - inconsistent return types def find_user(user_id: int) -> User | None | bool: if user_id < 0: return False user = db.get(user_id) return user # CORRECT - consistent return type def find_user(user_id: int) -> User: if user_id < 0: raise ValueError("user_id must be >=0; got {user_id}") return db.get(user_id)
Exceptions Over None for Errors
# INCORRECT - None conflates "not found" with "error" def load_config(path: Path) -> Config | None: if not path.exists(): return None try: return Config.from_file(path) except ParseError: return None # CORRECT - exceptions for errors, None only for "not found" def load_config(path: Path) -> Config: if not path.exists(): raise FileNotFoundError(f"Config file not found: {path}") try: return Config.from_file(path) except ParseError as e: raise ConfigurationError(f"Invalid config format: {e}") from e
Command-Query Separation
# INCORRECT - does both def get_next_id() -> int: global _counter _counter += 1 # Side effect (command) return _counter # Returns value (query) # CORRECT - separate command and query class IdGenerator: def __init__(self) -> None: self._counter = 0 def next(self) -> int: """Return the next ID (query only).""" return self._counter + 1 def advance(self) -> None: """Increment the counter (command only).""" self._counter += 1 def take(self) -> int: """Get next ID and advance. Clearly named to indicate both.""" id = self.next() self.advance() return id
Keep Functions Small and Focused
# INCORRECT - too much happening def process_document(doc: Document) -> ProcessedDocument: # Validate if not doc.content: raise ValueError("Empty document") if len(doc.content) > MAX_LENGTH: raise ValueError("Document too long") # Extract metadata title = doc.content.split("\n")[0] word_count = len(doc.content.split()) # Transform content cleaned = doc.content.lower().strip() tokens = cleaned.split() # ... 50 more lines # CORRECT - composed of focused functions def process_document(doc: Document) -> ProcessedDocument: validate_document(doc) metadata = extract_metadata(doc) tokens = tokenize_content(doc.content) return ProcessedDocument(metadata=metadata, tokens=tokens)
Parameter Guidelines
Limit Positional Parameters
# INCORRECT - too many positional parameters def create_user(name: str, email: str, age: int, role: str, dept: str) -> User: ... # CORRECT - group related parameters @dataclass class UserInput: name: str email: str age: int role: str department: str def create_user(input: UserInput) -> User: ...
Keyword-Only Arguments
Use
* to force keyword arguments for optional parameters or functions with 3+ parameters.
# CORRECT - keyword-only after * def fetch_data( url: str, *, # Everything after this must be keyword-only timeout: float = 30.0, retries: int = 3, headers: dict[str, str] | None = None, ) -> Response: ... # Callers must be explicit response = fetch_data("https://api.example.com", timeout=60.0, retries=5)
No Mutable Default Arguments
# INCORRECT - mutable default (shared across calls!) def add_item(item: str, items: list[str] = []) -> list[str]: items.append(item) return items add_item("a") # Returns ["a"] add_item("b") # Returns ["a", "b"] - BUG! # CORRECT - None default with internal creation def add_item(item: str, items: list[str] | None = None) -> list[str]: if items is None: items = [] items.append(item) return items
Async Functions
Prefix async functions with
async_ to make the async nature visible at call sites.
# CORRECT - async prefix makes it clear async def async_fetch_user(user_id: int) -> User: return await client.get(f"/users/{user_id}") async def async_process_batch(items: list[Item]) -> list[Result]: return await asyncio.gather(*[async_process(item) for item in items]) # Usage is clear about async nature user = await async_fetch_user(123)