Upload
mark-menard
View
320
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Writing small classes is hard. You know you should, but how? It's so much easier to write a large class. In this talk we'll build up a set of small classes starting from nothing using a set of directed refactorings applied as we build, all while keeping our tests green. We'll identify abstractions yearning to be free of their big class cages. In the process we'll also see how basic patterns such as composition, delegation and dependency inversion emerge from using small objects.
Citation preview
Enable Labs @mark_menard
#smallcode
Small Code
Mark Menard
RailsConf 2014!April 24, 2014
@mark_menard !Enable Labs
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
‘The great thing about writing shitty code that “just works,” is that it is too risky and too expensive to change, so it lives forever.’!
!!
-Reginald Braithwaite @raganwald
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
What do I mean by small?
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
It’s not about total line count.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
It’s not about method count.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
It’s not about class
count.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
It’s About
Small methods! Small classes
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Why should we strive for small code?
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
We don’t know what the future will bring.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Raise the level of abstraction.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Create composable objects.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Enable Future Change
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
The goal: Small units of understandable code that are amenable to change.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Our Primary Tools
• Extract Method!
• Move Method!
• Extract Class
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Let’s Look at Some Code
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
# some_ruby_program -v
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
options = CommandLineOptions.new(ARGV) do option :c option :v option :e end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeif options.has(:c) # Do something end !
if options.has(:v) # Do something else end !
if options.has(:e) # Do the other thing end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode# some_ruby_program -v !options = CommandLineOptions.new(ARGV) do option :c option :v option :e end !if options.has(:c) # Do something end !if options.has(:v) # Do something else end !if options.has(:e) # Do the other thing end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
describe CommandLineOptions do ! describe 'options' do ! let(:parser) { CommandLineOptions.new { option :c } } ! it "are true if present" do … ! it "are false if absent" do … end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
CommandLineOptions options are true if present are false if absent !
Finished in 0.00206 seconds 2 examples, 2 failures
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass 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) options << option_flag end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass 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) options << option_flag end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass 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) options << option_flag end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass 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) options << option_flag end !end
options = CommandLineOptions.new(ARGV) do option :c option :v option :e end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
CommandLineOptions options are true if present are false if absent !
Finished in 0.00206 seconds 2 examples, 0 failures
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Done!
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
% some_ruby_program -v -sfoo
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
options = CommandLineOptions.new(ARGV) do option :v option :s, :string end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeif options.valid? if options.value(:v) # Do something end !
if (s_option = options.value(:s)) # Do something end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeif options.valid? if options.value(:v) # Do something end !
if (s_option = options.value(:s)) # Do something end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeif options.valid? if options.value(:v) # Do something end !
if (s_option = options.value(:s)) # Do something end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode# some_ruby_program -v -sfoo !options = CommandLineOptions.new(ARGV) do option :v option :s, :string end !if options.valid? if options.value(:v) # Do something end ! if (s_option = options.value(:s)) # Do something end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions !
… def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end !
… !
end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
CommandLineOptions boolean options are true if present are false if absent string options must have content are valid if there is content are valid if not in argv can return the value return nil if not in argv!Finished in 0.00401 seconds7 examples, 0 failures
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Methods
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
The first rule of methods:
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Do one thing. Do it well. Do only that thing.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
One level of abstraction per method.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Use Descriptive Names
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
The fewer arguments the better.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Separate Queries from Commands
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Don’t Repeat Yourself
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Extract Method Refactoring
def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Extract Method Refactoring
def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end
High level of abstraction
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Extract Method Refactoring
def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end
Low level of abstraction
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Extract Method Refactoring
def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end
def print_invoice_for_amount (amount) print_header print_details (amount) end !def print_details (amount) puts "Name: #{@name}" puts "Amount: #{amount}" end
Move this to here
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Extract Method Refactoring
def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end
def print_invoice_for_amount (amount) print_header print_details (amount) end !def print_details (amount) puts "Name: #{@name}" puts "Amount: #{amount}" end
Same level of abstraction
Move this to here
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return false if option_type == :string && raw_value && raw_value.length < 3 end end ! def value (option_flag) raw_option_value = argv.find { |arg| arg =~ /^-#{option_flag}/ } return nil unless raw_option_value option_type = options[option_flag] return true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if option_type == :string && raw_option_value && raw_option_value.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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /^-#{option_flag}/ } end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if option_type == :string && raw_option_value && raw_option_value.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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string end ! def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /^-#{option_flag}/ } end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if option_type == :string && !string_option_valid?(raw_option_value) 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 true if option_type == :boolean && raw_option_value return extract_content(raw_option_value) if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if option_type == :string && !string_option_valid?(raw_option_value) 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 true if option_type == :boolean && raw_option_value return extract_content(raw_option_value) if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if option_type == :string && !string_option_valid?(raw_option_value) 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 true if option_type == :boolean && raw_option_value return extract_content(raw_option_value) if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if option_type == :string && !string_option_valid?(raw_option_value) 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 true if option_type == :boolean && raw_option_value return extract_content(raw_option_value) if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if option_type == :string && !string_option_valid?(raw_option_value) 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 true if option_type == :boolean && raw_option_value return extract_content(raw_option_value) if option_type == :string end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
# 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
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if (option_type == :string || option_type == :integer) && raw_option_value && raw_option_value.length < 3 return false if option_type == :integer && raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) 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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string return (Integer(raw_option_value[2..-1])) if option_type == :integer end ! private def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /^-#{option_flag}/ } end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeCommandLineOptions boolean options are true if present are false if absent string options must have content are valid if there is content are valid if not in argv can return the value return nil if not in argv integer options must have content are valid if there is content and it's an integer are invalid if the content is not an integer are valid if not in argv can return the value return nil if not in argv!Finished in 0.00338 seconds13 examples, 0 failures
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if (option_type == :string || option_type == :integer) && raw_option_value && raw_option_value.length < 3 return false if option_type == :integer && raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if (option_type == :string || option_type == :integer) && raw_option_value && raw_option_value.length < 3 return false if option_type == :integer && raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) return false if (option_type == :string || option_type == :integer) && raw_option_value && raw_option_value.length < 3 return false if option_type == :integer && raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! 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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string return (Integer(raw_option_value[2..-1])) if option_type == :integer end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! 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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string return (Integer(raw_option_value[2..-1])) if option_type == :integer end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! 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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string return (Integer(raw_option_value[2..-1])) if option_type == :integer end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions … ! def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string return false if raw_option_value && raw_option_value.length < 3 when :integer return false if raw_option_value && raw_option_value.length < 3 return false if raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end end ! … end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions … ! 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 case(option_type) when :string raw_option_value[2..-1] when :integer (Integer(raw_option_value[2..-1])) when :boolean return true if option_type == :boolean && raw_option_value end end ! … end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
How do we write small classes?
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
What are the characteristics of a small class?
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def valid? options.all?(&:valid) end ! def value (option_flag) options[option_flag].value end ! private def option (option_flag, option_type = :boolean) options[option_flag] = build_option(option_flag, option_type) end ! def build_option # Need to write this. end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def valid? options.all?(&:valid) end ! def value (option_flag) options[option_flag].value end ! private def option (option_flag, option_type = :boolean) options[option_flag] = build_option(option_flag, option_type) end ! def build_option # Need to write this. end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! def build_option # Need to write this. end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! def build_option # Need to write this. end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! def build_option # Need to write this. end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! … ! def valid? options.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! def build_option # Need to write this. end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Dependencies
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Dependencies
Command Line Options ?*
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Depend on Abstractions
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Command Line Options
String Option Integer Option
Boolean Option
valid?value
Option*
Depend on Abstractions
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Command Line Options
String Option Integer Option
Boolean Option
valid?value
Option*
This is my duck type!
Depend on Abstractions
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Command Line Options
String Option Integer Option
Boolean Option
valid?value
Option*
This is my duck type!
These are my concrete types.
Depend on Abstractions
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Command Line Options
String Option Integer Option
Boolean Option
valid?value
Option*
These are my concrete types.
Depend on this!
Depend on Abstractions
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Command Line Options
String Option Integer Option
Boolean Option
valid?value
Option*
Depend on this!
Don’t depend on these!
Depend on Abstractions
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
private def option (option_flag, option_type = :boolean) options[option_flag] = case (option_type) when :boolean return BooleanOption.new(option_flag, nil) when :string return StringOption.new(option_flag, nil) when :integer return IntegerOption.new(option_flag, nil) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
class CommandLineOptions ! … ! def build_option (option_flag, option_type) “#{option_type}_option".camelize.constantize.new( option_flag, raw_value_for_option(option_flag) ) end ! … !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass Option ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? end ! def value end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass BooleanOption < Option ! def valid? true end ! def value raw_value end end
class Option ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! def valid? end ! def value end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass StringOption < Option ! def valid? !! end ! def value !! end end
class IntegerOption < Option ! def valid? !! end ! def value !! end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string !! when :integer !!! end end end
def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string return false if raw_option_value && raw_option_value.length < 3 when :integer return false if raw_option_value && raw_option_value.length < 3 return false if raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end 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 case(option_type) when :string ! when :integer ! when :boolean ! 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 case(option_type) when :string raw_option_value[2..-1] when :integer (Integer(raw_option_value[2..-1])) when :boolean return true if option_type == :boolean && raw_option_value end end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string !! when :integer !!! end 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 case(option_type) when :string ! when :integer ! when :boolean ! 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 case(option_type) when :string raw_option_value[2..-1] when :integer (Integer(raw_option_value[2..-1])) when :boolean return true if option_type == :boolean && raw_option_value end end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value !! end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value !! end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string !! when :integer !!! end 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 case(option_type) when :string ! when :integer ! when :boolean ! end end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value !! end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value return nil unless raw_value raw_value[2..-1] end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value return nil unless raw_value Integer(raw_value[2..-1]) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string !! when :integer !!! end 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 case(option_type) when :string ! when :integer ! when :boolean ! end end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value !! end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value return nil unless raw_value raw_value[2..-1] end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value return nil unless raw_value Integer(raw_value[2..-1]) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string !! when :integer !!! end 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 case(option_type) when :string ! when :integer ! when :boolean ! end end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value !! end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value return nil unless raw_value raw_value[2..-1] end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value return nil unless raw_value Integer(raw_value[2..-1]) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
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 case(option_type) when :string ! when :integer ! when :boolean ! end end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
def valid? options.all?(&:valid?) end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value !! end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value return nil unless raw_value raw_value[2..-1] end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value return nil unless raw_value Integer(raw_value[2..-1]) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
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 case(option_type) when :string ! when :integer ! when :boolean ! end end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
def valid? options.all?(&:valid?) end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value !! end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value return nil unless raw_value raw_value[2..-1] end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value return nil unless raw_value Integer(raw_value[2..-1]) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
def value (option_flag) options[option_flag].value end
class StringOption < Option ! def valid? !! end ! def value !! end end !class IntegerOption < Option ! def valid? !!! end ! def value !! end end
def valid? options.all?(&:valid?) end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value !! end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value !! end end
class StringOption < Option ! def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! def value return nil unless raw_value raw_value[2..-1] end end !class IntegerOption < Option ! def valid? return true unless raw_value (raw_value && raw_value.length > 2) && (Integer(raw_value[2..-1]) rescue false) end ! def value return nil unless raw_value Integer(raw_value[2..-1]) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeOption classes Option stores it's flag stores it's raw value BooleanOption is true if the raw value is present is false if the raw value is nil is valid StringOption invalid when there is no content is valid if there is content is valid if raw value is nil can return the value value is nil if raw value is nil IntegerOption is invalid without content is invalid if the content is not an integer is valid if there is content and it's an integer is valid if raw value is nil can return the value returns nil if raw value is nil!Finished in 0.00495 seconds22 examples, 0 failures, 6 pending
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
How do we isolate abstractions?
Separate the “what” from the “how”.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string return false if raw_option_value && raw_option_value.length < 3 when :integer return false if raw_option_value && raw_option_value.length < 3 return false if raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
def valid? options.values.all?(&:valid?) end
def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string return false if raw_option_value && raw_option_value.length < 3 when :integer return false if raw_option_value && raw_option_value.length < 3 return false if raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
def valid? options.values.all?(&:valid?) end
def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string return false if raw_option_value && raw_option_value.length < 3 when :integer return false if raw_option_value && raw_option_value.length < 3 return false if raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end end
This all about HOW!
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
def valid? options.values.all?(&:valid?) end
def valid? options.each do |option_flag, option_type| raw_option_value = raw_value_for_option(option_flag) ! case(option_type) when :string return false if raw_option_value && raw_option_value.length < 3 when :integer return false if raw_option_value && raw_option_value.length < 3 return false if raw_option_value && !(Integer(raw_option_value[2..-1]) rescue false) end end end
This all about HOW!
This is WHAT I want done.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string return (Integer(raw_option_value[2..-1])) if option_type == :integer end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
def value (option_flag) options[option_flag].value 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 true if option_type == :boolean && raw_option_value return raw_option_value[2..-1] if option_type == :string return (Integer(raw_option_value[2..-1])) if option_type == :integer end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def valid? options.values.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! private ! def option (option_flag, option_type = :boolean) options[option_flag] = build_option(option_flag, option_type) end ! def build_option (option_flag, option_type) "#{option_type}_option".camelize.constantize.new(option_flag, raw_value_for_option(option_flag)) end ! def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /^-#{option_flag}/ } end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def valid? options.values.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! private ! def option (option_flag, option_type = :boolean) options[option_flag] = build_option(option_flag, option_type) end ! def build_option (option_flag, option_type) "#{option_type}_option".camelize.constantize.new(option_flag, raw_value_for_option(option_flag)) end ! def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /^-#{option_flag}/ } end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeclass CommandLineOptions ! attr_accessor :argv attr_reader :options ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! def valid? options.values.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! private ! def option (option_flag, option_type = :boolean) options[option_flag] = build_option(option_flag, option_type) end ! def build_option (option_flag, option_type) "#{option_type}_option".camelize.constantize.new(option_flag, raw_value_for_option(option_flag)) end ! def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /^-#{option_flag}/ } end !end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode!
def valid? options.values.all?(&:valid?) end !
def value (option_flag) options[option_flag].value end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
CommandLineOptions builds an option object for each defined option is valid if all options are valid is invalid if any option is invalid option object conventions uses a StringOption for string options uses a BooleanOption for boolean options uses an IntegerOption for integer options
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Then
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
some_ruby_program -v -efoo -i100 -afoo,bar,baz
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode describe "array options" do it "can return the value as an array" do options = CommandLineOptions.new([ "-afoo,bar,baz" ]) { option :a, :array } expect(options.value(:a)).to eq(["foo", "bar", "baz"]) end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode describe "array options" do it "can return the value as an array" do options = CommandLineOptions.new([ "-afoo,bar,baz" ]) { option :a, :array } expect(options.value(:a)).to eq(["foo", "bar", "baz"]) end end
class ArrayOption < OptionWithContent def value return nil if option_unset? extract_value_from_raw_value.split(",") end end
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcodeCommandLineOptions 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 array options can return the value as an array!OptionWithContent has a flag is valid when it has no raw value is valid when it has a value can return it's value when present returns nil if the flag has no raw value
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Now We’re Done!!Let them implement their own
option classes. It’s easy.
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
http://www.enablelabs.com/
866-895-8189
@mark_menard
Mark Menard Enable Labs
GitHub: MarkMenard
Refactoring to Small Code 16x9 - April 24, 2014
Enable Labs @mark_menard
#smallcode
Credits• Syntax highlighting: pbpaste | highlight --syntax=rb --style=edit-xcode --out-format=rtf | pbcopy!
• Command line option example inspiration Uncle Bob.
Refactoring to Small Code 16x9 - April 24, 2014