small code - ruby on ales 2014

91
Enable Labs @mark_menard Small Code Mark Menard Ruby on Ales 2014 @mark_menard Enable Labs

Upload: mark-menard

Post on 19-Jan-2015

453 views

Category:

Technology


1 download

DESCRIPTION

This is my presentation from Ruby on Ales - March 2014 - Bend, OR To paraphrase Mark Twain, "I didn't have time to write some small classes, so I wrote a BIG ONE instead." Now what do you do? Refactor! In this talk we'll refactor a large class into a series of smaller classes. We'll learn techniques to identify buried abstractions, what to extract, what to leave behind, and why delegation, composition and dependency inversion are key to writing small things that are easier to test.

TRANSCRIPT

Page 1: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Small Code

Mark Menard

Ruby on Ales 2014

@mark_menard !Enable Labs

Page 2: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

‘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

Page 3: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Introduction

Page 4: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

What do I mean by small?

Page 5: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

It’s not about total line count.

Page 6: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

It’s not about method count.

Page 7: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

It’s not about class

count.

Page 8: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

So, what do I mean by small?

Small methods! Small classes

Page 9: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Why should we strive for small code?

• We don’t know what the future will bring!• Raise the level of abstraction!• Create composable components!• Prefer delegation over inheritance

Page 10: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Why should we strive for small code?

• We don’t know what the future will bring!• Raise the level of abstraction!• Create composable components!• Prefer delegation over inheritance

Enable Future Change

Page 11: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

What are the challenges of small code?

•Dependency Management!•Context Management

Page 12: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

The goal: Small units of understandable code that are amenable to change.

Page 13: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Our Primary Tools

• Extract Method!

• Move Method!

• Extract Class

Page 14: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Let’s Look at Some Code

Page 15: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!14

% some_ruby_program -v -sfoo

Page 16: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!15

options = CommandLineOptions.new(ARGV) do option :v option :s, :string end

Page 17: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!16

if options.valid? if options.value(:v) # Do something end ! if (s_option = options.value(:s)) # Do something end end

Page 18: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!16

if options.valid? if options.value(:v) # Do something end ! if (s_option = options.value(:s)) # Do something end end

Page 19: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!16

if options.valid? if options.value(:v) # Do something end ! if (s_option = options.value(:s)) # Do something end end

Page 20: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!17

# 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

Page 21: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!18

class 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

Page 22: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!19

class CommandLineOptions ! … def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! … !end

# In some_ruby_program options = CommandLineOptions.new(ARGV) do option :v option :s, :string end

Page 23: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!20

class CommandLineOptions ! … def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! … !end # In some_ruby_program

options = CommandLineOptions.new(ARGV) do option :v option :s, :string end

Page 24: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!21

class 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

Page 25: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!22

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

Page 26: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!23

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

Page 27: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Methods

Page 28: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

“The object programs that live best and longest are those with

short methods.”!! ! ! ! ! ! -Refactoring by Fields, Harvey, Fowler, Black

Page 29: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

The first rule of methods:

Page 30: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Do one thing. Do it well. Do only that thing.

Page 31: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

One level of abstraction per method.

Page 32: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Use Descriptive Names

Page 33: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

The fewer arguments the better.

Page 34: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Separate Queries from Commands

Page 35: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Don’t Repeat Yourself

Page 36: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!33

class 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

Page 37: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!33

class 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

Page 38: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!33

class 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

Page 39: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!33

class 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

Page 40: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Extract Method Refactoringdef print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end

Page 41: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Extract Method Refactoringdef print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end

High level of abstraction

Page 42: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Extract Method Refactoringdef print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end

Low level of abstraction

Page 43: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Extract Method Refactoringdef 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

Page 44: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Extract Method Refactoringdef 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

Page 45: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Extract Method Refactoringdef 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

Page 46: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

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 && 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

!35

Page 47: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

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 && 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

!35

Page 48: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

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 && 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

!35

Page 49: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

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 && 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 ! … !end

!36

Page 50: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

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

!37

Page 51: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!38

# 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

Page 52: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!39

class 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

Page 53: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!40

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 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

Page 54: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!41

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

Page 55: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!41

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

Page 56: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!41

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

Page 57: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!42

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

Page 58: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!42

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

Page 59: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!42

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

Page 60: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!43

class 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

Page 61: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!44

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 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

Page 62: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

How do we write small classes?

• Write small methods!

• Talk to the class!

• Find a good name!

• Isolate Responsibilities!

• Find cohesive sets of variables/properties!

• Extract Class!

• Move method

Page 63: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

What are the characteristics of a well designed small class?

• Single responsibility!

• Cohesive properties!

• Small public interface (preferably a handful of methods at the most)!

• Implements a single Use Case if possible!

• Primary logic is expressed in a composed method!

Page 64: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!47

class 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

Page 65: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!48

class 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

Page 66: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Dependencies

Page 67: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

How do we deal with Dependencies?

• Dependency Inversion!

• Depend on Stable Abstractions

Page 68: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!51

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

Page 69: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!51

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

Page 70: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!52

class Option ! attr_reader :flag, :raw_value ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_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 !class BooleanOption < Option ! def valid? true end ! def value !!raw_value end end

Page 71: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!53

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

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 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

Page 72: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!53

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

class CommandLineOptions ! … ! def valid? options.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end !end

Page 73: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!54

Option 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

Page 74: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

How do we isolate abstractions?

Separate the “what” from the “how”.

Page 75: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!56

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

Page 76: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!57

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

Page 77: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

class 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

!58

Page 78: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!59

!

def valid? options.values.all?(&:valid?) end !

def value (option_flag) options[option_flag].value end

Page 79: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!60

CommandLineOptions builds an option object for each defined option (PENDING: Not yet implemented) is valid if all options are valid (PENDING: Not yet implemented) is invalid if any option is invalid (PENDING: Not yet implemented) option object conventions uses a StringOption for string options (PENDING: Not yet implemented) uses a BooleanOption for boolean options (PENDING: Not yet implemented) uses an IntegerOption for integer options (PENDING: Not yet implemented)

Page 80: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!61

describe CommandLineOptions do it "builds an option object for each defined option" do options = CommandLineOptions.new([ "-v" ]) { option :v } expect(options.options.values.size).to eq(1) end ! it "is valid if all options are valid" do options = CommandLineOptions.new([ "-sfoo" ]) { option :s, :string } expect(options.valid?).to be_true end ! it "is invalid if any option is invalid" do options = CommandLineOptions.new([ "-s" ]) { option :s, :string } expect(options.valid?).to be_false end ! describe "option object conventions" do ! it "uses a StringOption for string options" do options = CommandLineOptions.new([ "-s" ]) { option :s, :string } expect(options.options[:s].class).to eq(StringOption) end ! it "uses a BooleanOption for boolean options" do options = CommandLineOptions.new([ "-s" ]) { option :v } expect(options.options[:v].class).to eq(BooleanOption) end ! it "uses an IntegerOption for integer options" do options = CommandLineOptions.new([ "-s" ]) { option :i, :integer } expect(options.options[:i].class).to eq(IntegerOption) end end end

Page 81: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!61

describe CommandLineOptions do it "builds an option object for each defined option" do options = CommandLineOptions.new([ "-v" ]) { option :v } expect(options.options.values.size).to eq(1) end ! it "is valid if all options are valid" do options = CommandLineOptions.new([ "-sfoo" ]) { option :s, :string } expect(options.valid?).to be_true end ! it "is invalid if any option is invalid" do options = CommandLineOptions.new([ "-s" ]) { option :s, :string } expect(options.valid?).to be_false end ! describe "option object conventions" do ! it "uses a StringOption for string options" do options = CommandLineOptions.new([ "-s" ]) { option :s, :string } expect(options.options[:s].class).to eq(StringOption) end ! it "uses a BooleanOption for boolean options" do options = CommandLineOptions.new([ "-s" ]) { option :v } expect(options.options[:v].class).to eq(BooleanOption) end ! it "uses an IntegerOption for integer options" do options = CommandLineOptions.new([ "-s" ]) { option :i, :integer } expect(options.options[:i].class).to eq(IntegerOption) end end end

Page 82: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!61

describe CommandLineOptions do it "builds an option object for each defined option" do options = CommandLineOptions.new([ "-v" ]) { option :v } expect(options.options.values.size).to eq(1) end ! it "is valid if all options are valid" do options = CommandLineOptions.new([ "-sfoo" ]) { option :s, :string } expect(options.valid?).to be_true end ! it "is invalid if any option is invalid" do options = CommandLineOptions.new([ "-s" ]) { option :s, :string } expect(options.valid?).to be_false end ! describe "option object conventions" do ! it "uses a StringOption for string options" do options = CommandLineOptions.new([ "-s" ]) { option :s, :string } expect(options.options[:s].class).to eq(StringOption) end ! it "uses a BooleanOption for boolean options" do options = CommandLineOptions.new([ "-s" ]) { option :v } expect(options.options[:v].class).to eq(BooleanOption) end ! it "uses an IntegerOption for integer options" do options = CommandLineOptions.new([ "-s" ]) { option :i, :integer } expect(options.options[:i].class).to eq(IntegerOption) end end end

Page 83: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!62

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

Page 84: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Then

Page 85: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!64

some_ruby_program -v -efoo -i100 -afoo,bar,baz

Page 86: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!65

describe "array options" do it "can return the value as an array" do expect(CommandLineOptions.new([ "-afoo,bar,baz" ]) { option :a, :array }.value(:a)).to eq(["foo", "bar", "baz"]) end end

Page 87: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!65

describe "array options" do it "can return the value as an array" do expect(CommandLineOptions.new([ "-afoo,bar,baz" ]) { option :a, :array }.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

Page 88: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard!66

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 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

Page 89: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Now We’re Done!!Let them implement their own

option classes. It’s easy.

Page 90: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Credits• Syntax highlighting: pbpaste | highlight --syntax=rb --style=edit-xcode --

out-format=rtf | pbcopy!

• Command line option example inspiration Uncle Bob.

Page 91: Small Code - Ruby on Ales 2014

Enable Labs @mark_menard

Start Todayhttp://www.enablelabs.com/

[email protected]

Enable Labs@mark_menard