Overview
Documentation standards establish systematic approaches to recording technical information about software systems, APIs, codebases, and processes. These standards govern structure, formatting, content organization, and maintenance practices across different documentation types including inline code comments, API references, architectural documents, and user guides.
The absence of documentation standards leads to inconsistent documentation quality, fragmented information architecture, and increased onboarding time for new team members. Different developers document code in conflicting styles, making the codebase harder to navigate. API documentation becomes unreliable when some endpoints receive detailed coverage while others lack basic descriptions. Architecture decisions remain undocumented or scattered across email threads and chat messages.
Documentation standards address these issues by defining clear expectations for what gets documented, how documentation is structured, where it lives, and who maintains it. Projects with established documentation standards show measurable improvements in developer productivity, reduced bug counts from misunderstood APIs, and faster resolution of production issues.
Different documentation types serve distinct purposes and audiences. API documentation targets developers integrating with services, requiring precise parameter descriptions and usage examples. Inline comments explain complex logic to maintainers modifying code. Architecture documents communicate system design to technical leadership making strategic decisions. Each documentation type demands specific standards appropriate to its audience and purpose.
# Example: Well-documented Ruby class following standards
class PaymentProcessor
# Processes payment transactions through configured gateway.
#
# @param amount [Decimal] transaction amount in currency base units
# @param currency [String] ISO 4217 currency code
# @param payment_method [PaymentMethod] validated payment method
# @return [Transaction] completed transaction record
# @raise [PaymentError] when gateway rejects transaction
# @raise [ValidationError] when parameters fail validation
def process_payment(amount, currency, payment_method)
validate_amount(amount, currency)
gateway_response = gateway.charge(amount, currency, payment_method)
Transaction.create_from_response(gateway_response)
end
end
Documentation standards evolve with project maturity. Early-stage projects benefit from lightweight standards focusing on critical paths and public APIs. Mature systems with multiple teams require comprehensive standards covering cross-service contracts, data schemas, and operational procedures.
Key Principles
Documentation serves as a contract between code authors and future maintainers, including the original author months later. This contract specifies expected behavior, constraints, and context necessary for confident modification without breaking existing functionality. Documentation standards formalize this contract through consistent structure and content expectations.
Proximity principle states documentation should exist as close as possible to what it documents. Method documentation belongs in source files above method definitions, not in separate wiki pages. Architecture decisions documented in code repositories stay synchronized with implementation changes, while external documents quickly become outdated.
Minimum viable documentation balances completeness against maintenance burden. Over-documentation creates maintenance debt as code changes require updating multiple documentation sources. Under-documentation forces developers into code archaeology, piecing together behavior from implementation details. Standards define the minimum documentation threshold where benefits outweigh costs.
Single source of truth eliminates documentation contradictions by designating one authoritative source for each piece of information. When the same information appears in multiple locations, inconsistencies emerge as some copies get updated while others remain stale. Standards specify which documentation location is authoritative for each information type.
Audience-appropriate depth recognizes different readers need different information. Public API consumers need usage examples and parameter constraints but not implementation details. Internal maintainers need architectural context and design rationale. Operations teams need deployment procedures and troubleshooting guides. Standards segment documentation by audience to prevent information overload.
Documentation exists in three temporal categories: historical context explaining why decisions were made, current state describing how systems work now, and future intent outlining planned changes. Historical documentation prevents repeating past mistakes. Current documentation enables safe modifications. Future documentation coordinates team efforts. Standards ensure all three temporal categories receive appropriate coverage.
Signal-to-noise ratio measures documentation usefulness against clutter. High signal documentation provides information not obvious from reading code. Low signal documentation restates what code already clearly expresses, creating noise that obscures valuable information. Standards discourage noise while requiring signal.
# Low signal - obvious from method name
# Gets user by ID
def get_user(id)
User.find(id)
end
# High signal - explains non-obvious constraint
# Retrieves user account by ID. Returns nil for soft-deleted users
# to maintain referential integrity in audit logs. Use find_including_deleted
# when auditing requires accessing deleted accounts.
def get_user(id)
User.where(id: id, deleted_at: nil).first
end
Executable documentation validates accuracy through automated testing. Code examples in documentation that execute as part of test suites cannot become outdated without breaking builds. Standards promote documentation forms that enable automated validation.
Documentation standards distinguish between explanatory comments clarifying complex logic and documentary comments describing interfaces and behavior. Explanatory comments answer "how does this work" for maintainers reading implementation. Documentary comments answer "how do I use this" for consumers calling interfaces. Both serve important but distinct roles.
Progressive disclosure structures documentation so readers encounter basic information first, with detailed information available on demand. Method documentation leads with concise summaries before parameter details. README files present quick start guides before comprehensive references. Standards enforce progressive disclosure through required sections and recommended orderings.
Implementation Approaches
Documentation-as-code treats documentation as first-class source artifacts stored in version control alongside code. Documentation changes flow through the same code review process as implementation changes. Pull requests that modify behavior without updating documentation get rejected. This approach ensures documentation evolves synchronously with code.
Version control provides documentation history, showing when information changed and why. Blame annotations reveal which commit introduced documentation claims, enabling verification against implementation. Branch workflows allow documentation updates to be developed and reviewed in isolation before merging.
Documentation-as-code enables automated deployment pipelines that publish documentation updates immediately after merging. Continuous integration validates documentation builds, catches broken links, and runs example code. Documentation infrastructure mirrors code infrastructure.
Literate programming inverts traditional code organization by treating documentation as primary and code as secondary. Source files contain prose explanations interspersed with code blocks. Documentation tools extract code blocks for compilation while formatting prose for reading. This approach works well for algorithm implementations and data transformations where understanding the approach matters more than optimization.
# Literate programming example using Markdown
# ## Binary Search Implementation
#
# Binary search finds elements in sorted arrays by repeatedly
# dividing the search interval in half. Time complexity O(log n).
#
# Start with the full array range:
def binary_search(array, target)
left = 0
right = array.length - 1
# Continue while the search interval contains elements:
while left <= right
# Calculate midpoint, avoiding integer overflow:
mid = left + (right - left) / 2
# Check if we found the target:
return mid if array[mid] == target
# Discard half the remaining elements:
if array[mid] < target
left = mid + 1
else
right = mid - 1
end
end
# Target not found:
nil
end
Documentation generation extracts documentation from structured comments in source code, producing formatted references automatically. Developers write documentation once in source files. Build processes generate HTML, PDF, or other formats for distribution. Changes to source documentation automatically propagate to published references.
Generated documentation maintains consistency through enforced structure. Documentation generators parse specific comment formats, ensuring all documented methods provide required sections like parameters and return values. Missing required sections trigger build warnings or errors.
Wiki-based documentation uses collaborative editing platforms for documentation that changes frequently or requires non-technical contributor input. Wikis support rapid iteration without code review overhead. However, wikis disconnect documentation from code version control, creating synchronization challenges. Standards limit wiki usage to documentation that benefits from wiki characteristics like cross-project comparison matrices or operational runbooks frequently updated by on-call engineers.
Embedded documentation places documentation directly in artifacts it describes. Database schema documentation exists in migration files. API documentation embeds in API specification files like OpenAPI documents. Configuration documentation lives in example configuration files with inline comments. Embedded documentation travels with artifacts, remaining accessible when artifacts move between environments.
Hybrid approaches combine multiple strategies. Public APIs use documentation generation for reference material while maintaining wiki pages for integration guides and tutorials. Critical algorithms use literate programming in source files while system architecture uses separate diagram-focused documents. Standards specify which approach applies to which documentation type.
Ruby Implementation
Ruby provides multiple documentation approaches through language features and community tools. The language's expressive syntax and emphasis on developer happiness extends to documentation practices.
RDoc is Ruby's built-in documentation system. RDoc processes special comment blocks preceding classes, modules, and methods, generating HTML references. RDoc comments use a markup language supporting headings, lists, code blocks, and links.
# Payment gateway integration handling transaction processing.
#
# = Usage
#
# gateway = PaymentGateway.new(api_key: ENV['GATEWAY_KEY'])
# result = gateway.charge(amount: 1000, currency: 'USD')
#
# = Configuration
#
# Set the following environment variables:
# * GATEWAY_KEY - API authentication key
# * GATEWAY_ENDPOINT - Custom endpoint URL (optional)
#
class PaymentGateway
# Charges a payment method for the specified amount.
#
# Validates amount and currency before submitting to gateway.
# Gateway responses are idempotent - retry logic handles network failures.
#
# == Parameters
#
# amount:: Transaction amount in currency base units (cents)
# currency:: ISO 4217 currency code
# payment_method:: Token identifying payment method
#
# == Returns
#
# Transaction result with gateway reference ID
#
# == Raises
#
# GatewayError:: Gateway rejected transaction
# ValidationError:: Invalid parameters
#
def charge(amount:, currency:, payment_method:)
validate_transaction(amount, currency)
submit_to_gateway(amount, currency, payment_method)
end
end
RDoc integrates with Ruby's ri command-line documentation viewer. Developers query method documentation from terminals without leaving their workflow. Standard library documentation ships as RDoc format, establishing community familiarity.
YARD (Yet Another Ruby Documentation) extends RDoc with additional features and stricter structure. YARD introduces tags for parameter types, return values, and exceptions, enabling type checking tools to validate documentation claims against code.
class OrderProcessor
# Processes pending orders through fulfillment pipeline.
#
# @param order_ids [Array<Integer>] IDs of orders to process
# @param priority [Symbol] processing priority (:standard, :expedited, :urgent)
# @return [Hash<Integer, OrderResult>] results indexed by order ID
# @raise [OrderNotFoundError] if any order ID does not exist
# @raise [InvalidStateError] if order not in pending state
# @example Process urgent orders
# processor = OrderProcessor.new
# results = processor.process([123, 456], priority: :urgent)
# results[123].status # => :shipped
# @note Requires active fulfillment center connection
# @see FulfillmentCenter#process_order
def process(order_ids, priority: :standard)
orders = Order.find(order_ids)
orders.map { |order| [order.id, process_single(order, priority)] }.to_h
end
end
YARD tags provide structured information for documentation consumers and automated tools. Type annotations in tags enable static analysis tools to detect type mismatches. The @example tag contains executable code validated by test suites, preventing example drift.
Inline comments explain non-obvious implementation details. Ruby convention places explanatory comments on lines before the code they describe, using # markers. Inline comments complement documentary comments by addressing different audiences.
def calculate_discount(subtotal, customer)
# Loyalty tier discounts apply before promotional codes to prevent
# discount stacking that reduces margins below sustainable levels
base_discount = loyalty_discount(customer.tier)
# Promotional codes calculate from already-discounted price
# to match customer expectations from marketing materials
promo_discount = apply_promo_code(
subtotal * (1 - base_discount),
customer.active_promo
)
base_discount + promo_discount
end
Ruby's module system enables documentation organization. Documentation for module methods describes module-level behavior. Documentation for included instance methods describes behavior in including classes. Standards clarify where shared behavior gets documented.
# Audit logging mixin providing transaction tracking.
#
# Include in models requiring audit trail. Creates audit records
# for all attribute changes, associating changes with current user.
#
# @example Basic usage
# class Account < ApplicationRecord
# include Auditable
# auditable_attributes :balance, :status
# end
module Auditable
extend ActiveSupport::Concern
# Declares attributes that trigger audit logging.
#
# @param attrs [Array<Symbol>] attribute names to audit
# @return [void]
def self.auditable_attributes(*attrs)
attrs.each { |attr| audit_attribute(attr) }
end
end
TomDoc offers an alternative documentation format emphasizing human readability. TomDoc uses plain English sentences and avoids special markup syntax. While less common than YARD, some Ruby projects prefer TomDoc's minimal syntax.
Ruby gem documentation typically includes multiple files: README with overview and quick start, CHANGELOG tracking version changes, LICENSE specifying usage terms, and generated API reference. Standards specify content expectations for each file type.
Common Patterns
API documentation pattern structures interface documentation consistently across all public methods. Each method includes purpose summary, parameter descriptions with types and constraints, return value specification, exception documentation, and usage examples. Code review checklists verify new public methods meet documentation standards.
class APIClient
# Retrieves resource by identifier from API.
#
# @param resource_type [String] type of resource ('users', 'posts')
# @param id [Integer, String] resource identifier
# @param options [Hash] request options
# @option options [Boolean] :include_deleted (false) include soft-deleted records
# @option options [Array<String>] :fields fields to return (returns all if not specified)
# @return [Hash] resource attributes
# @return [nil] if resource not found and not_found_ok option set
# @raise [NotFoundError] if resource does not exist
# @raise [AuthenticationError] if credentials invalid
# @raise [RateLimitError] if rate limit exceeded
# @example Retrieve user with specific fields
# client.get('users', 123, fields: ['name', 'email'])
def get(resource_type, id, options = {})
request(:get, "/#{resource_type}/#{id}", options)
end
end
Decision record pattern documents architectural choices explaining both the decision and its context. Each decision record includes problem statement, considered alternatives with pros and cons, chosen solution, and implications. Decision records remain in version control as historical context, never deleted even when decisions change.
# DECISION RECORD: Cache invalidation strategy
#
# Context:
# User profile updates must propagate to all API servers within 5 seconds.
# Previous broadcast invalidation caused thundering herd on database.
#
# Considered alternatives:
# 1. TTL-based expiration: Simple but allows stale reads up to TTL duration
# 2. Version-based invalidation: Requires version field in all cached entities
# 3. Targeted invalidation with bloom filter: Complex implementation
#
# Decision:
# Implement version-based invalidation using optimistic locking.
#
# Implications:
# - All cached models need version column
# - Cache keys must include version number
# - Migration required for existing cached models
#
class CachedUserProfile
def self.fetch(user_id)
user = User.find(user_id)
cache_key = "user:#{user_id}:v#{user.lock_version}"
Rails.cache.fetch(cache_key) { serialize_user(user) }
end
end
README template pattern standardizes repository documentation structure. READMEs follow consistent sections: overview, prerequisites, installation, configuration, usage examples, testing instructions, deployment procedures, and contribution guidelines. New repositories start from README templates ensuring no critical sections get omitted.
Code example pattern demonstrates typical usage covering common scenarios. Examples are complete, runnable, and realistic rather than minimal toy examples. Examples progress from simple cases to complex scenarios, building reader understanding incrementally.
# EXAMPLE: Processing uploaded files
#
# Basic usage:
uploader = FileUploader.new(storage: :s3)
result = uploader.upload(file: params[:file])
# => {url: "https://bucket.s3.amazonaws.com/files/abc123.pdf", size: 45632}
# With custom options:
result = uploader.upload(
file: params[:file],
content_type: 'application/pdf',
metadata: {uploaded_by: current_user.id}
)
# Handling errors:
begin
result = uploader.upload(file: params[:file])
rescue FileUploader::InvalidTypeError => e
flash[:error] = "File type not allowed: #{e.detected_type}"
redirect_back(fallback_location: root_path)
rescue FileUploader::FileTooLargeError => e
flash[:error] = "File exceeds maximum size of #{e.max_size} bytes"
redirect_back(fallback_location: root_path)
end
Changelog pattern tracks public changes between versions. Changelogs organize entries by release version and change type: added features, changed behavior, deprecated functionality, removed features, fixed bugs, and security patches. Semantic versioning correlates with changelog categories.
Migration documentation pattern includes comments in database migrations explaining purpose and dependencies. Complex data transformations include rollback instructions. Breaking changes document affected code requiring updates.
class AddAuthTokenToUsers < ActiveRecord::Migration[7.0]
# Adds authentication token field for API access.
#
# Impact: Existing API authentication using session cookies continues working.
# New token-based authentication available after migration.
#
# Rollback: Removes auth_token column but does not revoke existing sessions.
# Deployed code must handle missing auth_token gracefully during rollback.
def change
add_column :users, :auth_token, :string
add_index :users, :auth_token, unique: true
# Generate tokens for existing users to enable immediate usage
reversible do |dir|
dir.up do
User.find_each do |user|
user.update_column(:auth_token, SecureRandom.hex(32))
end
end
end
end
end
Module documentation pattern documents both module usage when included in classes and module singleton methods. Documentation clarifies module dependencies and required implementing class methods.
Practical Examples
Documenting complex business logic requires balancing detail against readability. A payment processing service contains business rules about discount stacking, tax calculation, and refund policies. Documentation explains not just what the code does but why these specific rules exist.
class OrderPricingCalculator
# Calculates final order price applying discounts and taxes.
#
# Pricing follows multi-step process ensuring consistent treatment
# across all order types while preventing discount abuse:
#
# 1. Calculate base price from line items
# 2. Apply loyalty tier discounts (percentage-based)
# 3. Apply promotional codes (fixed amount or percentage)
# 4. Add applicable taxes to discounted subtotal
# 5. Add shipping costs (calculated from discounted weight)
#
# @param order [Order] order being priced
# @return [PricingResult] detailed price breakdown
# @raise [InvalidPricingStateError] if order missing required attributes
#
# @note Promotional codes calculate from loyalty-discounted price to prevent
# combining percentage-based loyalty discounts with percentage-based promo
# codes, which could reduce prices below cost. See decision record DR-2024-03.
#
# @example Enterprise customer with promotional code
# calculator = OrderPricingCalculator.new
# result = calculator.calculate(order)
# result.base_price # => 10000 (cents)
# result.loyalty_discount # => 1000 (10% enterprise discount)
# result.promo_discount # => 450 (5% code on discounted price)
# result.tax # => 726 (8.5% tax rate)
# result.shipping # => 800
# result.total # => 10076
def calculate(order)
validate_order_state(order)
base_price = calculate_base_price(order)
loyalty_discount = apply_loyalty_discount(base_price, order.customer)
discounted_price = base_price - loyalty_discount
promo_discount = apply_promotional_code(discounted_price, order.promo_code)
subtotal = discounted_price - promo_discount
tax = calculate_tax(subtotal, order.shipping_address)
shipping = calculate_shipping(order, discounted_price)
PricingResult.new(
base_price: base_price,
loyalty_discount: loyalty_discount,
promo_discount: promo_discount,
tax: tax,
shipping: shipping,
total: subtotal + tax + shipping
)
end
end
Documenting background job systems requires explaining retry behavior, failure handling, and system dependencies. Background jobs often execute hours after creation, making debugging difficult without comprehensive documentation.
# Processes large batch imports in background with retry logic.
#
# Import jobs split CSV files into chunks, processing each chunk
# independently to enable partial failure recovery. Failed chunks
# retry with exponential backoff up to 5 attempts before marking
# import as failed.
#
# = Monitoring
#
# Failed imports trigger PagerDuty alerts. Check Sidekiq dashboard
# for stuck jobs or repeatedly failing imports. Common failure modes:
#
# - External API rate limiting: Retry succeeds after backoff
# - Malformed CSV data: Requires manual data fix and import restart
# - Database deadlocks: Retry succeeds, temporary condition
#
# = Dependencies
#
# Requires active S3 connection for reading uploaded files.
# Requires external validation API for address verification.
# Address validation API timeout set to 5 seconds per row.
#
# = Recovery
#
# Import progress stored in Redis. Restart failed import from last
# successful chunk using ImportJob.resume(import_id).
#
class ImportJob < ApplicationJob
queue_as :imports
retry_on StandardError, wait: :exponentially_longer, attempts: 5
discard_on ActiveJob::DeserializationError
# Processes import job for given import record.
#
# @param import_id [Integer] ID of Import record to process
# @return [void]
def perform(import_id)
import = Import.find(import_id)
processor = ImportProcessor.new(import)
processor.process_chunks do |chunk_result|
# Update progress after each chunk for recovery
import.update!(
processed_rows: import.processed_rows + chunk_result.row_count,
last_processed_chunk: chunk_result.chunk_number
)
end
import.complete!
rescue => e
import.fail!(error_message: e.message, error_backtrace: e.backtrace)
raise
end
end
Documenting data transformations traces data flow through complex pipelines. ETL processes require documentation explaining source data formats, transformation logic, validation rules, and output schemas.
module DataPipeline
# Transforms raw webhook events into normalized analytics records.
#
# = Input Format
#
# Webhook events arrive as JSON with schema:
# {
# event_type: string,
# timestamp: ISO8601 datetime,
# user_id: string (may be null for anonymous),
# properties: object (event-specific attributes)
# }
#
# = Transformation Steps
#
# 1. Parse and validate timestamp (reject events >24 hours old)
# 2. Resolve user_id to internal account ID (create anonymous record if null)
# 3. Extract and normalize properties based on event_type
# 4. Enrich with session context (IP geolocation, device fingerprint)
# 5. Write to analytics database and stream to data warehouse
#
# = Validation Rules
#
# - Event timestamps must be within 24 hours of receipt
# - User IDs must exist in accounts table or be null
# - Required properties must be present for each event type
# - Property values must match expected types
#
# = Output Schema
#
# Analytics records written to events table:
# - id: UUID primary key
# - event_type: string (normalized to lowercase)
# - occurred_at: timestamp (from webhook timestamp field)
# - received_at: timestamp (when event entered system)
# - account_id: foreign key (null for anonymous)
# - session_id: UUID (correlates events within session)
# - properties: jsonb (normalized event attributes)
# - enrichment: jsonb (geolocation, device info)
#
class WebhookTransformer
# Transforms webhook payload into analytics event.
#
# @param payload [Hash] raw webhook JSON
# @return [AnalyticsEvent] persisted event record
# @raise [ValidationError] if payload fails validation
# @raise [TransformationError] if transformation fails
def transform(payload)
validate_payload(payload)
event_type = normalize_event_type(payload['event_type'])
occurred_at = parse_timestamp(payload['timestamp'])
account = resolve_account(payload['user_id'])
properties = extract_properties(payload['properties'], event_type)
enrichment = enrich_with_context(payload)
AnalyticsEvent.create!(
event_type: event_type,
occurred_at: occurred_at,
received_at: Time.current,
account_id: account&.id,
properties: properties,
enrichment: enrichment
)
end
end
end
Documenting configuration systems explains available options, default values, precedence rules, and environment-specific overrides. Configuration affects runtime behavior, making comprehensive documentation critical for operations teams.
# Application configuration with environment-specific overrides.
#
# = Configuration Sources (in precedence order)
#
# 1. Environment variables (highest priority)
# 2. config/settings.local.yml (not in version control)
# 3. config/settings/#{environment}.yml
# 4. config/settings.yml (defaults)
#
# = Key Configuration Options
#
# database.pool_size:: Database connection pool size (default: 5)
# Increase for high-concurrency workloads. Set to min(max_threads * 1.5).
#
# cache.redis_url:: Redis connection URL (default: localhost:6379)
# Must point to same Redis instance across all app servers.
#
# external_api.timeout:: API request timeout in seconds (default: 30)
# Balance between user experience and avoiding stuck connections.
#
# external_api.retry_attempts:: Failed request retry count (default: 3)
# Retries use exponential backoff. Set to 0 to disable retries.
#
# features.new_ui_enabled:: Enable redesigned UI (default: false)
# Controlled via feature flag. Requires asset recompilation.
#
# = Environment Variables
#
# DATABASE_URL:: PostgreSQL connection string (required in production)
# REDIS_URL:: Redis connection string (required if cache enabled)
# SECRET_KEY_BASE:: Secret for session encryption (required in production)
# API_KEY:: External service API key (required for API features)
# SENTRY_DSN:: Error tracking URL (optional)
#
module Settings
# Returns configuration value for given key path.
#
# @param key_path [String] dot-separated path like 'database.pool_size'
# @return [Object] configuration value
# @raise [ConfigurationError] if key not found and no default
#
# @example Get database pool size
# Settings.get('database.pool_size') # => 10
def self.get(key_path)
env_override(key_path) || file_config(key_path) || raise_missing(key_path)
end
end
Common Pitfalls
Over-documenting obvious behavior clutters code with noise that obscures important information. Documentation stating "returns true if condition is true" provides zero value while increasing maintenance burden. Developers skip reading documentation when it consistently wastes their time with obvious statements.
# BAD: Obvious documentation adding no value
# Returns the user's email
def email
@email
end
# BAD: Restating method name
# Gets the order by ID
def get_order(id)
Order.find(id)
end
# GOOD: Documents non-obvious behavior
# Returns user's primary email. Falls back to alternate email if primary
# is unverified and user has verified alternate. Returns nil if no verified
# email exists.
def email
primary_verified? ? primary_email : verified_alternate_email
end
# GOOD: Documents important constraint
# Retrieves order by ID. Raises ActiveRecord::RecordNotFound if order
# does not exist or belongs to different account than current user.
def get_order(id)
current_account.orders.find(id)
end
Documenting implementation instead of interface couples documentation to internal details that should remain hidden. Documentation should specify behavior observable to callers, not internal algorithms that may change. Implementation documentation belongs in inline comments, not interface documentation.
Stale documentation contradicts current code behavior after updates modify code without updating documentation. Automated checks mitigate staleness but cannot eliminate it. Code reviews must verify documentation updates accompany behavior changes. Test suites that execute documented examples catch some staleness through test failures.
Missing error documentation leaves callers unprepared for exceptions. Undocumented exceptions surprise developers during production failures. Comprehensive error documentation enables proper exception handling and helps API consumers design robust integration code.
# BAD: Missing error documentation
# Processes payment transaction
def charge(amount, payment_method)
gateway.charge(amount, payment_method)
end
# GOOD: Complete error documentation
# Processes payment transaction.
#
# @param amount [Integer] amount in cents
# @param payment_method [PaymentMethod] validated payment method
# @return [Transaction] completed transaction
# @raise [InsufficientFundsError] payment method has insufficient funds
# @raise [ExpiredCardError] payment method expired
# @raise [FraudDetectedError] transaction flagged by fraud detection
# @raise [GatewayTimeoutError] gateway did not respond within timeout
# @raise [GatewayError] gateway returned unexpected error
def charge(amount, payment_method)
gateway.charge(amount, payment_method)
rescue GatewayClient::CardError => e
raise map_gateway_error(e)
end
Insufficient examples force readers to imagine how code works. A single minimal example shows basic usage but not real-world scenarios. Multiple examples showing progression from simple to complex usage patterns help readers understand both basics and advanced features.
Documentation-code drift occurs when documentation describes intended behavior while code implements different behavior. This drift emerges from implementation compromises not reflected in documentation, or documentation written before implementation that never gets corrected. Tests that verify documentation examples help catch drift.
Missing context in comments references decisions, issues, or discussions without providing enough information for future readers to understand reasoning. Comments like "fixes bug in production" help the commit author but not developers investigating related issues months later.
# BAD: Missing context
# Fix for production bug
def calculate_discount(amount)
amount * 0.1
end
# GOOD: Includes context
# Discount calculation capped at 10% to prevent edge case where combining
# multiple promotions could result in negative final price. See incident
# report INC-2024-042 for details on production impact. Alternative solutions
# considered: validation in checkout flow, but discount calculation encapsulation
# preferred for maintainability.
def calculate_discount(amount)
[amount * discount_percentage, amount * 0.1].min
end
Assuming audience knowledge writes documentation for experts when actual readers have varying experience levels. Public API documentation should not assume familiarity with internal architecture. Onboarding documentation should not assume expertise with company-specific tooling.
Copy-paste documentation duplicates content across multiple locations instead of using links to single source of truth. When duplicated content needs updates, some copies get updated while others remain stale, creating contradictions.
Unclear ownership leaves documentation maintenance responsibility ambiguous. Without clear ownership, documentation updates fall through the cracks. Critical documentation should have designated maintainers responsible for keeping it current.
Reference
Documentation Types and Location
| Type | Location | Audience | Update Frequency |
|---|---|---|---|
| API Reference | Generated from source | Developers using API | Every code change |
| Architecture Docs | docs/ directory | Technical leadership | Quarterly or major changes |
| Inline Comments | Source files | Code maintainers | During implementation |
| README | Repository root | All stakeholders | Project milestones |
| Changelogs | CHANGELOG.md | Users and integrators | Each release |
| Runbooks | Wiki or docs/operations/ | Operations team | Monthly or incidents |
| Decision Records | docs/decisions/ | Technical team | When decisions made |
Ruby Documentation Tags (YARD)
| Tag | Purpose | Example |
|---|---|---|
| @param | Parameter description with type | @param name [String] user name |
| @option | Hash option description | @option opts [Boolean] :verified check status |
| @return | Return value type and description | @return [User, nil] found user or nil |
| @raise | Exception that may be raised | @raise [NotFoundError] when missing |
| @yield | Block parameters | @yield [item] gives each item |
| @yieldparam | Block parameter type | @yieldparam item [String] item name |
| @yieldreturn | Block return expectation | @yieldreturn [Boolean] true to continue |
| @example | Usage example | @example Basic usage |
| @note | Important notice | @note Requires authentication |
| @see | Cross-reference | @see #related_method |
| @since | Version when added | @since 2.1.0 |
| @deprecated | Deprecation notice | @deprecated Use new_method instead |
Comment Conventions
| Convention | Usage | Example |
|---|---|---|
| Single line # | Inline explanation | # Calculate tax before discount |
| Multi-line # blocks | Method/class documentation | Multiple # lines forming doc block |
| FIXME: | Known issues needing fix | # FIXME: Handle edge case with nil |
| TODO: | Planned improvements | # TODO: Optimize query performance |
| HACK: | Non-ideal solution | # HACK: Workaround for gem bug |
| NOTE: | Important information | # NOTE: Must run before validation |
| OPTIMIZE: | Performance opportunity | # OPTIMIZE: Cache this calculation |
README Sections
| Section | Required | Content |
|---|---|---|
| Project Name & Description | Yes | One-paragraph overview |
| Prerequisites | If applicable | Required software and versions |
| Installation | Yes | Step-by-step setup instructions |
| Configuration | If applicable | Environment variables and settings |
| Usage | Yes | Basic usage examples |
| Testing | Yes | How to run test suite |
| Deployment | For services | Deployment procedures |
| Contributing | For open source | Contribution guidelines |
| License | Yes | License information |
Code Example Elements
| Element | Purpose | Best Practice |
|---|---|---|
| Setup code | Establish context | Show object creation and configuration |
| Invocation | Demonstrate usage | Call method with realistic parameters |
| Output | Show results | Include return values or side effects |
| Error handling | Show failure cases | Demonstrate exception handling |
| Comments | Explain non-obvious | Clarify intent without stating obvious |
| Progression | Build understanding | Start simple, add complexity |
Decision Record Format
| Section | Content |
|---|---|
| Title | Concise decision statement |
| Status | Proposed, Accepted, Deprecated, Superseded |
| Context | Problem being solved, constraints, requirements |
| Decision | Chosen solution with rationale |
| Consequences | Positive and negative implications |
| Alternatives Considered | Options evaluated but not chosen |
Documentation Quality Checklist
| Criterion | Check |
|---|---|
| Accuracy | Documentation matches current code behavior |
| Completeness | All public interfaces documented |
| Clarity | Technical terms defined, examples included |
| Currency | Updated within last 6 months |
| Accessibility | Appropriate depth for target audience |
| Executability | Examples run without modification |
| Error coverage | Exceptions and edge cases documented |
| Context | Why decisions made, not just what |