SlideShare a Scribd company logo
Let’s Do Some Upfront Design 
Windy City Rails 2014 
Mark Menard 
@mark_menard ! 
Enable Labs 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Who likes TDD? 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Rename Method 
Inline Method 
Replace Method with Method Object 
Extract Class 
Who likes refactoring? 
Rename Class 
Replace Temp with Query 
Extract Method 
Move Method 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Extract Method 
Move Method 
Replace Method with Method Object 
Extract Class 
Enable Labs 5 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
A Tale of a Refactoring 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
# some_ruby_program -v -efoo 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
# some_ruby_program -v -efoo 
! 
options = CommandLineOptions.new(ARGV) do 
option :v 
option :e, :string 
end 
! 
if options.has(:v) 
# Do something 
end 
! 
if options.has(:e) 
some_value = options.value(:e) 
# Do something with the expression 
end 
Enable Labs 8 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
describe CommandLineOptions do 
! 
describe "boolean options" do 
let(:options) { CommandLineOptions.new { option :c } } 
it "are true if present" do … 
it "are false if absent" do … 
end 
Enable Labs @mark_menard 
! 
describe "string options" do 
let(:options) { CommandLineOptions.new { option :e, :string } } 
it "must have content" do … 
it "can return the value" do … 
it "return nil if not in argv" do … 
end 
end 
9 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
CommandLineOptions 
boolean options 
are true if present 
are false if absent 
string options 
must have content 
can return the value 
return nil if not in argv 
! 
Finished in 0.00236 seconds 
5 examples, 0 failures 
Enable Labs 10 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class CommandLineOptions 
! 
attr_accessor :argv 
attr_reader :options 
! 
def initialize (argv = [], &block) 
@options = {} 
@argv = argv 
instance_eval &block 
end 
! 
def has (option_flag) 
options.include?(option_flag) && argv.include?("-#{option_flag}") 
end 
! 
def option (option_flag, option_type = :boolean) 
options[option_flag] = option_type 
end 
! 
def valid? 
options.each do |option_flag, option_type| 
return false if option_type == :string && raw_value_for_option(option_flag).length < 3 
end 
end 
! 
def value (option_flag) 
raw_option_value = raw_value_for_option(option_flag) 
return nil unless raw_option_value 
option_type = options[option_flag] 
return raw_option_value[2..-1] if option_type == :string 
end 
! 
private def raw_value_for_option (option_flag) 
argv.find { |arg| arg =~ /-#{option_flag}/ } 
end 
! 
end 
Enable Labs 11 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
# some_ruby_program -v -efoo -i100 
! 
options = CommandLineOptions.new(ARGV) do 
option :v 
option :e, :string 
option :i, :integer 
end 
! 
verbose = options.value(:v) 
expression = options.value(:e) 
iteration_count = options.value(:i) || 1 
Enable Labs 12 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
describe "integer options" do 
it "must have content" 
it "must be an integer" 
it "can return the value as an integer" 
it "returns nil if not in argv" 
end 
Enable Labs 13 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
CommandLineOptions 
boolean options 
are true if present 
are false if absent 
string options 
must have content 
can return the value 
return nil if not in argv 
integer options 
must have content (PENDING: No reason given) 
must be an integer (PENDING: Not yet implemented) 
can return the value as an integer (PENDING: Not yet implemented) 
returns nil if not in argv (PENDING: Not yet implemented) 
Enable Labs 14 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
CommandLineOptions 
boolean options 
are true if present 
are false if absent 
string options 
must have content 
is valid when there is content 
can return the value 
return nil if not in argv 
integer options 
must have content 
must be an integer 
can return the value as an integer 
returns nil if not in argv (PENDING: Not yet implemented) 
! 
Pending: 
CommandLineOptions integer options returns nil if not in argv 
# Not yet implemented 
# ./spec/command_line_options_spec.rb:61 
! 
Finished in 0.00291 seconds 
10 examples, 0 failures, 1 pending 
Enable Labs 15 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class CommandLineOptions ! 
attr_accessor :argv 
attr_reader :options ! 
def initialize (argv = [], &block) 
@options = {} 
@argv = argv 
instance_eval &block 
Enable Labs 16 @mark_menard 
end ! 
def has (option_flag) 
options.include?(option_flag) && argv.include?("-#{option_flag}") 
end ! 
def valid? 
options.each do |option_flag, option_type| 
return false unless option_valid?(option_type, raw_value_for_option(option_flag)) 
end 
end ! 
def value (option_flag) 
raw_option_value = raw_value_for_option(option_flag) 
return nil unless raw_option_value 
option_type = options[option_flag] 
return extract_value_from_raw_value(raw_option_value) if option_type == :string 
end ! 
private def option (option_flag, option_type = :boolean) 
options[option_flag] = option_type 
end ! 
private def option_valid? (option_type, raw_value) 
send("#{option_type}_option_valid?", raw_value) 
end ! 
private def string_option_valid? (raw_value) 
extract_value_from_raw_value(raw_value).length > 0 
end ! 
private def integer_option_valid? (raw_value) 
extract_value_from_raw_value(raw_value).length > 0 && (Integer(extract_value_from_raw_value(raw_value)) rescue false) 
end ! 
private def boolean_option_valid? (raw_value) 
true 
end ! 
private def extract_value_from_raw_value (raw_value) 
raw_value[2..-1] 
end ! 
private def raw_value_for_option (option_flag) 
argv.find { |arg| arg =~ /-#{option_flag}/ } 
end ! 
end 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Oooooof! ! 
! 
This class is getting big… 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
So… let’s extract a class and move 
some methods. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
describe StringOption do 
let(:string_option) { StringOption.new('s', '-sfoo') } 
Enable Labs 19 @mark_menard 
! 
it "has a flag" 
it "is valid when it has a value" 
it "can return it's value when present" 
it "returns nil if the flag has no raw value" 
end 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
CommandLineOptions 
boolean options 
are true if present 
are false if absent 
string options 
must have content 
is valid when there is content 
can return the value 
return nil if not in argv 
integer options 
must have content 
must be an integer 
can return the value as an integer 
returns nil if not in argv (PENDING: Not yet implemented) 
! 
StringOption 
has a flag (PENDING: No reason given) 
is valid when it has a value (PENDING: Not yet implemented) 
can return it's value when present (PENDING: Not yet implemented) 
returns nil if the flag has no raw value (PENDING: Not yet implemented) 
Enable Labs 20 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class StringOption 
! 
attr_reader :flag 
Enable Labs 21 @mark_menard 
! 
def initialize (flag, raw_value) 
@flag = flag 
@raw_value = raw_value 
end 
! 
end 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class CommandLineOptions 
! 
private def string_option_valid? (raw_value) 
extract_value_from_raw_value(raw_value).length > 0 
end 
! 
end 
class StringOption 
! 
attr_reader :flag, :raw_value 
! 
def initialize (flag, raw_value) 
@flag = flag 
@raw_value = raw_value 
end 
! 
def valid? 
extract_value_from_raw_value.length > 0 
end 
! 
private def extract_value_from_raw_value 
raw_value[2..-1] 
end 
! 
end 
Enable Labs 22 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Ow, ow, ow, ow… ow! 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
The Extract Class and Move Method 
refactorings are VERY expensive. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class StringOption 
! 
attr_reader :flag, :raw_value 
! 
def initialize (flag, raw_value) 
@flag = flag 
@raw_value = raw_value 
end 
! 
def valid? 
extract_value_from_raw_value.length > 0 
end 
! 
private def extract_value_from_raw_value 
raw_value[2..-1] 
end 
! 
end 
Enable Labs 28 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class IntegerOption 
! 
attr_reader :flag, :raw_value 
! 
def initialize (flag, raw_value) 
@flag = flag 
@raw_value = raw_value 
end 
! 
def valid? 
extract_value_from_raw_value.length > 0 && real_value_is_integer? 
end 
! 
private def extract_value_from_raw_value 
raw_value[2..-1] 
end 
! 
private def real_value_is_integer? 
(Integer(extract_value_from_raw_value) rescue false) 
end 
! 
end 
Enable Labs 29 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class BooleanOption 
! 
attr_reader :flag, :raw_value 
! 
def initialize (flag, raw_value) 
@flag = flag 
@raw_value = raw_value 
end 
! 
def valid? 
true 
end 
! 
def value 
!!raw_value 
end 
end 
Enable Labs 30 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class Option 
! 
attr_reader :flag, :raw_value 
! 
def initialize (flag, raw_value) 
@flag = flag 
@raw_value = raw_value 
end 
! 
end 
! 
Enable Labs 31 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class StringOption < Option 
! 
def valid? 
extract_value_from_raw_value.length > 0 
end 
! 
private def extract_value_from_raw_value 
raw_value[2..-1] 
end 
! 
end 
Enable Labs 32 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class IntegerOption < Option 
! 
def valid? 
extract_value_from_raw_value.length > 0 && real_value_is_integer? 
end 
! 
private def extract_value_from_raw_value 
raw_value[2..-1] 
end 
! 
private def real_value_is_integer? 
(Integer(extract_value_from_raw_value) rescue false) 
end 
! 
end 
Enable Labs 33 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class BooleanOption < Option 
! 
def valid? 
true 
end 
! 
def value 
!!raw_value 
end 
end 
Enable Labs 34 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
CommandLineOptions 
boolean options 
are true if present 
are false if absent 
string options 
must have content 
is valid when there is content 
can return the value 
return nil if not in argv 
integer options 
must have content 
must be an integer 
can return the value as an integer 
returns nil if not in argv 
! 
StringOption 
has a flag 
is valid when it has a value 
can return it's value when present 
returns nil if the flag has no raw value 
Duplication! 
Enable Labs 35 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
All code is an impediment to change. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Including your tests! 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Can we avoid this churn? 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Do some upfront design! 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
But that’s not agile… right? 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Sequence Diagrams! 
Collaboration Diagrams! 
Mental Experiments! 
Playing with Code / Exploratory Tests 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Container Cost Calculations 
• Bunker Charge (fuel charge)! 
• Taxes! 
• Carrier Surcharges 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Enable Labs 43 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
def initialize (type: type, shipment: shipment) 
@type = type 
@shipment = shipment 
end 
! 
def calculate 
cost = 0 
itinerary = shipment.itinerary 
carrier = shipment.carrier 
! 
# Calculate bunker charge 
! 
distance = itinerary.segments.sum(&:distance_in_km) 
cost += carrier.bunker_charge_per_km * distance 
! 
# Calculate taxes 
! 
cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) 
! 
# Calculate surcharges 
! 
cost += carrier.surcharges.sum(&:charge) 
cost 
end 
end 
Enable Labs 44 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
def initialize (type: type, shipment: shipment) 
@type = type 
@shipment = shipment 
end 
! 
def calculate 
cost = 0 
itinerary = shipment.itinerary 
carrier = shipment.carrier 
! 
# Calculate bunker charge 
! 
distance = itinerary.segments.sum(&:distance_in_km) 
cost += carrier.bunker_charge_per_km * distance 
! 
# Calculate taxes 
! 
cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) 
! 
# Calculate surcharges 
! 
cost += carrier.surcharges.sum(&:charge) 
cost 
end 
end 
What 
Enable Labs 44 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
def initialize (type: type, shipment: shipment) 
@type = type 
@shipment = shipment 
end 
! 
def calculate 
cost = 0 
itinerary = shipment.itinerary 
carrier = shipment.carrier 
! 
# Calculate bunker charge 
! 
distance = itinerary.segments.sum(&:distance_in_km) 
cost += carrier.bunker_charge_per_km * distance 
! 
# Calculate taxes 
! 
cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) 
! 
# Calculate surcharges 
! 
cost += carrier.surcharges.sum(&:charge) 
cost 
end 
end 
How 
Enable Labs 44 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
def initialize (type: type, shipment: shipment) 
@type = type 
@shipment = shipment 
end 
! 
def calculate 
cost = 0 
itinerary = shipment.itinerary 
carrier = shipment.carrier 
! 
# Calculate bunker charge 
! 
distance = itinerary.segments.sum(&:distance_in_km) 
cost += carrier.bunker_charge_per_km * distance 
! 
# Calculate taxes 
! 
cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) 
! 
# Calculate surcharges 
! 
cost += carrier.surcharges.sum(&:charge) 
cost 
end 
end 
What 
How 
Enable Labs 45 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Enable Labs 46 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :itinerary, :carrier 
! 
def initialize (shipment: shipment) 
@shipment = shipment 
@itinerary = shipment.itinerary 
@carrier = shipment.carrier 
end 
! 
def calculate 
calculate_bunker_charge + 
calculate_tax_charge + 
calculate_surcharge 
end 
! 
def calculate_bunker_charge 
distance = itinerary.segments.sum(&:distance_in_km) 
carrier.bunker_charge_per_km * distance 
end 
! 
def calculate_tax_charge 
itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) 
end 
! 
def calculate_surcharge 
carrier.surcharges.sum(&:charge) 
end 
! 
end 
What 
How 
Enable Labs 47 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Processor Coordinator 
Coordinator Coordinator 
Coordinator 
Processor 
Processor 
Processor 
Processor 
Processor 
Processor 
Processor Coordinators 
vs Processors 
Processor 
Processor 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
How What 
What What 
How 
How 
What 
How 
How 
How 
How 
How Coordinators 
vs Processors 
How 
How 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Container Cost Calculations 
• Bunker Charge (fuel charge)! 
• Taxes! 
• Carrier Surcharges 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
What How 
Enable Labs 50 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Coordinator Processor 
Enable Labs 51 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
So, how do I build that without the 
churn? 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
So, how do I build that without the churn? 
1. Identify a top level Coordinator and give it a descriptive name based 
on the use case. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
end 
Enable Labs 54 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
So, how do I build that without the churn? 
1. Identify a top level Coordinator and give it a descriptive name based 
on the use case.! 
2. Write a failing top level feature spec that runs everything without test 
doubles using real data. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation feature" do 
before(:each) do 
setup_itineraries 
setup_carrier_charges 
end 
! 
describe "basic shipment" do 
let(:shipment) { build(:basic_shipment) } 
! 
it "should correctly calculate shipment charge" do 
calculator = ShipmentCostCalculator.new(shipment: shipment) 
expect(calculator.calculate).to eq(1200) 
end 
end 
! 
# more scenarios... 
! 
end 
Enable Labs 56 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation feature" do 
before(:each) do 
setup_itineraries 
setup_carrier_charges 
end 
! 
describe "basic shipment" do 
let(:shipment) { build(:basic_shipment) } 
! 
it "should correctly calculate shipment charge" do 
calculator = ShipmentCostCalculator.new(shipment: shipment) 
expect(calculator.calculate).to eq(1200) 
end 
end 
! 
# more scenarios... 
! 
end 
Enable Labs 56 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation feature" do 
before(:each) do 
setup_itineraries 
setup_carrier_charges 
end 
! 
describe "basic shipment" do 
let(:shipment) { build(:basic_shipment) } 
! 
it "should correctly calculate shipment charge" do 
calculator = ShipmentCostCalculator.new(shipment: shipment) 
expect(calculator.calculate).to eq(1200) 
end 
end 
! 
# more scenarios... 
! 
end 
Enable Labs 56 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation feature" do 
before(:each) do 
setup_itineraries 
setup_carrier_charges 
end 
! 
describe "basic shipment" do 
let(:shipment) { build(:basic_shipment) } 
! 
it "should correctly calculate shipment charge" do 
calculator = ShipmentCostCalculator.new(shipment: shipment) 
expect(calculator.calculate).to eq(1200) 
end 
end 
! 
# more scenarios... 
! 
end 
Enable Labs 56 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment 
! 
def initialize (shipment:) 
@shipment = shipment 
end 
! 
def calculate 
end 
end 
Enable Labs 57 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
So, how do I build that without the churn? 
1. Identify a top level Coordinator and give it a descriptive name based 
on the use case.! 
2. Write a failing top level feature spec that runs everything without test 
doubles using real data.! 
3. Write a failing coordinator spec using test doubles for the top level 
coordinator.! 
1. Identify other collaborators and inject them using test doubles. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { 
double("bunker_charge_calculator", :calculate => 1) 
} 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
end 
Enable Labs 59 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { 
double("bunker_charge_calculator", :calculate => 1) 
} 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
end 
Enable Labs 59 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { 
double("bunker_charge_calculator", :calculate => 1) 
} 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
end 
Enable Labs 59 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { 
double("bunker_charge_calculator", :calculate => 1) 
} 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
end 
Enable Labs 59 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { 
double("bunker_charge_calculator", :calculate => 1) 
} 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
end 
Enable Labs 59 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { 
double("bunker_charge_calculator", :calculate => 1) 
} 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
end 
Enable Labs 59 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { 
double("bunker_charge_calculator", :calculate => 1) 
} 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
end 
Enable Labs 59 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
So, how do I build that without the churn? 
1. Identify a top level Coordinator and give it a descriptive name based 
on the use case.! 
2. Write a failing top level feature spec that runs everything without test 
doubles using real data.! 
3. Write a failing coordinator spec using test doubles for the top level 
coordinator.! 
1. Identify other collaborators and inject them using test doubles.! 
4. Make the top level Coordinator pass the spec. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :bunker_charge_calculator 
! 
def initialize (shipment:, 
bunker_charge_calculator: BunkerChargeCalculator.new) 
! 
@shipment = shipment 
@bunker_charge_calculator = bunker_charge_calculator 
end 
! 
def calculate 
bunker_charge_calculator.calculate(shipment) 
end 
! 
end 
Enable Labs 61 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :bunker_charge_calculator 
! 
def initialize (shipment:, 
bunker_charge_calculator: BunkerChargeCalculator.new) 
! 
@shipment = shipment 
@bunker_charge_calculator = bunker_charge_calculator 
end 
! 
def calculate 
bunker_charge_calculator.calculate(shipment) 
end 
! 
end 
Enable Labs 61 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :bunker_charge_calculator 
! 
def initialize (shipment:, 
bunker_charge_calculator: BunkerChargeCalculator.new) 
! 
@shipment = shipment 
@bunker_charge_calculator = bunker_charge_calculator 
end 
! 
def calculate 
bunker_charge_calculator.calculate(shipment) 
end 
! 
end 
Enable Labs 61 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
let(:shipment) { double } 
let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } 
! 
let(:shipment_cost_calculator) { 
ShipmentCostCalculator.new(shipment: shipment) 
} 
! 
before(:each) do 
expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) 
expect(TaxChargeCalculator).to receive(:new).and_return(tax_charge_calculator) 
expect(SurchargeCalculator).to receive(:new).and_return(surcharge_calculator) 
end 
! 
it "uses a bunker charge calculator" do 
expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
it "uses a tax charge calculator" do 
expect(tax_charge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
it "uses a surcharge calculator" do 
expect(surcharge_calculator).to receive(:calculate).once.with(shipment) 
shipment_cost_calculator.calculate 
end 
! 
it "returns the sum of bunker charge, tax charge, and shurcharges" do 
expect(shipment_cost_calculator.calculate).to eq(6) 
end 
end 
Enable Labs 62 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :bunker_charge_calculator 
attr_reader :tax_charge_calculator, :surcharge_calculator 
! 
def initialize (shipment:, 
bunker_charge_calculator: BunkerChargeCalculator.new, 
tax_charge_calculator: TaxChargeCalculator.new, 
surcharge_calculator: SurchargeCalculator.new) 
! 
@shipment = shipment 
@bunker_charge_calculator = bunker_charge_calculator 
@tax_charge_calculator = tax_charge_calculator 
@surcharge_calculator = surcharge_calculator 
end 
! 
def calculate 
bunker_charge_calculator.calculate(shipment) + 
tax_charge_calculator.calculate(shipment) + 
surcharge_calculator.calculate(shipment) 
end 
! 
end 
Enable Labs 63 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :bunker_charge_calculator 
attr_reader :tax_charge_calculator, :surcharge_calculator 
! 
def initialize (shipment:, 
bunker_charge_calculator: BunkerChargeCalculator.new, 
tax_charge_calculator: TaxChargeCalculator.new, 
surcharge_calculator: SurchargeCalculator.new) 
! 
@shipment = shipment 
@bunker_charge_calculator = bunker_charge_calculator 
@tax_charge_calculator = tax_charge_calculator 
@surcharge_calculator = surcharge_calculator 
end 
! 
def calculate 
bunker_charge_calculator.calculate(shipment) + 
tax_charge_calculator.calculate(shipment) + 
surcharge_calculator.calculate(shipment) 
end 
! 
end 
Enable Labs 63 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :bunker_charge_calculator 
attr_reader :tax_charge_calculator, :surcharge_calculator 
! 
def initialize (shipment:, 
bunker_charge_calculator: BunkerChargeCalculator.new, 
tax_charge_calculator: TaxChargeCalculator.new, 
surcharge_calculator: SurchargeCalculator.new) 
! 
@shipment = shipment 
@bunker_charge_calculator = bunker_charge_calculator 
@tax_charge_calculator = tax_charge_calculator 
@surcharge_calculator = surcharge_calculator 
end 
! 
def calculate 
bunker_charge_calculator.calculate(shipment) + 
tax_charge_calculator.calculate(shipment) + 
surcharge_calculator.calculate(shipment) 
end 
! 
end 
Enable Labs 63 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
class ShipmentCostCalculator 
attr_reader :shipment, :bunker_charge_calculator 
attr_reader :tax_charge_calculator, :surcharge_calculator 
! 
def initialize (shipment:, 
bunker_charge_calculator: BunkerChargeCalculator.new, 
tax_charge_calculator: TaxChargeCalculator.new, 
surcharge_calculator: SurchargeCalculator.new) 
! 
@shipment = shipment 
@bunker_charge_calculator = bunker_charge_calculator 
@tax_charge_calculator = tax_charge_calculator 
@surcharge_calculator = surcharge_calculator 
end 
! 
def calculate 
bunker_charge_calculator.calculate(shipment) + 
tax_charge_calculator.calculate(shipment) + 
surcharge_calculator.calculate(shipment) 
end 
! 
end 
Enable Labs 63 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
require 'spec_helper' 
! 
describe "shipment cost calculation" do 
! 
# . . . 
it "returns the sum of bunker charge, tax charge, and shurcharges" do 
expect(shipment_cost_calculator.calculate).to eq(6) 
end 
end 
Enable Labs 64 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
So, how do I build that without the churn? 
1. Identify a top level Coordinator and give it a descriptive name based 
on the use case.! 
2. Write a top level feature spec that runs everything without test 
doubles using real data.! 
3. Write a coordinator spec using test doubles for the top level 
coordinator.! 
1. Identify other collaborators and inject them using test doubles.! 
4. Make the top level Coordinator pass the spec.! 
5. Repeat 2-3 for the identified collaborators until you can’t delegate 
anymore. 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Coordinator ? 
Enable Labs 66 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Shipment 
Cost 
Calculator Surcharge 
Calculator 
Tax Charge 
Calculator 
Bunker 
Charge 
Calculator 
Bunker Rate 
Repository 
Container 
Weight 
Calculator 
Enable Labs 67 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
I can’t delegate anymore!! 
! 
Now what do I do? 
Enable Labs @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Triage and ! Enable Labs 
Training 
Two days on-site at your place of business.! 
! ! 
First Day: Code Deep Dive! 
! 
• Code Review! 
• OO Design Review! 
• Testing Strategy Review! 
• Hands-on Pairing! 
! 
Second Day: Training! 
! 
• Directly address issues identified on day one. 
Get hands-on 
mentoring and help 
with your project. 
Two days on-site 
hands-on. 
Enable Labs 69 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Small Code! Enable Labs 
Workshop 
Learn how to write 
small beautiful 
code. 
Smaller classes and systems are easier to build and maintain. From 
fundamentals through system design the Small Code Workshop 
teaches:! 
! 
• Method Design! 
• Naming Strategies! 
• Class Design! 
• Inheritance vs Composition! 
• Exploratory Testing! 
• Dependency Management! 
• Directed Refactorings! 
• … and more 
Two to four days 
on-site hands-on. 
Enable Labs 70 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
On the Project ! Enable Labs 
Training 
Work hand in hand! 
with Enable Labs! 
staff on your 
projects. 
You’ll work hands on with Enable Labs developers and trainers on 
your project using your code. Our developers will focus on teaching 
the fundamental principles of Small Code and good object oriented 
design while moving your project forward.! 
! 
You get to define your objectives for the two weeks and our team will 
focus on getting you and your team to a better place.! 
! 
We will focus on:! 
! 
• Immediate improvements to the design of your most difficult 
components.! 
• Improving your test suite design to assure it’s delivering maximum 
value.! 
• Teaching fundamentals of class naming, class design, separation 
of concerns and more. 
Level up in two 
weeks while 
working in your 
code base. 
Enable Labs 71 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Custom App! Enable Labs 
Development 
Full life-cycle 
project 
development 
From idea to deployment our team can bring your vision to life. Using 
a highly interactive process our team will work directly with your stake 
holders to create your applications. 
Fully co-located 
cross functional 
team 
We use:! 
! 
• Test Driven Development! 
• Client on-site! 
• Pair Programming 
We work with:! 
! 
• Ruby! 
• Rails! 
• AngularJS! 
• iOS! 
• Android! 
• … and much more 
Enable Labs 72 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
Enable Labs 
@mark_menard 
http://www.enablelabs.com/ 
info@enablelabs.com 
866-895-8189 
Enable Labs 73 @mark_menard 
LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014

More Related Content

PDF
Small Code - RailsConf 2014
PDF
Small Code - Ruby on Ales 2014
PDF
Write Small Things (Code)
PDF
Swift Tutorial Part 1. The Complete Guide For Swift Programming Language
PPTX
Clean code
PDF
Sorbet at Grailed
PPTX
Developer’s viewpoint on swift programming language
PPTX
IOS Swift language 2nd tutorial
Small Code - RailsConf 2014
Small Code - Ruby on Ales 2014
Write Small Things (Code)
Swift Tutorial Part 1. The Complete Guide For Swift Programming Language
Clean code
Sorbet at Grailed
Developer’s viewpoint on swift programming language
IOS Swift language 2nd tutorial

What's hot (13)

PPS
Coding Best Practices
PPT
ppt18
PPT
name name2 n2
PPT
name name2 n2.ppt
PPT
name name2 n
PPT
Ruby for Perl Programmers
PPT
ppt9
PDF
STC 2016 Programming Language Storytime
PDF
Annotations in PHP, They Exist.
PPT
PDF
Ruby for Java Developers
PPT
Ruby For Java Programmers
PDF
Functional Programming - Worth the Effort
Coding Best Practices
ppt18
name name2 n2
name name2 n2.ppt
name name2 n
Ruby for Perl Programmers
ppt9
STC 2016 Programming Language Storytime
Annotations in PHP, They Exist.
Ruby for Java Developers
Ruby For Java Programmers
Functional Programming - Worth the Effort
Ad

Viewers also liked (12)

PDF
Fabric guide
PPTX
1312- System of Wheat Intensification
PPTX
Changes Under Mao - Agriculture
PPTX
Kisan Call Center
PPTX
Smart farming using ARDUINO (Nirma University)
PPTX
Final ppt
PPTX
Pra presentation
PPTX
Individual contact method in Extension Education
PPT
Fundamental of Extension Methods: Tools and Techniques of PRA
PPT
Presentation on PRA & PLA Process
PPTX
Interpretation of histograms
Fabric guide
1312- System of Wheat Intensification
Changes Under Mao - Agriculture
Kisan Call Center
Smart farming using ARDUINO (Nirma University)
Final ppt
Pra presentation
Individual contact method in Extension Education
Fundamental of Extension Methods: Tools and Techniques of PRA
Presentation on PRA & PLA Process
Interpretation of histograms
Ad

Similar to Let's Do Some Upfront Design - WindyCityRails 2014 (20)

PDF
Everything as-code. Polyglotte Entwicklung in der Praxis. #oop2017
PDF
Everything-as-code. Polyglotte Software-Entwicklung in der Praxis.
PDF
PDF
Puppet Camp Paris 2014: Test Driven Development
PDF
20140408 tdd puppetcamp-paris
KEY
Dsl
PDF
Deep Dive Into Swift
PDF
Objective-C to Swift - Swift Cloud Workshop 3
PDF
Crossing the Bridge: Connecting Rails and your Front-end Framework
PDF
resolvendo problemas de comunicação em equipes distribuídas com bdd
PDF
Rspec and Capybara Intro Tutorial at RailsConf 2013
PDF
Beginning Scala with Skinny Framework #jjug_ccc
ODP
Aura for PHP at Fossmeet 2014
PDF
Metaprogramming Rails
PDF
Exploring Ceylon with Gavin King - JUG BB Talk - Belrin 2014
PDF
Write your Ruby in Style
PDF
Everything-as-code. Eine vielsprachige Reise. #javaland
PDF
Everything-as-code - Polyglotte Softwareentwicklung
PPTX
Code is not text! How graph technologies can help us to understand our code b...
PDF
Angular Intermediate
Everything as-code. Polyglotte Entwicklung in der Praxis. #oop2017
Everything-as-code. Polyglotte Software-Entwicklung in der Praxis.
Puppet Camp Paris 2014: Test Driven Development
20140408 tdd puppetcamp-paris
Dsl
Deep Dive Into Swift
Objective-C to Swift - Swift Cloud Workshop 3
Crossing the Bridge: Connecting Rails and your Front-end Framework
resolvendo problemas de comunicação em equipes distribuídas com bdd
Rspec and Capybara Intro Tutorial at RailsConf 2013
Beginning Scala with Skinny Framework #jjug_ccc
Aura for PHP at Fossmeet 2014
Metaprogramming Rails
Exploring Ceylon with Gavin King - JUG BB Talk - Belrin 2014
Write your Ruby in Style
Everything-as-code. Eine vielsprachige Reise. #javaland
Everything-as-code - Polyglotte Softwareentwicklung
Code is not text! How graph technologies can help us to understand our code b...
Angular Intermediate

More from Mark Menard (12)

PDF
A Tour of Wyriki
PDF
JRuby 6 Years in Production
PDF
Conference of Grand Masters Tech Talk 2013
KEY
Startup Lessons Learned
PDF
Mobile Platforms and App Development
KEY
Ruby on Rails Training - Module 2
KEY
Ruby on Rails Training - Module 1
KEY
Introduction to Ruby
PPT
Intro to Rails ActiveRecord
PPT
Behavior Driven Development with Rails
PPT
Intro to Ruby on Rails
PPT
JRuby in a Java World
A Tour of Wyriki
JRuby 6 Years in Production
Conference of Grand Masters Tech Talk 2013
Startup Lessons Learned
Mobile Platforms and App Development
Ruby on Rails Training - Module 2
Ruby on Rails Training - Module 1
Introduction to Ruby
Intro to Rails ActiveRecord
Behavior Driven Development with Rails
Intro to Ruby on Rails
JRuby in a Java World

Recently uploaded (20)

PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
PDF
1 - Historical Antecedents, Social Consideration.pdf
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
PDF
A comparative analysis of optical character recognition models for extracting...
PPTX
1. Introduction to Computer Programming.pptx
PDF
From MVP to Full-Scale Product A Startup’s Software Journey.pdf
PDF
Web App vs Mobile App What Should You Build First.pdf
PDF
DP Operators-handbook-extract for the Mautical Institute
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
August Patch Tuesday
PDF
Microsoft Solutions Partner Drive Digital Transformation with D365.pdf
PDF
Transform Your ITIL® 4 & ITSM Strategy with AI in 2025.pdf
PPTX
TLE Review Electricity (Electricity).pptx
PPTX
Chapter 5: Probability Theory and Statistics
PPTX
A Presentation on Touch Screen Technology
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
A novel scalable deep ensemble learning framework for big data classification...
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Accuracy of neural networks in brain wave diagnosis of schizophrenia
1 - Historical Antecedents, Social Consideration.pdf
Encapsulation_ Review paper, used for researhc scholars
gpt5_lecture_notes_comprehensive_20250812015547.pdf
A comparative analysis of optical character recognition models for extracting...
1. Introduction to Computer Programming.pptx
From MVP to Full-Scale Product A Startup’s Software Journey.pdf
Web App vs Mobile App What Should You Build First.pdf
DP Operators-handbook-extract for the Mautical Institute
Digital-Transformation-Roadmap-for-Companies.pptx
August Patch Tuesday
Microsoft Solutions Partner Drive Digital Transformation with D365.pdf
Transform Your ITIL® 4 & ITSM Strategy with AI in 2025.pdf
TLE Review Electricity (Electricity).pptx
Chapter 5: Probability Theory and Statistics
A Presentation on Touch Screen Technology
Building Integrated photovoltaic BIPV_UPV.pdf
A novel scalable deep ensemble learning framework for big data classification...
Unlocking AI with Model Context Protocol (MCP)
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf

Let's Do Some Upfront Design - WindyCityRails 2014

  • 1. Let’s Do Some Upfront Design Windy City Rails 2014 Mark Menard @mark_menard ! Enable Labs Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 2. Who likes TDD? Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 3. Rename Method Inline Method Replace Method with Method Object Extract Class Who likes refactoring? Rename Class Replace Temp with Query Extract Method Move Method Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 4. Extract Method Move Method Replace Method with Method Object Extract Class Enable Labs 5 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 5. A Tale of a Refactoring Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 6. # some_ruby_program -v -efoo Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 7. # some_ruby_program -v -efoo ! options = CommandLineOptions.new(ARGV) do option :v option :e, :string end ! if options.has(:v) # Do something end ! if options.has(:e) some_value = options.value(:e) # Do something with the expression end Enable Labs 8 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 8. describe CommandLineOptions do ! describe "boolean options" do let(:options) { CommandLineOptions.new { option :c } } it "are true if present" do … it "are false if absent" do … end Enable Labs @mark_menard ! describe "string options" do let(:options) { CommandLineOptions.new { option :e, :string } } it "must have content" do … it "can return the value" do … it "return nil if not in argv" do … end end 9 LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 9. CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv ! Finished in 0.00236 seconds 5 examples, 0 failures Enable Labs 10 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 10. class CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def has (option_flag) options.include?(option_flag) && argv.include?("-#{option_flag}") end ! def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! def valid? options.each do |option_flag, option_type| return false if option_type == :string && raw_value_for_option(option_flag).length < 3 end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return raw_option_value[2..-1] if option_type == :string end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end ! end Enable Labs 11 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 11. # some_ruby_program -v -efoo -i100 ! options = CommandLineOptions.new(ARGV) do option :v option :e, :string option :i, :integer end ! verbose = options.value(:v) expression = options.value(:e) iteration_count = options.value(:i) || 1 Enable Labs 12 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 12. describe "integer options" do it "must have content" it "must be an integer" it "can return the value as an integer" it "returns nil if not in argv" end Enable Labs 13 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 13. CommandLineOptions boolean options are true if present are false if absent string options must have content can return the value return nil if not in argv integer options must have content (PENDING: No reason given) must be an integer (PENDING: Not yet implemented) can return the value as an integer (PENDING: Not yet implemented) returns nil if not in argv (PENDING: Not yet implemented) Enable Labs 14 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 14. CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv (PENDING: Not yet implemented) ! Pending: CommandLineOptions integer options returns nil if not in argv # Not yet implemented # ./spec/command_line_options_spec.rb:61 ! Finished in 0.00291 seconds 10 examples, 0 failures, 1 pending Enable Labs 15 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 15. class CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block Enable Labs 16 @mark_menard end ! def has (option_flag) options.include?(option_flag) && argv.include?("-#{option_flag}") end ! def valid? options.each do |option_flag, option_type| return false unless option_valid?(option_type, raw_value_for_option(option_flag)) end end ! def value (option_flag) raw_option_value = raw_value_for_option(option_flag) return nil unless raw_option_value option_type = options[option_flag] return extract_value_from_raw_value(raw_option_value) if option_type == :string end ! private def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! private def option_valid? (option_type, raw_value) send("#{option_type}_option_valid?", raw_value) end ! private def string_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 end ! private def integer_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 && (Integer(extract_value_from_raw_value(raw_value)) rescue false) end ! private def boolean_option_valid? (raw_value) true end ! private def extract_value_from_raw_value (raw_value) raw_value[2..-1] end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end ! end LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 16. Oooooof! ! ! This class is getting big… Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 17. So… let’s extract a class and move some methods. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 18. describe StringOption do let(:string_option) { StringOption.new('s', '-sfoo') } Enable Labs 19 @mark_menard ! it "has a flag" it "is valid when it has a value" it "can return it's value when present" it "returns nil if the flag has no raw value" end LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 19. CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv (PENDING: Not yet implemented) ! StringOption has a flag (PENDING: No reason given) is valid when it has a value (PENDING: Not yet implemented) can return it's value when present (PENDING: Not yet implemented) returns nil if the flag has no raw value (PENDING: Not yet implemented) Enable Labs 20 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 20. class StringOption ! attr_reader :flag Enable Labs 21 @mark_menard ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! end LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 21. class CommandLineOptions ! private def string_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 end ! end class StringOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 end ! private def extract_value_from_raw_value raw_value[2..-1] end ! end Enable Labs 22 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 22. Ow, ow, ow, ow… ow! Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 23. The Extract Class and Move Method refactorings are VERY expensive. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 24. class StringOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 end ! private def extract_value_from_raw_value raw_value[2..-1] end ! end Enable Labs 28 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 25. class IntegerOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? extract_value_from_raw_value.length > 0 && real_value_is_integer? end ! private def extract_value_from_raw_value raw_value[2..-1] end ! private def real_value_is_integer? (Integer(extract_value_from_raw_value) rescue false) end ! end Enable Labs 29 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 26. class BooleanOption ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? true end ! def value !!raw_value end end Enable Labs 30 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 27. class Option ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! end ! Enable Labs 31 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 28. class StringOption < Option ! def valid? extract_value_from_raw_value.length > 0 end ! private def extract_value_from_raw_value raw_value[2..-1] end ! end Enable Labs 32 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 29. class IntegerOption < Option ! def valid? extract_value_from_raw_value.length > 0 && real_value_is_integer? end ! private def extract_value_from_raw_value raw_value[2..-1] end ! private def real_value_is_integer? (Integer(extract_value_from_raw_value) rescue false) end ! end Enable Labs 33 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 30. class BooleanOption < Option ! def valid? true end ! def value !!raw_value end end Enable Labs 34 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 31. CommandLineOptions boolean options are true if present are false if absent string options must have content is valid when there is content can return the value return nil if not in argv integer options must have content must be an integer can return the value as an integer returns nil if not in argv ! StringOption has a flag is valid when it has a value can return it's value when present returns nil if the flag has no raw value Duplication! Enable Labs 35 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 32. All code is an impediment to change. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 33. Including your tests! Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 34. Can we avoid this churn? Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 35. Do some upfront design! Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 36. But that’s not agile… right? Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 37. Sequence Diagrams! Collaboration Diagrams! Mental Experiments! Playing with Code / Exploratory Tests Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 38. Container Cost Calculations • Bunker Charge (fuel charge)! • Taxes! • Carrier Surcharges Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 39. Enable Labs 43 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 40. class ShipmentCostCalculator def initialize (type: type, shipment: shipment) @type = type @shipment = shipment end ! def calculate cost = 0 itinerary = shipment.itinerary carrier = shipment.carrier ! # Calculate bunker charge ! distance = itinerary.segments.sum(&:distance_in_km) cost += carrier.bunker_charge_per_km * distance ! # Calculate taxes ! cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) ! # Calculate surcharges ! cost += carrier.surcharges.sum(&:charge) cost end end Enable Labs 44 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 41. class ShipmentCostCalculator def initialize (type: type, shipment: shipment) @type = type @shipment = shipment end ! def calculate cost = 0 itinerary = shipment.itinerary carrier = shipment.carrier ! # Calculate bunker charge ! distance = itinerary.segments.sum(&:distance_in_km) cost += carrier.bunker_charge_per_km * distance ! # Calculate taxes ! cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) ! # Calculate surcharges ! cost += carrier.surcharges.sum(&:charge) cost end end What Enable Labs 44 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 42. class ShipmentCostCalculator def initialize (type: type, shipment: shipment) @type = type @shipment = shipment end ! def calculate cost = 0 itinerary = shipment.itinerary carrier = shipment.carrier ! # Calculate bunker charge ! distance = itinerary.segments.sum(&:distance_in_km) cost += carrier.bunker_charge_per_km * distance ! # Calculate taxes ! cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) ! # Calculate surcharges ! cost += carrier.surcharges.sum(&:charge) cost end end How Enable Labs 44 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 43. class ShipmentCostCalculator def initialize (type: type, shipment: shipment) @type = type @shipment = shipment end ! def calculate cost = 0 itinerary = shipment.itinerary carrier = shipment.carrier ! # Calculate bunker charge ! distance = itinerary.segments.sum(&:distance_in_km) cost += carrier.bunker_charge_per_km * distance ! # Calculate taxes ! cost += itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) ! # Calculate surcharges ! cost += carrier.surcharges.sum(&:charge) cost end end What How Enable Labs 45 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 44. Enable Labs 46 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 45. class ShipmentCostCalculator attr_reader :shipment, :itinerary, :carrier ! def initialize (shipment: shipment) @shipment = shipment @itinerary = shipment.itinerary @carrier = shipment.carrier end ! def calculate calculate_bunker_charge + calculate_tax_charge + calculate_surcharge end ! def calculate_bunker_charge distance = itinerary.segments.sum(&:distance_in_km) carrier.bunker_charge_per_km * distance end ! def calculate_tax_charge itinerary.segments.select { |s| s.taxable? }.sum(&:tax_fee) end ! def calculate_surcharge carrier.surcharges.sum(&:charge) end ! end What How Enable Labs 47 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 46. Processor Coordinator Coordinator Coordinator Coordinator Processor Processor Processor Processor Processor Processor Processor Coordinators vs Processors Processor Processor Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 47. How What What What How How What How How How How How Coordinators vs Processors How How Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 48. Container Cost Calculations • Bunker Charge (fuel charge)! • Taxes! • Carrier Surcharges Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 49. What How Enable Labs 50 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 50. Coordinator Processor Enable Labs 51 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 51. So, how do I build that without the churn? Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 52. So, how do I build that without the churn? 1. Identify a top level Coordinator and give it a descriptive name based on the use case. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 53. class ShipmentCostCalculator end Enable Labs 54 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 54. So, how do I build that without the churn? 1. Identify a top level Coordinator and give it a descriptive name based on the use case.! 2. Write a failing top level feature spec that runs everything without test doubles using real data. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 55. require 'spec_helper' ! describe "shipment cost calculation feature" do before(:each) do setup_itineraries setup_carrier_charges end ! describe "basic shipment" do let(:shipment) { build(:basic_shipment) } ! it "should correctly calculate shipment charge" do calculator = ShipmentCostCalculator.new(shipment: shipment) expect(calculator.calculate).to eq(1200) end end ! # more scenarios... ! end Enable Labs 56 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 56. require 'spec_helper' ! describe "shipment cost calculation feature" do before(:each) do setup_itineraries setup_carrier_charges end ! describe "basic shipment" do let(:shipment) { build(:basic_shipment) } ! it "should correctly calculate shipment charge" do calculator = ShipmentCostCalculator.new(shipment: shipment) expect(calculator.calculate).to eq(1200) end end ! # more scenarios... ! end Enable Labs 56 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 57. require 'spec_helper' ! describe "shipment cost calculation feature" do before(:each) do setup_itineraries setup_carrier_charges end ! describe "basic shipment" do let(:shipment) { build(:basic_shipment) } ! it "should correctly calculate shipment charge" do calculator = ShipmentCostCalculator.new(shipment: shipment) expect(calculator.calculate).to eq(1200) end end ! # more scenarios... ! end Enable Labs 56 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 58. require 'spec_helper' ! describe "shipment cost calculation feature" do before(:each) do setup_itineraries setup_carrier_charges end ! describe "basic shipment" do let(:shipment) { build(:basic_shipment) } ! it "should correctly calculate shipment charge" do calculator = ShipmentCostCalculator.new(shipment: shipment) expect(calculator.calculate).to eq(1200) end end ! # more scenarios... ! end Enable Labs 56 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 59. class ShipmentCostCalculator attr_reader :shipment ! def initialize (shipment:) @shipment = shipment end ! def calculate end end Enable Labs 57 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 60. So, how do I build that without the churn? 1. Identify a top level Coordinator and give it a descriptive name based on the use case.! 2. Write a failing top level feature spec that runs everything without test doubles using real data.! 3. Write a failing coordinator spec using test doubles for the top level coordinator.! 1. Identify other collaborators and inject them using test doubles. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 61. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! end Enable Labs 59 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 62. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! end Enable Labs 59 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 63. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! end Enable Labs 59 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 64. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! end Enable Labs 59 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 65. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! end Enable Labs 59 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 66. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! end Enable Labs 59 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 67. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! end Enable Labs 59 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 68. So, how do I build that without the churn? 1. Identify a top level Coordinator and give it a descriptive name based on the use case.! 2. Write a failing top level feature spec that runs everything without test doubles using real data.! 3. Write a failing coordinator spec using test doubles for the top level coordinator.! 1. Identify other collaborators and inject them using test doubles.! 4. Make the top level Coordinator pass the spec. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 69. class ShipmentCostCalculator attr_reader :shipment, :bunker_charge_calculator ! def initialize (shipment:, bunker_charge_calculator: BunkerChargeCalculator.new) ! @shipment = shipment @bunker_charge_calculator = bunker_charge_calculator end ! def calculate bunker_charge_calculator.calculate(shipment) end ! end Enable Labs 61 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 70. class ShipmentCostCalculator attr_reader :shipment, :bunker_charge_calculator ! def initialize (shipment:, bunker_charge_calculator: BunkerChargeCalculator.new) ! @shipment = shipment @bunker_charge_calculator = bunker_charge_calculator end ! def calculate bunker_charge_calculator.calculate(shipment) end ! end Enable Labs 61 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 71. class ShipmentCostCalculator attr_reader :shipment, :bunker_charge_calculator ! def initialize (shipment:, bunker_charge_calculator: BunkerChargeCalculator.new) ! @shipment = shipment @bunker_charge_calculator = bunker_charge_calculator end ! def calculate bunker_charge_calculator.calculate(shipment) end ! end Enable Labs 61 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 72. require 'spec_helper' ! describe "shipment cost calculation" do ! let(:shipment) { double } let(:bunker_charge_calculator) { double("bunker_charge_calculator", :calculate => 1) } ! let(:shipment_cost_calculator) { ShipmentCostCalculator.new(shipment: shipment) } ! before(:each) do expect(BunkerChargeCalculator).to receive(:new).and_return(bunker_charge_calculator) expect(TaxChargeCalculator).to receive(:new).and_return(tax_charge_calculator) expect(SurchargeCalculator).to receive(:new).and_return(surcharge_calculator) end ! it "uses a bunker charge calculator" do expect(bunker_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! it "uses a tax charge calculator" do expect(tax_charge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! it "uses a surcharge calculator" do expect(surcharge_calculator).to receive(:calculate).once.with(shipment) shipment_cost_calculator.calculate end ! it "returns the sum of bunker charge, tax charge, and shurcharges" do expect(shipment_cost_calculator.calculate).to eq(6) end end Enable Labs 62 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 73. class ShipmentCostCalculator attr_reader :shipment, :bunker_charge_calculator attr_reader :tax_charge_calculator, :surcharge_calculator ! def initialize (shipment:, bunker_charge_calculator: BunkerChargeCalculator.new, tax_charge_calculator: TaxChargeCalculator.new, surcharge_calculator: SurchargeCalculator.new) ! @shipment = shipment @bunker_charge_calculator = bunker_charge_calculator @tax_charge_calculator = tax_charge_calculator @surcharge_calculator = surcharge_calculator end ! def calculate bunker_charge_calculator.calculate(shipment) + tax_charge_calculator.calculate(shipment) + surcharge_calculator.calculate(shipment) end ! end Enable Labs 63 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 74. class ShipmentCostCalculator attr_reader :shipment, :bunker_charge_calculator attr_reader :tax_charge_calculator, :surcharge_calculator ! def initialize (shipment:, bunker_charge_calculator: BunkerChargeCalculator.new, tax_charge_calculator: TaxChargeCalculator.new, surcharge_calculator: SurchargeCalculator.new) ! @shipment = shipment @bunker_charge_calculator = bunker_charge_calculator @tax_charge_calculator = tax_charge_calculator @surcharge_calculator = surcharge_calculator end ! def calculate bunker_charge_calculator.calculate(shipment) + tax_charge_calculator.calculate(shipment) + surcharge_calculator.calculate(shipment) end ! end Enable Labs 63 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 75. class ShipmentCostCalculator attr_reader :shipment, :bunker_charge_calculator attr_reader :tax_charge_calculator, :surcharge_calculator ! def initialize (shipment:, bunker_charge_calculator: BunkerChargeCalculator.new, tax_charge_calculator: TaxChargeCalculator.new, surcharge_calculator: SurchargeCalculator.new) ! @shipment = shipment @bunker_charge_calculator = bunker_charge_calculator @tax_charge_calculator = tax_charge_calculator @surcharge_calculator = surcharge_calculator end ! def calculate bunker_charge_calculator.calculate(shipment) + tax_charge_calculator.calculate(shipment) + surcharge_calculator.calculate(shipment) end ! end Enable Labs 63 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 76. class ShipmentCostCalculator attr_reader :shipment, :bunker_charge_calculator attr_reader :tax_charge_calculator, :surcharge_calculator ! def initialize (shipment:, bunker_charge_calculator: BunkerChargeCalculator.new, tax_charge_calculator: TaxChargeCalculator.new, surcharge_calculator: SurchargeCalculator.new) ! @shipment = shipment @bunker_charge_calculator = bunker_charge_calculator @tax_charge_calculator = tax_charge_calculator @surcharge_calculator = surcharge_calculator end ! def calculate bunker_charge_calculator.calculate(shipment) + tax_charge_calculator.calculate(shipment) + surcharge_calculator.calculate(shipment) end ! end Enable Labs 63 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 77. require 'spec_helper' ! describe "shipment cost calculation" do ! # . . . it "returns the sum of bunker charge, tax charge, and shurcharges" do expect(shipment_cost_calculator.calculate).to eq(6) end end Enable Labs 64 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 78. So, how do I build that without the churn? 1. Identify a top level Coordinator and give it a descriptive name based on the use case.! 2. Write a top level feature spec that runs everything without test doubles using real data.! 3. Write a coordinator spec using test doubles for the top level coordinator.! 1. Identify other collaborators and inject them using test doubles.! 4. Make the top level Coordinator pass the spec.! 5. Repeat 2-3 for the identified collaborators until you can’t delegate anymore. Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 79. Coordinator ? Enable Labs 66 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 80. Shipment Cost Calculator Surcharge Calculator Tax Charge Calculator Bunker Charge Calculator Bunker Rate Repository Container Weight Calculator Enable Labs 67 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 81. I can’t delegate anymore!! ! Now what do I do? Enable Labs @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 82. Triage and ! Enable Labs Training Two days on-site at your place of business.! ! ! First Day: Code Deep Dive! ! • Code Review! • OO Design Review! • Testing Strategy Review! • Hands-on Pairing! ! Second Day: Training! ! • Directly address issues identified on day one. Get hands-on mentoring and help with your project. Two days on-site hands-on. Enable Labs 69 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 83. Small Code! Enable Labs Workshop Learn how to write small beautiful code. Smaller classes and systems are easier to build and maintain. From fundamentals through system design the Small Code Workshop teaches:! ! • Method Design! • Naming Strategies! • Class Design! • Inheritance vs Composition! • Exploratory Testing! • Dependency Management! • Directed Refactorings! • … and more Two to four days on-site hands-on. Enable Labs 70 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 84. On the Project ! Enable Labs Training Work hand in hand! with Enable Labs! staff on your projects. You’ll work hands on with Enable Labs developers and trainers on your project using your code. Our developers will focus on teaching the fundamental principles of Small Code and good object oriented design while moving your project forward.! ! You get to define your objectives for the two weeks and our team will focus on getting you and your team to a better place.! ! We will focus on:! ! • Immediate improvements to the design of your most difficult components.! • Improving your test suite design to assure it’s delivering maximum value.! • Teaching fundamentals of class naming, class design, separation of concerns and more. Level up in two weeks while working in your code base. Enable Labs 71 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 85. Custom App! Enable Labs Development Full life-cycle project development From idea to deployment our team can bring your vision to life. Using a highly interactive process our team will work directly with your stake holders to create your applications. Fully co-located cross functional team We use:! ! • Test Driven Development! • Client on-site! • Pair Programming We work with:! ! • Ruby! • Rails! • AngularJS! • iOS! • Android! • … and much more Enable Labs 72 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014
  • 86. Enable Labs @mark_menard http://www.enablelabs.com/ info@enablelabs.com 866-895-8189 Enable Labs 73 @mark_menard LetsDoSomeUpfrontDesign-WindyCityRails2014 - September 4, 2014