docstring-and-inline-docs
verified8398e0a7-b8d3-4185-9d51-9bbac85522c2
Write docstrings and comments that explain *why* and *contract*, not what the code visibly does. Use when adding public functions or clarifying non-obvious logic.
Metadata
Skill file
# Docstrings and Inline Documentation
Use when writing or reviewing function/method documentation — the goal is to document the contract (what, params, return, raises) and the intent (why), not to restate the code.
## The Docstring Contract
### Python (Google-style)
```python
def transfer_funds(
from_account: str,
to_account: str,
amount_cents: int,
*,
idempotency_key: str | None = None,
) -> TransferResult:
"""Move money between two accounts atomically.
Both accounts must belong to the same currency. This is an idempotent
operation — passing the same idempotency_key twice returns the original
result without double-charging.
Args:
from_account: Source account ID (e.g., "acct_abc123").
to_account: Destination account ID. Must differ from from_account.
amount_cents: Amount to transfer in cents (USD). Must be positive.
idempotency_key: Optional client-generated key for safe retries.
Returns:
TransferResult with transfer_id, status, and ledger entries.
Raises:
InsufficientFundsError: from_account balance < amount_cents.
CurrencyMismatchError: Accounts have different currencies.
ValueError: from_account == to_account, or amount_cents <= 0.
"""
```
### JavaScript/TypeScript (JSDoc)
```typescript
/**
* Move money between two accounts atomically.
*
* @param fromAccount - Source account ID.
* @param toAccount - Destination account ID. Must differ from source.
* @param amountCents - Transfer amount in cents (must be positive).
* @param idempotencyKey - Optional key for safe retries.
* @returns Transfer result with ID, status, and entries.
* @throws {InsufficientFundsError} If source balance is insufficient.
*/
async function transferFunds(
fromAccount: string,
toAccount: string,
amountCents: number,
idempotencyKey?: string
): Promise<TransferResult> { ... }
```
### Go
```go
// TransferFunds moves money between two accounts atomically.
//
// Both accounts must share the same currency. Passing the same idempotencyKey
// twice returns the original result without double-charging.
//
// Errors:
// - ErrInsufficientFunds if the source account balance is too low.
// - ErrCurrencyMismatch if the accounts have different currencies.
func TransferFunds(
fromAccount, toAccount string,
amountCents int64,
idempotencyKey *string,
) (TransferResult, error) { ... }
```
## The Comment Rule: Why, Not What
```python
# ❌ BAD — restates the code
# Loop over items and add to total
for item in items:
total += item.price
# ✅ GOOD — explains why this is done this way
# Accumulate in cents (int) to avoid floating-point rounding.
# Conversion to dollars happens only at display time.
for item in items:
total += item.price_cents
# ❌ BAD — obvious from the code
i += 1 # increment i
# ✅ GOOD — explains a non-obvious constraint
i += 1 # Skip the header row (index 0) which contains column names
# ✅ GOOD — explains a workaround
# SQLAlchemy 2.0.23 has a bug where joinedload with limit() generates
# a cartesian product. Use selectinload instead until the fix ships.
# See: https://github.com/sqlalchemy/sqlalchemy/issues/12345
```
## When to Comment vs Refactor
| Situation | Action |
|---|---|
| The code is hard to understand | Refactor first, then comment any remaining non-obvious choices |
| There's a business rule or domain constraint | Comment the rule and its source (ticket, spec, regulation) |
| A workaround for a library bug | Comment with the bug link and version affected |
| A performance optimization that reads oddly | Comment with the before/after benchmark |
| Standard, idiomatic code | No comment needed |
| A TODO or FIXME | Either do it now, or file a ticket and reference it |
## Removing Stale Comments
```bash
# Find comments that may be lying — look for function signatures that changed
git log --all -p -S "old_param_name" -- '*.py'
# Find TODO comments older than 6 months
git log --all --since="6 months ago" --grep="TODO" --oneline
```
## Guardrails
- **Never** commit commented-out code. Delete it. Git history preserves it.
- **Never** write docstrings that repeat the function signature — the types + name tell you "what"; the docstring tells you "why" and "what can go wrong."
- **Never** leave stale comments that contradict the code. A wrong comment is worse than no comment.
- **Always** document: side effects (writes to DB, sends email, modifies global state), thread-safety guarantees, and performance characteristics for large inputs.
## Pitfalls
- **Comments that drift from code**: Parameter renamed from `user` to `account` but the comment still says "the user object." Use a linter to flag mismatches.
- **Docstrings that restate the signature verbatim**: `def foo(x: int) -> str: """Foo takes x and returns a string."""` — this adds zero information. Delete it or explain the contract.
- **Over-commenting clean code**: If every line has a comment, the code is either too complex or the comments are noise. Refactor instead.
- **TODO as a graveyard**: "TODO: handle errors" from 2022. TODOs without ticket links rot. File an issue and reference it, or fix it now.
- **Forgetting to update docstrings when changing behavior**: Added a new `raises` condition? Update the docstring. Changed the return format? Update the docstring.
## Verify / Checklist
- [ ] Every public function/class has a docstring with purpose, params, return, and raises
- [ ] No comment restates what the code visibly does; every comment explains why or documents a constraint
- [ ] No commented-out code remains
- [ ] All TODO comments link to a ticket or issue
- [ ] Docstrings match current function signatures (run `pydocstyle` or `darglint`)
- [ ] Function parameter names in docstrings match actual parameter names
- [ ] Side effects and thread-safety notes are documented where applicable
Attached files
No attached files.