Small Code - Ruby on Ales 2014

766 views

Published on

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.

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
766
On SlideShare
0
From Embeds
0
Number of Embeds
16
Actions
Shares
0
Downloads
8
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Small Code - Ruby on Ales 2014

  1. 1. Small Code Ruby on Ales 2014 Mark Menard @mark_menard Enable Labs Enable Labs ! @mark_menard
  2. 2. ‘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 Enable Labs @mark_menard
  3. 3. Introduction Enable Labs @mark_menard
  4. 4. What do I mean by small? Enable Labs @mark_menard
  5. 5. It’s not about total line count. Enable Labs @mark_menard
  6. 6. It’s not about method count. Enable Labs @mark_menard
  7. 7. It’s not about class count. Enable Labs @mark_menard
  8. 8. So, what do I mean by small? Small methods! Enable Labs Small classes @mark_menard
  9. 9. 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 Labs @mark_menard
  10. 10. 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 Enable Labs @mark_menard
  11. 11. What are the challenges of small code? • Dependency Management! • Context Management Enable Labs @mark_menard
  12. 12. The goal: Small units of understandable code that are amenable to change. Enable Labs @mark_menard
  13. 13. Our Primary Tools • • • Extract Method! Move Method! Extract Class Enable Labs @mark_menard
  14. 14. Let’s Look at Some Code Enable Labs @mark_menard
  15. 15. % some_ruby_program -v -sfoo Enable Labs !14 @mark_menard
  16. 16. options = CommandLineOptions.new(ARGV) do option :v option :s, :string end Enable Labs !15 @mark_menard
  17. 17. if options.valid? if options.value(:v) # Do something end ! if (s_option = options.value(:s)) # Do something end end Enable Labs !16 @mark_menard
  18. 18. if options.valid? if options.value(:v) # Do something end ! if (s_option = options.value(:s)) # Do something end end Enable Labs !16 @mark_menard
  19. 19. if options.valid? if options.value(:v) # Do something end ! if (s_option = options.value(:s)) # Do something end end Enable Labs !16 @mark_menard
  20. 20. # 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 Enable Labs !17 @mark_menard
  21. 21. 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 Enable Labs !18 @mark_menard
  22. 22. class CommandLineOptions ! … def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block end ! … ! end Enable Labs # In some_ruby_program options = CommandLineOptions.new(ARGV) do option :v option :s, :string end !19 @mark_menard
  23. 23. class CommandLineOptions ! … def option (option_flag, option_type = :boolean) options[option_flag] = option_type end ! … ! end Enable Labs # In some_ruby_program options = CommandLineOptions.new(ARGV) do option :v option :s, :string end !20 @mark_menard
  24. 24. 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 Enable Labs !21 @mark_menard
  25. 25. 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 Enable Labs !22 @mark_menard
  26. 26. 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 seconds 7 examples, 0 failures Enable Labs !23 @mark_menard
  27. 27. Methods Enable Labs @mark_menard
  28. 28. “The object programs that live best and longest are those with short methods.”! ! ! ! ! ! ! -Refactoring by Fields, Harvey, Fowler, Black Enable Labs @mark_menard
  29. 29. The first rule of methods: Enable Labs @mark_menard
  30. 30. Do one thing. Do it well. Do only that thing. Enable Labs @mark_menard
  31. 31. One level of abstraction per method. Enable Labs @mark_menard
  32. 32. Use Descriptive Names Enable Labs @mark_menard
  33. 33. The fewer arguments the better. Enable Labs @mark_menard
  34. 34. Separate Queries from Commands Enable Labs @mark_menard
  35. 35. Don’t Repeat Yourself Enable Labs @mark_menard
  36. 36. 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 Enable Labs !33 @mark_menard
  37. 37. 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 Enable Labs !33 @mark_menard
  38. 38. 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 Enable Labs !33 @mark_menard
  39. 39. 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 Enable Labs !33 @mark_menard
  40. 40. Extract Method Refactoring def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end Enable Labs @mark_menard
  41. 41. Extract Method Refactoring def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end High level of abstraction Enable Labs @mark_menard
  42. 42. Extract Method Refactoring def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end Low level of abstraction Enable Labs @mark_menard
  43. 43. Extract Method Refactoring def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end Move this to here Enable Labs def print_invoice_for_amount (amount) print_header print_details (amount) end ! def print_details (amount) puts "Name: #{@name}" puts "Amount: #{amount}" end @mark_menard
  44. 44. Extract Method Refactoring def print_invoice_for_amount (amount) print_header puts "Name: #{@name}" puts "Amount: #{amount}" end Move this to here Enable Labs Same level of abstraction def print_invoice_for_amount (amount) print_header print_details (amount) end ! def print_details (amount) puts "Name: #{@name}" puts "Amount: #{amount}" end @mark_menard
  45. 45. 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 Enable Labs @mark_menard
  46. 46. 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 Enable Labs !35 @mark_menard
  47. 47. 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 Enable Labs !35 @mark_menard
  48. 48. 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 Enable Labs !35 @mark_menard
  49. 49. 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 Enable Labs !36 @mark_menard
  50. 50. 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 Enable Labs !37 @mark_menard
  51. 51. # 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 !38 @mark_menard
  52. 52. 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 Enable Labs !39 @mark_menard
  53. 53. 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 seconds 13 examples, 0 failures Enable Labs !40 @mark_menard
  54. 54. 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 Enable Labs !41 @mark_menard
  55. 55. 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 Enable Labs !41 @mark_menard
  56. 56. 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 Enable Labs !41 @mark_menard
  57. 57. 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 Enable Labs !42 @mark_menard
  58. 58. 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 Enable Labs !42 @mark_menard
  59. 59. 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 Enable Labs !42 @mark_menard
  60. 60. 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 Enable Labs !43 @mark_menard
  61. 61. 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 Enable Labs !44 @mark_menard
  62. 62. 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 Enable Labs @mark_menard
  63. 63. 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! Enable Labs @mark_menard
  64. 64. 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 Enable Labs !47 @mark_menard
  65. 65. 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 Enable Labs !48 @mark_menard
  66. 66. Dependencies Enable Labs @mark_menard
  67. 67. How do we deal with Dependencies? • Dependency Inversion! • Depend on Stable Abstractions Enable Labs @mark_menard
  68. 68. 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 Enable Labs !51 @mark_menard
  69. 69. 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 Enable Labs !51 @mark_menard
  70. 70. class Option ! ! ! def initialize (flag, raw_value) @flag = flag @raw_value = raw_value end ! class StringOption < Option ! ! attr_reader :flag, :raw_value end ! class IntegerOption < 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 Enable Labs ! 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 !52 @mark_menard
  71. 71. ! ! class StringOption < Option 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 return nil unless raw_value raw_value[2..-1] end end ! class ! 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 Enable Labs def valid? return true unless raw_value raw_value && raw_value.length > 2 end ! 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 !53 @mark_menard
  72. 72. class StringOption < Option ! class CommandLineOptions ! … ! def valid? options.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end ! end Enable Labs ! 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 !53 @mark_menard
  73. 73. 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 seconds 22 examples, 0 failures, 6 pending Enable Labs !54 @mark_menard
  74. 74. How do we isolate abstractions? Separate the “what” from the “how”. Enable Labs @mark_menard
  75. 75. ! 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 valid? options.values.all?(&:valid?) end Enable Labs !56 @mark_menard
  76. 76. 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 def value (option_flag) options[option_flag].value end Enable Labs !57 @mark_menard
  77. 77. 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 Enable Labs !58 @mark_menard
  78. 78. ! def valid? options.values.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end Enable Labs !59 @mark_menard
  79. 79. 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) Enable Labs !60 @mark_menard
  80. 80. 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 Enable Labs !61 @mark_menard
  81. 81. 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 Enable Labs !61 @mark_menard
  82. 82. 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 Enable Labs !61 @mark_menard
  83. 83. 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 Enable Labs !62 @mark_menard
  84. 84. Then Enable Labs @mark_menard
  85. 85. some_ruby_program -v -efoo -i100 -afoo,bar,baz Enable Labs !64 @mark_menard
  86. 86. 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 Enable Labs !65 @mark_menard
  87. 87. 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 Enable Labs !65 @mark_menard
  88. 88. 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 Enable Labs !66 @mark_menard
  89. 89. Now We’re Done!! Let them implement their own option classes. It’s easy. Enable Labs @mark_menard
  90. 90. Credits • Syntax highlighting: pbpaste | highlight --syntax=rb --style=edit-xcode -out-format=rtf | pbcopy! • Command line option example inspiration Uncle Bob. Enable Labs @mark_menard
  91. 91. Enable Labs Start Today @mark_menard http://www.enablelabs.com/ info@enablelabs.com 866-895-8189 Enable Labs @mark_menard

×