(Gavin)
GitHub/bodacious
Slack/gavin
Twitter/morriceGavin
class Order
attr_accessor :order_number
attr_accessor :items
end
@order = Order.new
@order.order_number = nil
@order.items = { foo: 'bar' }
Code should express intent, but not implementation
class Order
# attr_accessor :order_number
attr_accessor :items
def generate_order_number
@order_number = SecureRandom.hex(4)
end
def order_number
@order_number
end
end
class Order
# attr_accessor :order_number
# attr_accessor :items
def items
@items
end
def add_item(item)
@items << item
end
def generate_order_number
@order_number = SecureRandom.hex(4)
end
def order_number
@order_number
end
end
class Order
def initialize(items: [])
@items = items.to_a
@order_number = generate_unique_number
end
def items
@items
end
def order_number
@order_number
end
private
def generate_unique_number
@order_number = SecureRandom.hex(4)
end
end
@order = Order.new(items: Item.new(sku: "shoes-123"))
@order.items << Item.new(sku: "aviators-456")
@order.order_number.clear
@order.order_number # => ""
Internal state should be completely shielded from the external environment
class Order
def initialize(items: [])
@items = items.to_a
@order_number = generate_unique_number
end
def items
@items.dup
end
def order_number
@order_number.freeze
end
private
def generate_unique_number
@order_number = SecureRandom.hex(4)
end
end
class Order
def initialize(items: [])
@items = items.to_a
@order_number = generate_unique_number
@status = :pending_confirmation
self.total_price = 0
end
def total_price
@total_price
end
def total_price=(value)
@total_price = Money.new(value.to_i)
end
def status
@status
end
# ... same
end
class OrdersController < ApplicationController
def confirm
# ...
if @order.status == :pending_confirmation
@order.status = :confirmed
@order.items.each { |item| item.hold! }
@order.total_price = @order.items.sum(&:price)
end
# ...
end
end
The consumer knows too much about the abstraction "confirm"
class OrderController < ApplicationController
def confirm
# ...
if @order.status == :pending_confirmation
# @order.status = :confirmed
@order.items.each { |item| item.hold! }
@order.total_price = @order.items.sum(&:price)
end
# ...
end
end
The consumer knows too much about the abstraction "confirm"
class Order
def initialize(items: [])
@items = items.to_a
@order_number = generate_unique_number
@status = :pending_confirmation
self.total_price = 0
end
def may_confirm?
@status == :pending_confirmation
end
def confirm!
@status = :confirmed
@items.each(&:hold!)
self.total_price = items.sum(&:price)
end
def total_price
@total_price
end
def total_price=(value)
@total_price = Money.new(value.to_i)
end
# ... same
end
Behaviour should be internal and explicit, not implicit
class OrderController < ApplicationController
def confirm
# ...
@order.confirm! if @order.may_confirm?
# ...
end
end
class Order
def initialize(items: [])
@items = items.to_a
@order_number = generate_unique_number
@status = :pending_confirmation
@total_price = 0
end
def may_confirm?
@status == :pending_confirmation
end
private :may_confirm?
def confirm!
return false unless may_confirm?
@status = :confirmed
@items.each(&:hold!)
self.total_price = items.sum(&:price)
end
def total_price
@total_price
end
def total_price=(value)
@total_price = Money.new(value.to_i)
end
# ... same
end
class OrderController < ApplicationController
def confirm
# ...
if @order.confirm!
# ...
end
end
class Order
def initialize(items: [])
@items = items
@order_number = generate_unique_number
@status = :pending
end
private
def generate_unique_number
# ...
end
end
it sucks because ...
@order = Order.new
@order.instance_variable_set(:@status, :cancelled)
@order.send(:generate_unique_number)
class Order
def initialize(items: [])
@items = items
@order_number = generate_unique_number
@status = :pending
end
undef :instance_variable_set,
:instance_variable_get,
:instance_variables,
:send
# ...
end
@order = Order.new
@order.cancel!
@order.public_send(:generate_unique_number)
Enforce public interface methods at all times
Prefer methods over attribute setters
Code should express intent, but not implementation
Ensure exposed state is immutable
Internal state should be completely shielded from the external environment
Hide the inner workings behind an interface
Behaviour should be internal and explicit, not implicit
Avoid methods that allow direct access to hidden information
Sometimes the best defence is a good pair of running shoes
Slides