CrackedRuby CrackedRuby

Principle of Least Astonishment

Overview

The Principle of Least Astonishment (POLA), also called the Principle of Least Surprise, states that when two elements of a system conflict or are ambiguous, the behavior should match what users most naturally expect. The principle applies to user interface design, API design, programming language features, and system architecture.

The principle originated in user interface design during the 1970s but has since expanded to encompass all aspects of software design. The core insight is that unexpected behavior increases cognitive load, creates bugs, and reduces maintainability. When developers encounter surprising behavior, they must stop, investigate, and form new mental models—interrupting their workflow and increasing the likelihood of errors.

POLA operates at multiple levels. At the syntactic level, similar-looking code should behave similarly. At the semantic level, operations should produce results that align with their names and conventional meanings. At the architectural level, system components should interact in predictable ways that follow established patterns.

Ruby exemplifies POLA in its design philosophy. Matz (Yukihiro Matsumoto) explicitly designed Ruby to prioritize programmer happiness, which means minimizing surprise. Ruby's method names read like English, operators behave consistently across types, and the language provides multiple ways to express the same concept, each optimized for different contexts.

# Ruby's intuitive method naming
"hello".upcase          # => "HELLO"
"hello".capitalize      # => "Hello"
[1, 2, 3].reverse       # => [3, 2, 1]

# Consistent behavior across types
"hello".empty?          # => false
[].empty?               # => true
{}.empty?               # => true

The principle becomes critical as systems grow in complexity. Small violations compound—a single surprising method name might cause momentary confusion, but hundreds of inconsistencies across an API make the system nearly unusable without constant documentation reference.

Key Principles

The Principle of Least Astonishment rests on consistency, predictability, and adherence to established conventions. These components interact to create systems that feel natural to use.

Consistency requires that similar operations behave similarly. When a user learns how one part of a system works, that knowledge should transfer to similar parts. This applies to naming conventions, parameter ordering, return values, and error handling. Consistency reduces the amount of information a user must hold in working memory because patterns apply broadly rather than requiring case-by-case memorization.

Predictability means that users can correctly anticipate behavior based on their existing knowledge and mental models. A predictable system aligns with domain conventions, programming language idioms, and mathematical or logical expectations. Predictability differs from consistency—a system can be internally consistent but still unpredictable if it violates broader conventions.

Principle of Least Surprise in Naming states that identifiers should accurately describe their behavior without misleading implications. A method named delete should remove data, not hide it. A property named count should return a number, not a boolean. Misleading names create cognitive dissonance where the code's apparent meaning conflicts with its actual behavior.

# Good: names match behavior
class ShoppingCart
  def add_item(item)
    @items << item
  end
  
  def remove_item(item)
    @items.delete(item)
  end
  
  def total_price
    @items.sum(&:price)
  end
end

# Violates POLA: misleading names
class ShoppingCart
  def add_item(item)
    @items << item
    send_analytics_event  # Unexpected side effect
  end
  
  def total_price
    @items.count  # Returns count, not price
  end
end

Principle of Uniform Access suggests that accessing data and computing values should use the same interface. Ruby implements this through automatic getter/setter methods and the convention that methods without side effects don't require parentheses. This makes refactoring from stored values to computed values transparent to calling code.

Principle of Symmetry requires that inverse operations exist and behave as expected. If a system provides open, it should provide close. If it provides lock, it should provide unlock. The inverse operation should cleanly reverse the original operation's effects. Asymmetric operations create resource leaks and confused state.

# Symmetric operations
file = File.open("data.txt", "r")
content = file.read
file.close

# Ruby provides automatic cleanup
File.open("data.txt", "r") do |file|
  content = file.read
end  # Automatically closes

# Symmetric state management
mutex = Mutex.new
mutex.lock
begin
  # Critical section
ensure
  mutex.unlock
end

Principle of Least Privilege intersects with POLA by suggesting that operations should require the minimum necessary permissions or information. When users must provide excessive or irrelevant data, they question whether they understand the operation correctly. The principle promotes focused, single-purpose interfaces.

The relationship between these principles creates emergent properties. A system exhibiting all these characteristics feels "natural"—users rarely consult documentation because behavior matches intuition. Violations in one area often indicate violations in others, suggesting deeper design issues.

Ruby Implementation

Ruby's design philosophy directly incorporates POLA. The language provides multiple syntactic forms for common operations, allowing developers to choose the form that best expresses intent in each context. This reduces surprise because code reads more naturally.

Method Naming Conventions in Ruby follow consistent patterns that signal behavior. Methods ending with ? return booleans and perform no mutations. Methods ending with ! perform dangerous operations, typically in-place mutations. Methods without suffixes generally return new objects without modifying the receiver.

# Query methods
"".empty?               # => true
[1, 2].include?(1)      # => true
user.admin?             # => true/false

# Mutating methods (dangerous)
str = "hello"
str.upcase!             # Modifies str in place
str                     # => "HELLO"

array = [3, 1, 2]
array.sort!             # Modifies array in place
array                   # => [1, 2, 3]

# Non-mutating methods (safe)
str = "hello"
upper = str.upcase      # Returns new string
str                     # => "hello" (unchanged)
upper                   # => "HELLO"

Operator Consistency ensures that operators behave predictably across types. The + operator performs addition for numbers, concatenation for strings and arrays, and union for sets. While the specific operation varies, the semantics remain consistent: combining two things to produce a result containing both.

# Consistent operator behavior
1 + 2                   # => 3
"hello" + " world"      # => "hello world"
[1, 2] + [3, 4]         # => [1, 2, 3, 4]

# Subtraction removes elements
5 - 3                   # => 2
[1, 2, 3] - [2]         # => [1, 3]

Uniform Access Principle allows Ruby to treat attribute access and method calls identically. This enables transparent refactoring between stored and computed values. The attr_reader, attr_writer, and attr_accessor methods create getter and setter methods that look identical to method definitions.

class Person
  attr_reader :first_name, :last_name
  
  # Initially a simple reader
  def full_name
    "#{first_name} #{last_name}"
  end
  
  # Can change to cached value without changing interface
  def full_name
    @full_name ||= "#{first_name} #{last_name}"
  end
  
  # Or add validation without changing interface
  def age=(value)
    raise ArgumentError, "Age must be positive" if value < 0
    @age = value
  end
end

Block Conventions provide consistent patterns for resource management and iteration. Methods that acquire resources accept blocks and guarantee cleanup, even if exceptions occur. This pattern reduces surprise because developers learn it once and apply it everywhere.

# Automatic resource management
File.open("data.txt") do |file|
  file.read
end  # File closed automatically

Database.transaction do |txn|
  txn.execute("INSERT ...")
  txn.execute("UPDATE ...")
end  # Commits or rolls back automatically

# Consistent iteration
[1, 2, 3].each { |n| puts n }
{ a: 1, b: 2 }.each { |key, value| puts "#{key}: #{value}" }
File.foreach("data.txt") { |line| puts line }

Default Arguments in Ruby follow predictable patterns. Optional parameters appear after required parameters. Default values use sensible defaults that work for common cases. When methods have many optional parameters, Ruby provides hash options with default values.

# Predictable parameter ordering
def create_user(email, name, role: "user", active: true)
  User.new(email: email, name: name, role: role, active: active)
end

create_user("user@example.com", "John Doe")
create_user("admin@example.com", "Jane Smith", role: "admin")

# Clear option hashing
def configure_server(options = {})
  defaults = {
    host: "localhost",
    port: 8080,
    timeout: 30,
    ssl: false
  }
  settings = defaults.merge(options)
  # Configure server with settings
end

Return Value Conventions maintain consistency across the standard library. Methods that might fail return nil rather than raising exceptions, unless failure represents a programming error. Enumerable methods return new collections rather than modifying the original. Boolean methods never return nil—they always return true or false.

# Consistent return values
hash = { a: 1, b: 2 }
hash[:a]                # => 1
hash[:c]                # => nil (key not found)

array = [1, 2, 3]
array.map { |n| n * 2 } # => [2, 4, 6]
array                   # => [1, 2, 3] (unchanged)

# Boolean methods never return nil
"".empty?               # => true (not nil)
[].any?                 # => false (not nil)

Method Chaining works consistently because most methods return appropriate objects. String methods return strings, array methods return arrays, and query methods return booleans. This consistency enables fluent interfaces without surprise.

# Predictable chaining
result = "  Hello World  "
  .strip
  .downcase
  .gsub(" ", "_")
  .split("_")
  .map(&:capitalize)
  .join("-")
# => "Hello-World"

# Works with arrays
numbers = (1..10)
  .to_a
  .select(&:even?)
  .map { |n| n * n }
  .reduce(:+)
# => 220

Practical Examples

Real-world applications of POLA demonstrate how the principle affects daily development decisions. These examples show both adherence to and violations of the principle.

API Method Design requires careful attention to POLA. Consider a user authentication API. Users expect methods like login, logout, authenticate, and authorize to behave in specific ways based on their names and domain conventions.

class AuthenticationService
  # Good: method names clearly indicate behavior
  def authenticate(email, password)
    user = User.find_by(email: email)
    return nil unless user&.valid_password?(password)
    user
  end
  
  def create_session(user)
    Session.create(user: user, token: SecureRandom.hex(32))
  end
  
  def invalidate_session(token)
    Session.find_by(token: token)&.destroy
  end
  
  # Violates POLA: side effects not indicated by name
  def authenticate_bad(email, password)
    user = User.find_by(email: email)
    return nil unless user&.valid_password?(password)
    
    # Unexpected side effects
    user.update(last_login: Time.now)
    Session.create(user: user)  # Creates session automatically
    AuditLog.create(action: "login", user: user)
    NotificationMailer.login_notification(user).deliver_later
    
    user  # Returns user, not session
  end
end

Configuration Objects should use clear, predictable interfaces. Configuration follows conventions about defaults, validation, and mutability. Surprising configuration behavior causes production incidents.

# Good: predictable configuration
class DatabaseConfig
  attr_reader :host, :port, :database, :pool_size
  
  def initialize(options = {})
    @host = options.fetch(:host, "localhost")
    @port = options.fetch(:port, 5432)
    @database = options.fetch(:database) { raise ArgumentError, "database required" }
    @pool_size = options.fetch(:pool_size, 5)
    
    validate!
    freeze  # Immutable after creation
  end
  
  private
  
  def validate!
    raise ArgumentError, "port must be positive" unless port > 0
    raise ArgumentError, "pool_size must be positive" unless pool_size > 0
  end
end

# Violates POLA: surprising defaults and mutation
class DatabaseConfigBad
  attr_accessor :host, :port, :database
  
  def initialize(options = {})
    @host = options[:host]  # Nil if not provided
    @port = options[:port]  # Nil if not provided
    @database = options[:database]
  end
  
  def connect
    @host ||= "production.db.example.com"  # Surprising default
    @port ||= 5432
    # Modifies state during "read" operation
  end
end

Collection Interfaces benefit from consistency. Custom collections should behave like built-in collections. Users should be able to apply their knowledge of Array and Hash to custom collection types.

# Good: behaves like standard Ruby collection
class PriorityQueue
  include Enumerable
  
  def initialize
    @items = []
  end
  
  def push(item, priority)
    @items << [item, priority]
    @items.sort_by! { |_, p| -p }
    item
  end
  
  def pop
    @items.shift&.first
  end
  
  def each(&block)
    @items.each { |item, _| block.call(item) }
  end
  
  def empty?
    @items.empty?
  end
  
  def size
    @items.size
  end
end

# Usage feels natural
queue = PriorityQueue.new
queue.push("low", 1)
queue.push("high", 10)
queue.push("medium", 5)

queue.map { |item| item.upcase }  # Works because Enumerable
queue.empty?                       # Returns boolean
queue.size                         # Returns integer

Error Handling should match user expectations about when and how failures occur. Methods should fail fast for programming errors but return nil or sensible defaults for runtime conditions.

# Good: appropriate error handling
class PaymentProcessor
  def charge(amount, card)
    raise ArgumentError, "amount must be positive" if amount <= 0
    raise ArgumentError, "card required" if card.nil?
    
    # Network/business errors return result objects
    response = gateway.charge(amount, card)
    return PaymentResult.failure(response.error) unless response.success?
    
    PaymentResult.success(response.transaction_id)
  end
  
  def find_transaction(id)
    Transaction.find_by(id: id)  # Returns nil if not found
  end
end

# Violates POLA: inconsistent error handling
class PaymentProcessorBad
  def charge(amount, card)
    # Doesn't validate, fails unpredictably later
    response = gateway.charge(amount, card)
    response.success?  # Returns boolean, loses error information
  end
  
  def find_transaction(id)
    # Raises exception instead of returning nil
    Transaction.find(id)  # Raises ActiveRecord::RecordNotFound
  end
end

State Management should make state transitions explicit and predictable. Objects should have clear lifecycles, and attempting invalid operations should fail clearly.

# Good: explicit state management
class Document
  attr_reader :status
  
  def initialize
    @status = :draft
  end
  
  def publish
    raise InvalidStateError, "Cannot publish #{status} document" unless status == :draft
    @status = :published
    true
  end
  
  def archive
    raise InvalidStateError, "Cannot archive #{status} document" unless status == :published
    @status = :archived
    true
  end
  
  def draft?
    status == :draft
  end
  
  def published?
    status == :published
  end
  
  def archived?
    status == :archived
  end
end

Design Considerations

Applying POLA requires balancing multiple competing concerns. Decisions involve trade-offs between brevity and explicitness, flexibility and constraints, performance and predictability.

Consistency vs Optimization creates tension when optimal implementations violate expectations. A method might perform better using an unexpected algorithm, but surprising behavior increases cognitive load. The cost of surprise often exceeds performance gains, especially in maintainability-critical systems.

Consider a caching layer. Users expect cache reads to return the same value as the underlying data source. An optimization that returns stale data to avoid database queries violates this expectation, even if documented. The performance gain must substantially exceed the confusion cost.

Naming Conventions require careful consideration. Names should be accurate, not merely suggestive. A method that does more than its name implies violates POLA. When a simple name doesn't capture full behavior, choose a longer, more accurate name.

# Good: accurate names
def create_user_and_send_welcome_email(params)
  user = User.create!(params)
  UserMailer.welcome(user).deliver_later
  user
end

# Bad: hidden side effects
def create_user(params)
  user = User.create!(params)
  UserMailer.welcome(user).deliver_later  # Unexpected
  user
end

Domain Conventions should guide design decisions. Financial systems use different conventions than scientific computing systems. A method named round in a financial context should round to currency precision, not arbitrary precision. Understanding the domain helps predict user expectations.

Backward Compatibility sometimes conflicts with POLA. Legacy behavior might violate the principle, but changing it breaks existing code. In these cases, provide new, well-named methods while deprecating old ones. Clear migration paths reduce surprise during upgrades.

Default Behavior should serve common cases without surprising edge cases. Defaults should be safe, not dangerous. A method defaulting to deleting data surprises users more than one defaulting to read-only operations. When in doubt, require explicit parameters for dangerous operations.

Documentation Necessity signals design problems. If methods require extensive documentation to explain behavior, the design likely violates POLA. Good designs are self-documenting—names and interfaces clearly communicate behavior. Documentation should enhance understanding, not compensate for confusing design.

API Evolution requires maintaining predictability across versions. Adding features should extend behavior naturally, not change existing behavior. Deprecation cycles give users time to adjust expectations. Sudden breaking changes create maximum surprise.

Performance Transparency matters when optimizations change observable behavior. Caching shouldn't change return values. Lazy evaluation shouldn't change side effect timing. When performance optimizations create surprising behavior, document them clearly or reconsider the optimization.

Cultural Context affects expectations. Ruby developers expect certain idioms, Python developers expect others. When designing for a specific community, follow that community's conventions. Cross-language libraries should document convention differences clearly.

Common Patterns

Established patterns demonstrate POLA in practice. These patterns emerge from collective experience about what works and what surprises users.

Builder Pattern provides predictable object construction. Builders use method chaining with clear, incremental configuration. Each method returns the builder, making the pattern obvious.

# Predictable builder pattern
class QueryBuilder
  def initialize
    @conditions = []
    @order = nil
    @limit = nil
  end
  
  def where(condition)
    @conditions << condition
    self
  end
  
  def order(column)
    @order = column
    self
  end
  
  def limit(count)
    @limit = count
    self
  end
  
  def build
    query = "SELECT * FROM users"
    query += " WHERE #{@conditions.join(' AND ')}" if @conditions.any?
    query += " ORDER BY #{@order}" if @order
    query += " LIMIT #{@limit}" if @limit
    query
  end
end

# Usage is clear and predictable
query = QueryBuilder.new
  .where("age > 18")
  .where("active = true")
  .order("created_at DESC")
  .limit(10)
  .build

Null Object Pattern eliminates nil checks by providing objects that respond to the same interface but perform no operations. This maintains consistency—every object responds to expected methods.

# Predictable null object
class RealUser
  attr_reader :name, :email
  
  def initialize(name, email)
    @name = name
    @email = email
  end
  
  def admin?
    @role == "admin"
  end
  
  def send_notification(message)
    NotificationMailer.notify(self, message).deliver_later
  end
end

class NullUser
  def name
    "Guest"
  end
  
  def email
    nil
  end
  
  def admin?
    false
  end
  
  def send_notification(message)
    # Does nothing, but doesn't raise an error
  end
end

# Usage doesn't require nil checks
def process_user(user)
  user.send_notification("Welcome!")
  puts "Processing #{user.name}"
end

Command Pattern encapsulates operations as objects with predictable interfaces. Each command responds to execute, undo, and redo methods. The consistency enables generic command processing.

class Command
  def execute
    raise NotImplementedError
  end
  
  def undo
    raise NotImplementedError
  end
end

class CreateUserCommand < Command
  def initialize(params)
    @params = params
    @user = nil
  end
  
  def execute
    @user = User.create!(@params)
  end
  
  def undo
    @user&.destroy
  end
end

class CommandProcessor
  def initialize
    @history = []
  end
  
  def execute(command)
    command.execute
    @history << command
  end
  
  def undo
    @history.pop&.undo
  end
end

Strategy Pattern allows algorithm selection without changing client code. Strategies implement a common interface, making them interchangeable. Users understand they can swap strategies without other code changes.

class SortStrategy
  def sort(array)
    raise NotImplementedError
  end
end

class QuickSort < SortStrategy
  def sort(array)
    return array if array.length <= 1
    pivot = array.delete_at(rand(array.length))
    left, right = array.partition { |x| x < pivot }
    QuickSort.new.sort(left) + [pivot] + QuickSort.new.sort(right)
  end
end

class MergeSort < SortStrategy
  def sort(array)
    return array if array.length <= 1
    mid = array.length / 2
    left = MergeSort.new.sort(array[0...mid])
    right = MergeSort.new.sort(array[mid..-1])
    merge(left, right)
  end
  
  private
  
  def merge(left, right)
    result = []
    until left.empty? || right.empty?
      result << (left.first <= right.first ? left.shift : right.shift)
    end
    result + left + right
  end
end

# Usage is consistent regardless of strategy
sorter = QuickSort.new
sorted = sorter.sort([3, 1, 4, 1, 5])

Factory Pattern centralizes object creation with predictable interfaces. Factories hide construction complexity while maintaining consistent creation semantics.

class UserFactory
  def self.create(type, params)
    case type
    when :admin
      AdminUser.new(params)
    when :customer
      CustomerUser.new(params)
    when :guest
      GuestUser.new(params)
    else
      raise ArgumentError, "Unknown user type: #{type}"
    end
  end
end

# Predictable creation
admin = UserFactory.create(:admin, name: "Admin", permissions: :all)
customer = UserFactory.create(:customer, name: "Customer", plan: :premium)

Common Pitfalls

Developers frequently violate POLA through specific patterns. Recognizing these pitfalls helps avoid them.

Hidden Side Effects represent the most common violation. Methods that appear to query state but actually modify it create confusion. Methods named like getters shouldn't have side effects.

# Bad: getter has side effects
class Account
  attr_reader :balance
  
  def available_balance
    @balance -= calculate_fees  # Modifies state during query
  end
end

# Good: explicit mutation
class Account
  attr_reader :balance
  
  def available_balance
    balance - calculate_fees
  end
  
  def deduct_fees!
    @balance -= calculate_fees
  end
end

Inconsistent Return Values confuse users. Methods should consistently return the same type. Returning different types based on input or state makes code unpredictable.

# Bad: inconsistent returns
def find_user(id)
  user = User.find_by(id: id)
  return false unless user  # Returns false
  return nil unless user.active?  # Returns nil
  user  # Returns User
end

# Good: consistent returns
def find_user(id)
  user = User.find_by(id: id)
  return nil unless user&.active?
  user
end

Boolean Trap occurs when boolean parameters change method behavior dramatically. The meaning of true or false is unclear at call sites.

# Bad: unclear boolean parameter
def process_order(order, send_email)
  order.process!
  OrderMailer.confirmation(order).deliver_later if send_email
end

process_order(order, true)  # What does true mean?

# Good: explicit options
def process_order(order, notifications: :enabled)
  order.process!
  OrderMailer.confirmation(order).deliver_later if notifications == :enabled
end

process_order(order, notifications: :enabled)

Temporal Coupling creates hidden dependencies on method call order. When methods must be called in specific sequence without clear indication, users make mistakes.

# Bad: hidden temporal coupling
class ReportGenerator
  def prepare_data
    @data = Database.query("SELECT ...")
  end
  
  def generate_report
    # Fails if prepare_data not called first
    Report.new(@data).generate
  end
end

# Good: explicit dependencies
class ReportGenerator
  def generate_report
    data = prepare_data
    Report.new(data).generate
  end
  
  private
  
  def prepare_data
    Database.query("SELECT ...")
  end
end

Overly Clever Abstractions prioritize brevity over clarity. Code that saves a few characters but obscures meaning violates POLA.

# Bad: too clever
def u(t)
  User.where(t => true)
end

# Good: clear and explicit
def find_users_by_status(status)
  User.where(status: status)
end

Mutating Parameters surprises users when methods modify their arguments. Ruby passes object references, so mutations affect the caller's objects.

# Bad: mutates parameter
def normalize_names(users)
  users.map! { |u| u.name.strip }
end

# Good: returns new array
def normalize_names(users)
  users.map { |u| u.name.strip }
end

Inconsistent Nil Handling creates unpredictability. Some methods raise exceptions for nil, others return nil, others return default values. Pick one strategy per module.

# Bad: inconsistent nil handling
def calculate_tax(amount)
  return 0 if amount.nil?  # Returns 0
end

def calculate_shipping(weight)
  raise ArgumentError if weight.nil?  # Raises exception
end

def calculate_discount(total)
  nil  # Returns nil
end

# Good: consistent strategy
def calculate_tax(amount)
  raise ArgumentError, "amount required" if amount.nil?
  amount * 0.08
end

def calculate_shipping(weight)
  raise ArgumentError, "weight required" if weight.nil?
  weight * 0.5
end

def calculate_discount(total)
  raise ArgumentError, "total required" if total.nil?
  total * 0.1
end

Reference

Core Principles

Principle Description Application
Consistency Similar operations behave similarly Use consistent naming, parameter ordering, return types
Predictability Behavior matches user expectations Follow domain conventions and language idioms
Clarity Names accurately describe behavior Avoid misleading or ambiguous identifiers
Symmetry Inverse operations exist and work correctly Provide matching pairs like open/close, lock/unlock
Least Privilege Request minimum necessary information Focused interfaces with clear requirements

Ruby Naming Conventions

Convention Meaning Example
method? Returns boolean, no mutation empty?, valid?, include?
method! Dangerous operation or mutation sort!, upcase!, delete!
method Returns new value, no mutation sort, upcase, select
attr_reader Creates getter method attr_reader :name
attr_writer Creates setter method attr_writer :name
attr_accessor Creates both getter and setter attr_accessor :name

Violation Patterns

Anti-Pattern Problem Solution
Hidden side effects Getters modify state Make mutations explicit with ! suffix
Inconsistent returns Different types returned Return consistent types or nil
Boolean trap Unclear true/false meaning Use symbol or string options
Temporal coupling Hidden call order dependency Make dependencies explicit
Mutating parameters Unexpected argument changes Return new objects, don't mutate
Clever abstractions Obscure code for brevity Prioritize clarity over conciseness

Design Guidelines

Guideline Implementation
Method names Accurately describe full behavior including side effects
Return values Consistent types, nil for not found, exceptions for errors
Parameters Required first, optional last, use keyword arguments for options
Defaults Safe values, dangerous operations require explicit parameters
Error handling Fail fast for programming errors, return nil for missing data
State changes Explicit through method names and return values
Collection interfaces Include Enumerable, implement standard methods
Resource management Use blocks with automatic cleanup

Common Method Patterns

Pattern Structure Usage
Query def value? Returns boolean, no side effects
Mutation def change! Modifies receiver in place
Transformation def transform Returns new object
Builder def option(value); self; end Method chaining configuration
Factory def self.create(params) Object construction
Command def execute; def undo Encapsulated operations