Overview
Ruby documentation encompasses multiple tools and conventions for creating maintainable technical documentation. The primary documentation systems include RDoc, which ships with Ruby and generates HTML documentation from source code comments, and YARD, which provides enhanced markup syntax and extensibility. Both systems parse specially formatted comments to extract API documentation, method signatures, parameter information, and usage examples.
RDoc uses a simple markup syntax embedded in Ruby comments. Comments beginning with ##
or #
immediately before method definitions become part of the generated documentation. RDoc recognizes standard formatting including *bold*
, _italic_
, and +code+
markup, along with structured elements like numbered lists and definition lists.
# Calculates compound interest for a given principal amount
#
# * +principal+ - The initial amount (Float)
# * +rate+ - Annual interest rate as decimal (Float)
# * +time+ - Investment period in years (Integer)
#
# Returns the total amount after compound interest calculation
def compound_interest(principal, rate, time)
principal * (1 + rate) ** time
end
YARD extends RDoc capabilities with structured tags and improved type information. YARD tags like @param
, @return
, and @example
provide semantic meaning to documentation elements. The tag-based approach enables better tooling integration and more precise API contracts.
# @param [String] name The user's full name
# @param [Integer] age The user's age in years
# @return [Hash] User profile with normalized data
# @example Creating a user profile
# create_profile("John Doe", 25)
# #=> {name: "John Doe", age: 25, slug: "john-doe"}
def create_profile(name, age)
{
name: name,
age: age,
slug: name.downcase.gsub(/\s+/, '-')
}
end
Documentation generation integrates with standard Ruby workflows through rake tasks and command-line tools. The rdoc
command processes Ruby files to produce HTML documentation trees, while yardoc
provides similar functionality with additional customization options. Both tools support configuration files for consistent output across projects.
Basic Usage
Method documentation follows established patterns for parameter description, return value specification, and usage examples. Document public API methods completely while keeping private method documentation minimal. Focus documentation efforts on methods that external developers will call directly.
class DatabaseConnection
# Establishes connection to database using provided configuration
#
# @param [Hash] config Database connection parameters
# @option config [String] :host Database server hostname
# @option config [Integer] :port Database server port (default: 5432)
# @option config [String] :database Database name
# @return [DatabaseConnection] Connected database instance
# @raise [ConnectionError] When connection parameters are invalid
def self.connect(config)
validate_config(config)
new(config).tap(&:establish_connection)
end
# Executes SQL query with parameter binding
#
# @param [String] sql The SQL query with placeholder markers
# @param [Array] params Parameters to bind to query placeholders
# @return [Array<Hash>] Query results as array of row hashes
def query(sql, params = [])
prepare_statement(sql).execute(params)
end
end
Class documentation describes the overall purpose, primary usage patterns, and key concepts. Include initialization requirements and basic workflow examples. Document class-level configuration options and their effects on instance behavior.
# Manages user authentication and session handling
#
# Provides secure authentication mechanisms including password hashing,
# session token generation, and multi-factor authentication support.
# Integrates with external identity providers through OAuth2 protocols.
#
# @example Basic authentication workflow
# auth = AuthManager.new(config)
# user = auth.authenticate(username, password)
# session = auth.create_session(user)
#
# @example OAuth integration
# auth.configure_oauth(:google, client_id: 'xxx', secret: 'yyy')
# redirect_url = auth.oauth_authorization_url(:google)
class AuthManager
def initialize(config)
@config = config
@providers = {}
end
end
Module documentation explains the module's role in the system architecture and how including classes should use its functionality. Document module constants, class methods, and instance methods that modules contribute to including classes.
# Provides caching functionality for expensive operations
#
# Implements multiple caching strategies including memory, file system,
# and distributed caching. Automatically handles cache key generation,
# expiration policies, and cache invalidation patterns.
#
# @example Memory caching
# class Calculator
# include Cacheable
#
# cache_method :fibonacci, ttl: 3600
# def fibonacci(n)
# # expensive calculation
# end
# end
module Cacheable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
# Enables caching for the specified method
# @param [Symbol] method_name Method to cache
# @param [Hash] options Caching options
# @option options [Integer] :ttl Time to live in seconds
def cache_method(method_name, options = {})
# implementation
end
end
end
Constant documentation explains the constant's purpose and acceptable values. Include examples of proper usage and any constraints on the constant's value. Document relationships between related constants within the same module or class.
class HTTPClient
# Default timeout values for HTTP operations
TIMEOUT_OPTIONS = {
connect: 10, # Connection establishment timeout (seconds)
read: 30, # Response read timeout (seconds)
write: 30 # Request write timeout (seconds)
}.freeze
# HTTP status codes requiring retry logic
RETRYABLE_CODES = [408, 429, 502, 503, 504].freeze
# Maximum number of retry attempts for failed requests
MAX_RETRIES = 3
end
Advanced Usage
Custom YARD tags extend documentation capabilities for domain-specific requirements. Define tags using YARD's tag registration system to capture specialized metadata. Custom tags enable documentation of application-specific concepts like permissions, feature flags, or API versioning information.
# Register custom tags for API documentation
YARD::Tags::Library.define_tag("API Version", :api_version)
YARD::Tags::Library.define_tag("Required Permission", :requires_permission)
YARD::Tags::Library.define_tag("Feature Flag", :feature_flag)
class UserController
# Updates user profile information
#
# @api_version 2.1
# @requires_permission users:write
# @feature_flag profile_editing_v2
# @param [Integer] user_id Target user identifier
# @param [Hash] attributes Profile attributes to update
# @return [User] Updated user object
def update_profile(user_id, attributes)
# implementation
end
end
Documentation templates standardize formatting across large codebases. YARD supports custom templates for consistent HTML output styling and content organization. Templates can include project-specific navigation, branding, and additional metadata sections.
# .yardopts configuration for custom templates
--template-path templates/
--template custom_template
--markup markdown
--charset utf-8
--output-dir doc/
--protected
--private
lib/**/*.rb
Macro definitions reduce documentation duplication for repetitive patterns. YARD macros expand into full documentation blocks, maintaining consistency while reducing maintenance overhead. Macros handle common patterns like attribute accessors, delegation methods, and configuration options.
# Define macro for documented attribute accessors
# @!macro [new] attr_documented
# @!attribute [rw] $1
# @param [Object] value The $1 value
# @return [Object] The current $1 value
class Configuration
# @!macro attr_documented
attr_accessor :timeout
# @!macro attr_documented
attr_accessor :max_retries
# @!macro attr_documented
attr_accessor :base_url
end
Cross-reference linking connects related documentation elements automatically. YARD generates links between classes, methods, and modules based on naming conventions and explicit reference syntax. Maintain reference integrity across refactoring by using consistent naming patterns.
class PaymentProcessor
# Processes payment using configured gateway
#
# Uses {GatewayConfig#primary_gateway} for payment routing.
# Logs transaction details via {TransactionLogger#record}.
# Raises {PaymentError} for processing failures.
#
# @param [Payment] payment Payment details
# @see GatewayConfig#configure_gateway
# @see TransactionLogger
def process_payment(payment)
gateway = GatewayConfig.primary_gateway
TransactionLogger.record(payment, gateway)
gateway.charge(payment.amount, payment.source)
rescue Gateway::Error => e
raise PaymentError, "Payment failed: #{e.message}"
end
end
Conditional documentation addresses version-specific or environment-specific behavior. Document feature availability, deprecation timelines, and alternative implementations. Use YARD's conditional processing for version-specific documentation blocks.
class DatabaseAdapter
# Executes database query with optional connection pooling
#
# Connection pooling available in Ruby 2.7+ with fiber scheduler support.
# Falls back to synchronous execution in earlier Ruby versions.
#
# @param [String] query SQL query string
# @param [Hash] options Query execution options
# @option options [Boolean] :async Use async execution (Ruby 2.7+)
# @return [QueryResult] Query execution results
def execute_query(query, options = {})
if RUBY_VERSION >= '2.7.0' && options[:async]
execute_async(query, options)
else
execute_sync(query, options)
end
end
private
# @!visibility private
def execute_async(query, options)
# Fiber-based async implementation
end
# @!visibility private
def execute_sync(query, options)
# Traditional synchronous implementation
end
end
Testing Strategies
Documentation testing ensures accuracy and prevents documentation drift. Write tests that validate code examples execute correctly and produce expected outputs. Use tools like yard-doctest
or custom test harnesses to verify documentation examples.
# spec/documentation_spec.rb
require 'yard'
require 'yard-doctest'
RSpec.describe 'Documentation Examples' do
it 'validates all YARD examples' do
YARD.parse(['lib/**/*.rb'])
doctest = YardDoctest::DocTest.new
expect(doctest.run).to be_truthy,
"Documentation examples failed validation"
end
it 'ensures API methods have examples' do
missing_examples = []
YARD::Registry.all(:method).each do |method|
next if method.visibility != :public
next if method.has_tag?(:example)
missing_examples << method.path
end
expect(missing_examples).to be_empty,
"Methods missing examples: #{missing_examples.join(', ')}"
end
end
Test documentation coverage by analyzing parsed documentation trees. Measure coverage metrics including method documentation completeness, parameter documentation, and return value documentation. Establish coverage thresholds and monitor coverage trends over time.
class DocumentationCoverageAnalyzer
def initialize(source_paths)
@source_paths = source_paths
YARD.parse(source_paths)
end
# Analyzes documentation coverage across the codebase
#
# @return [Hash] Coverage metrics including percentages and details
def analyze_coverage
methods = YARD::Registry.all(:method)
public_methods = methods.select { |m| m.visibility == :public }
documented_methods = public_methods.select { |m| documented?(m) }
{
total_methods: public_methods.size,
documented_methods: documented_methods.size,
coverage_percentage: (documented_methods.size.to_f / public_methods.size * 100).round(2),
undocumented: public_methods - documented_methods
}
end
private
def documented?(method)
return false unless method.docstring.present?
return false unless method.tags(:param).size == method.parameters.size
return false unless method.has_tag?(:return)
true
end
end
Documentation integration testing validates generated documentation against expected structure and content. Test navigation functionality, search capabilities, and cross-reference link integrity. Automated testing prevents broken documentation deployment.
# spec/integration/documentation_integration_spec.rb
require 'nokogiri'
require 'net/http'
RSpec.describe 'Generated Documentation' do
before(:all) do
system('bundle exec yardoc')
end
let(:doc_index) { File.read('doc/index.html') }
let(:parsed_doc) { Nokogiri::HTML(doc_index) }
it 'generates complete navigation structure' do
nav_links = parsed_doc.css('#nav .item a')
expect(nav_links.size).to be > 0
nav_links.each do |link|
href = link['href']
expect(File.exist?("doc/#{href}")).to be true
end
end
it 'includes all public classes in documentation' do
class_names = YARD::Registry.all(:class).map(&:name).map(&:to_s)
class_names.each do |class_name|
expect(doc_index).to include(class_name)
end
end
it 'validates cross-reference links' do
cross_refs = parsed_doc.css('a.crossref')
cross_refs.each do |link|
href = link['href']
next unless href.start_with?('#')
target_id = href[1..]
expect(parsed_doc.css("##{target_id}")).not_to be_empty
end
end
end
Mock documentation scenarios for complex dependencies or external services. Create documentation examples that don't require actual service connections or expensive operations. Use mock objects and stub data to demonstrate API usage patterns.
class PaymentGatewayDocumentation
# Documents payment gateway integration patterns
#
# @example Processing a successful payment
# gateway = PaymentGateway.new(api_key: 'test_key')
#
# # Mock successful response for documentation
# allow(gateway).to receive(:charge).and_return(
# OpenStruct.new(
# success?: true,
# transaction_id: 'txn_123',
# amount: 2500
# )
# )
#
# result = gateway.charge(2500, payment_method)
# puts result.transaction_id #=> "txn_123"
#
# @example Handling payment failures
# allow(gateway).to receive(:charge).and_raise(
# PaymentGateway::DeclinedError.new('Insufficient funds')
# )
#
# begin
# gateway.charge(2500, payment_method)
# rescue PaymentGateway::DeclinedError => e
# puts "Payment declined: #{e.message}"
# end
def document_payment_scenarios
# Documentation examples only - no implementation needed
end
end
Production Patterns
Continuous integration documentation ensures documentation stays current with code changes. Configure CI pipelines to generate and validate documentation automatically. Deploy updated documentation to staging environments for review before production publication.
# .github/workflows/documentation.yml configuration snippet
#
# name: Documentation
# on: [push, pull_request]
# jobs:
# generate:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - uses: ruby/setup-ruby@v1
# with:
# bundler-cache: true
# - name: Generate documentation
# run: bundle exec yardoc
# - name: Validate documentation
# run: bundle exec rspec spec/documentation_spec.rb
class DocumentationDeployment
# Manages documentation deployment pipeline
#
# Integrates with CI/CD systems to automate documentation generation
# and deployment. Supports multiple environments and rollback capabilities.
#
# @param [Hash] config Deployment configuration
# @option config [String] :target_environment Deployment target
# @option config [String] :documentation_url Base URL for documentation
def initialize(config)
@config = config
@generator = DocumentationGenerator.new
end
# Deploys documentation to specified environment
#
# @param [String] version Git commit or tag to deploy
# @return [DeploymentResult] Deployment status and details
def deploy(version)
@generator.generate_for_version(version)
upload_documentation
update_search_index
DeploymentResult.new(status: :success, url: documentation_url)
rescue => e
handle_deployment_failure(e)
end
end
Documentation versioning maintains historical API documentation for multiple software versions. Implement version-specific documentation generation and hosting. Provide clear migration paths between API versions through comparative documentation.
class VersionedDocumentation
VERSION_MAPPING = {
'v1' => '1.0.0..1.9.9',
'v2' => '2.0.0..2.9.9',
'v3' => '3.0.0..'
}.freeze
# Generates documentation for specific API version
#
# @param [String] version_tag Git tag or version identifier
# @param [Hash] options Generation options
# @option options [Boolean] :include_deprecated Include deprecated methods
# @return [String] Path to generated documentation
def generate_version_docs(version_tag, options = {})
checkout_version(version_tag)
configure_yard_for_version(version_tag, options)
output_path = "doc/versions/#{version_tag}"
system("bundle exec yardoc --output-dir #{output_path}")
generate_version_index(version_tag, output_path)
output_path
end
private
def configure_yard_for_version(version, options)
config = base_yard_config
config[:title] = "API Documentation v#{version}"
config[:include_deprecated] = options.fetch(:include_deprecated, true)
File.write('.yardopts', config.map { |k, v| "--#{k} #{v}" }.join("\n"))
end
end
Documentation monitoring tracks usage patterns and identifies improvement opportunities. Monitor documentation site analytics, search queries, and user feedback. Use metrics to prioritize documentation updates and identify knowledge gaps.
class DocumentationMetrics
# Analyzes documentation usage and effectiveness
#
# Tracks page views, search patterns, and user engagement metrics.
# Identifies frequently accessed content and areas needing improvement.
def initialize(analytics_client)
@analytics = analytics_client
end
# Generates documentation usage report
#
# @param [Date] start_date Report period start
# @param [Date] end_date Report period end
# @return [Hash] Usage metrics and recommendations
def usage_report(start_date, end_date)
page_views = @analytics.page_views(start_date, end_date)
search_queries = @analytics.search_queries(start_date, end_date)
{
total_views: page_views.sum(&:views),
top_pages: page_views.sort_by(&:views).reverse.first(10),
common_searches: search_queries.group_by(&:query).transform_values(&:size),
recommendations: generate_recommendations(page_views, search_queries)
}
end
private
def generate_recommendations(views, searches)
low_traffic = views.select { |page| page.views < average_views(views) * 0.1 }
failed_searches = searches.select { |search| search.results_count == 0 }
{
pages_needing_promotion: low_traffic.map(&:path),
missing_content: failed_searches.map(&:query).uniq
}
end
end
Performance optimization for large documentation sites involves caching, compression, and efficient asset delivery. Implement static site generation with incremental builds. Use content delivery networks for global documentation distribution.
class DocumentationOptimizer
# Optimizes documentation site performance
#
# Implements caching strategies, asset optimization, and incremental
# generation for large documentation sites. Reduces build times and
# improves user experience through performance optimizations.
def initialize(site_config)
@config = site_config
@cache = DocumentationCache.new
end
# Performs incremental documentation build
#
# Only rebuilds documentation for changed files, significantly reducing
# build times for large projects with extensive documentation.
#
# @param [Array<String>] changed_files List of modified source files
# @return [BuildResult] Build status and performance metrics
def incremental_build(changed_files)
start_time = Time.now
affected_docs = determine_affected_documentation(changed_files)
rebuild_documentation(affected_docs)
optimize_assets
BuildResult.new(
duration: Time.now - start_time,
files_processed: affected_docs.size,
cache_hits: @cache.hit_count
)
end
private
def optimize_assets
compress_css_files
optimize_javascript
generate_responsive_images
end
end
Common Pitfalls
Outdated documentation represents the most pervasive documentation problem. Documentation becomes stale when code changes without corresponding documentation updates. Establish processes that tie documentation updates to code changes through automated validation and review requirements.
# Common mistake: Documentation doesn't match implementation
class UserService
# Validates user email format
# @param [String] email User email address
# @return [Boolean] True if email is valid
def validate_email(email)
# Implementation changed but documentation didn't update
result = EmailValidator.new(email).validate
return result.success? && result.deliverable? # Returns ValidatorResult, not Boolean
end
end
# Better approach: Keep documentation synchronized
class UserService
# Validates user email format and deliverability
# @param [String] email User email address
# @return [EmailValidationResult] Validation result with detailed status
# @example Successful validation
# result = validate_email("user@example.com")
# result.valid? #=> true
# result.deliverable? #=> true
def validate_email(email)
EmailValidator.new(email).validate
end
end
Over-documentation clutters codebases with obvious or redundant information. Avoid documenting implementation details that change frequently or concepts that experienced developers understand implicitly. Focus documentation on public APIs, complex algorithms, and business logic.
# Over-documented: Obvious information adds no value
class Calculator
# Adds two numbers together
# @param [Integer] a The first number to add
# @param [Integer] b The second number to add
# @return [Integer] The sum of a and b
def add(a, b)
a + b # Returns the result of adding a and b
end
# Sets the value of x to the provided value
# @param [Integer] value The value to set x to
def x=(value)
@x = value # Assigns value to instance variable @x
end
end
# Better: Document non-obvious behavior and edge cases
class Calculator
# Performs arbitrary precision decimal addition
# Handles edge cases including infinity and NaN values
# @param [Numeric] a First operand
# @param [Numeric] b Second operand
# @return [BigDecimal, Float] Sum with appropriate precision
# @raise [ArgumentError] When operands cannot be coerced to numeric types
def add(a, b)
# Complex implementation with precision handling
end
end
Inconsistent documentation styles create confusion and maintenance overhead. Establish style guides covering parameter description format, example structure, and cross-reference conventions. Use linting tools to enforce consistency automatically.
# Inconsistent documentation styles across methods
class ApiClient
# Gets user data
# userId: the user ID
# Returns: user object
def get_user(userId)
end
# @param [String] user_id The unique identifier for the user
# @return [Hash] User profile information including name, email, preferences
# @raise [NotFoundError] When user_id doesn't exist
# @example
# client.fetch_user("123")
def fetch_user(user_id)
end
end
# Consistent documentation following established patterns
class ApiClient
# Retrieves user profile information
# @param [String] user_id Unique user identifier
# @return [Hash] User profile data
# @raise [NotFoundError] When user doesn't exist
def get_user(user_id)
end
# Retrieves detailed user information including preferences
# @param [String] user_id Unique user identifier
# @return [Hash] Complete user profile with preferences
# @raise [NotFoundError] When user doesn't exist
def fetch_user(user_id)
end
end
Missing error documentation leaves developers unprepared for exception handling. Document all exceptions that methods can raise, including inherited exceptions and exceptions from dependencies. Provide guidance on appropriate error handling strategies.
# Missing error documentation creates surprises
class DatabaseQuery
# Executes SQL query against database
# @param [String] sql Query to execute
# @return [Array<Hash>] Query results
def execute(sql)
connection.query(sql) # Can raise multiple exception types
end
end
# Complete error documentation prevents surprises
class DatabaseQuery
# Executes SQL query against database connection
# @param [String] sql Valid SQL query string
# @return [Array<Hash>] Query result rows as hash objects
# @raise [ConnectionError] When database connection is unavailable
# @raise [QueryError] When SQL syntax is invalid
# @raise [TimeoutError] When query exceeds configured timeout
# @raise [PermissionError] When user lacks required database privileges
# @example Handling query errors
# begin
# results = query.execute("SELECT * FROM users")
# rescue QueryError => e
# logger.error "SQL error: #{e.message}"
# return []
# end
def execute(sql)
connection.query(sql)
end
end
Documentation that assumes too much knowledge excludes potential users. Provide sufficient context for developers who may be unfamiliar with domain concepts or implementation patterns. Link to external resources for background information when appropriate.
# Assumes too much domain knowledge
class MLModelTrainer
# Trains model using backpropagation with SGD optimizer
# @param [Array] features Feature vectors
# @param [Array] labels Target classifications
# @return [TrainedModel] Optimized model weights
def train(features, labels)
# Assumes reader knows backpropagation, SGD, feature vectors
end
end
# Provides sufficient context for broader audience
class MLModelTrainer
# Trains machine learning classification model using supervised learning
#
# Uses stochastic gradient descent (SGD) optimization with backpropagation
# to adjust model weights based on training data. The training process
# iteratively improves model accuracy by minimizing prediction errors.
#
# @param [Array<Array<Float>>] features Training input data where each
# sub-array represents feature values for one training example
# @param [Array<Integer>] labels Expected output classifications corresponding
# to each feature vector (0-based class indices)
# @return [TrainedModel] Model with optimized weights ready for predictions
# @see https://en.wikipedia.org/wiki/Backpropagation for algorithm details
def train(features, labels)
# Implementation with clear variable names and comments
end
end
Reference
RDoc Markup Reference
Syntax | Output | Usage |
---|---|---|
*bold* |
bold | Emphasis in descriptions |
_italic_ |
italic | Secondary emphasis |
+code+ |
code |
Inline code references |
[link text](URL) |
Hyperlink | External references |
{ClassName} |
Cross-reference | Internal code links |
* item |
• item | Bulleted lists |
1. item |
1. item | Numbered lists |
YARD Tag Reference
Tag | Syntax | Purpose |
---|---|---|
@param |
@param [Type] name Description |
Method parameters |
@return |
@return [Type] Description |
Return values |
@raise |
@raise [ExceptionType] Description |
Possible exceptions |
@example |
@example Title\n code |
Usage examples |
@see |
@see ClassName#method |
Related references |
@since |
@since 1.2.0 |
Version availability |
@deprecated |
@deprecated Use {#new_method} instead |
Deprecation notices |
@yield |
@yield [Type] Description |
Block parameters |
@yieldparam |
@yieldparam [Type] name Description |
Block parameter details |
@yieldreturn |
@yieldreturn [Type] Description |
Block return values |
Type Specification Conventions
Type Pattern | Description | Example |
---|---|---|
[String] |
Single type | Basic string parameter |
[String, nil] |
Union types | Optional string parameter |
[Array<String>] |
Generic collections | Array of strings |
[Hash<Symbol,Object>] |
Hash with key/value types | Options hash |
[#to_s] |
Duck typing | Objects responding to method |
[Boolean] |
True or false values | Flag parameters |
[void] |
No return value | Procedures |
Documentation Generation Commands
Command | Purpose | Key Options |
---|---|---|
rdoc |
Generate RDoc documentation | --main , --title , --output-dir |
yardoc |
Generate YARD documentation | --output-dir , --markup , --template |
yard server |
Development documentation server | --port , --reload |
yard stats |
Documentation coverage statistics | --list-undoc |
Configuration Files
File | Purpose | Format |
---|---|---|
.yardopts |
YARD command-line options | One option per line |
.document |
RDoc file inclusion patterns | Glob patterns |
yard_config.yml |
YARD project configuration | YAML format |
Visibility Modifiers
Modifier | YARD Syntax | RDoc Syntax | Effect |
---|---|---|---|
Public | Default | Default | Included in documentation |
Protected | @!visibility protected |
# :protected: |
Conditionally included |
Private | @!visibility private |
# :private: |
Excluded by default |
Hidden | @!visibility private |
# :nodoc: |
Never included |
Error Documentation Patterns
Exception Category | Documentation Approach | Example |
---|---|---|
Input validation | Document invalid input scenarios | @raise [ArgumentError] When email format is invalid |
Resource unavailability | Document dependency failures | @raise [ConnectionError] When database is unreachable |
Permission issues | Document authorization requirements | @raise [UnauthorizedError] When user lacks admin privileges |
State conflicts | Document timing and state issues | @raise [StateError] When object is already finalized |
External service errors | Document third-party failures | @raise [ServiceError] When payment gateway is unavailable |