Writing small code is hard. You know you should, but how do you actually do it? 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 classes small. We'll identify abstractions yearning to be free of their big object cages. In the process we'll also see how basic patterns such as composition, delegation and dependency injection emerge from using small objects. We'll even write some tests too.
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Write Small Things (Code)
1. Write Small Things
Mark Menard
@mark_menard
Enable Labs
Los Angeles Ruby Conf 2014
@mark_menard
Enable Labs
1
WiteSmallThings - February 8, 2014
2. Who is Mark Menard
Husband of Sylva!
Father of Ezra and Avi!
From and Resides inTroy, NY!
Owner of Enable Labs a boutique consultancy doing Web and Mobile
Development!
Has done for about five years!
Doing software development for a long time
@mark_menard
Enable Labs
2
WiteSmallThings - February 8, 2014
3. ‘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
@mark_menard
Enable Labs
3
WiteSmallThings - February 8, 2014
5. What is meant by small?
@mark_menard
Enable Labs
5
WiteSmallThings - February 8, 2014
6. It’s not about
total line count.
@mark_menard
Enable Labs
6
WiteSmallThings - February 8, 2014
7. It’s not about
method count.
@mark_menard
Enable Labs
7
WiteSmallThings - February 8, 2014
8. It’s not about
class count.
@mark_menard
Enable Labs
8
WiteSmallThings - February 8, 2014
9. So, what do I mean by small things?
Small methods!
Small classes
@mark_menard
Enable Labs
9
WiteSmallThings - February 8, 2014
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
@mark_menard
Enable Labs
10
WiteSmallThings - February 8, 2014
11. What are the challenges of small
code?
•
•
Dependency Management!
Context Management
@mark_menard
Enable Labs
11
WiteSmallThings - February 8, 2014
12. The goal: Small units of
understandable code that
are amenable to change.
@mark_menard
Enable Labs
12
WiteSmallThings - February 8, 2014
13. Our Three Main Tools
•
•
•
Extract Method!
Extract Class!
Composed Method
@mark_menard
Enable Labs
13
WiteSmallThings - February 8, 2014
15. “The object programs that live
best and longest are those with
short methods.”!
! ! ! ! ! ! -Refactoring by Fields, Harvey, Fowler, Black
@mark_menard
Enable Labs
15
WiteSmallThings - February 8, 2014
16. The first rule of methods:
@mark_menard
Enable Labs
16
WiteSmallThings - February 8, 2014
17. Do one thing. Do it well. Do only
that thing.
@mark_menard
Enable Labs
17
WiteSmallThings - February 8, 2014
22. Let’s Build a Command Line
Option Library
@mark_menard
Enable Labs
22
WiteSmallThings - February 8, 2014
23. 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
Enable Labs
!23
23
@mark_menard
WiteSmallThings - February 8, 2014
24. 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
Enable Labs
!24
24
@mark_menard
WiteSmallThings - February 8, 2014
25. CommandLineOptions
options
are true if present
are false if absent
!
Finished in 0.00206 seconds
2 examples, 2 failures
Enable Labs
!25
25
@mark_menard
WiteSmallThings - February 8, 2014
26. class CommandLineOptions
!
attr_accessor :argv
attr_reader :options
!
def initialize (argv = [], &block)
@options = []
@argv = argv
instance_eval &block
end
!
def has (option_flag)
options.include?(option_flag) && argv.include?("-#{option_flag}")
end
!
options = CommandLineOptions.new(ARGV) do
def option (option_flag)
option :c
options << option_flag
option :v
end
option :e
!
end
end
Enable Labs
!26
26
@mark_menard
WiteSmallThings - February 8, 2014
27. CommandLineOptions
options
are true if present
are false if absent
!
Finished in 0.00206 seconds
2 examples, 0 failures
Enable Labs
!27
27
@mark_menard
WiteSmallThings - February 8, 2014
29. # some_ruby_program -v -efoo
!
options = CommandLineOptions.new(ARGV) do
option :v
option :e, :string
end
!
if options.has(:v)
# Do something
end
!
if options.has(:e)
some_value = options.value(:e)
# Do something with the expression
end
Enable Labs
!29
29
@mark_menard
WiteSmallThings - February 8, 2014
30. describe CommandLineOptions do
!
describe "boolean options" do
let(:options) { CommandLineOptions.new { option :c } }
it "are true if present" do …
it "are false if absent" do …
end
!
describe "string options" do
let(:options) { CommandLineOptions.new { option :e, :string } }
it "must have content" do …
it "can return the value" do …
it "return nil if not in argv" do …
end
end
Enable Labs
!30
30
@mark_menard
WiteSmallThings - February 8, 2014
31. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content (FAILED - 1)
can return the value (FAILED - 2)
return nil if not in argv (FAILED - 3)
!
Failures:
!
!
!
!
1) CommandLineOptions string options must have content
Failure/Error: expect(options.valid?).to be_false
NoMethodError:
undefined method `valid?' for #<CommandLineOptions:0x00000102cc8fa0>
# ./spec/command_line_options_spec.rb:26:in `block (3 levels) in <top (required)>'
2) CommandLineOptions string options can return the value
Failure/Error: expect(options.value(:e)).to eq("foo")
NoMethodError:
undefined method `value' for #<CommandLineOptions:0x00000102cca1c0>
# ./spec/command_line_options_spec.rb:31:in `block (3 levels) in <top (required)>'
3) CommandLineOptions string options return nil if not in argv
Failure/Error: expect(options.value(:e)).to be_nil
NoMethodError:
undefined method `value' for #<CommandLineOptions:0x00000102cc2a38>
# ./spec/command_line_options_spec.rb:35:in `block (3 levels) in <top (required)>'
Finished in 0.00205 seconds
5 examples, 3 failures
Enable Labs
!31
31
@mark_menard
WiteSmallThings - February 8, 2014
32. def valid?
options.each do |option_flag, option_type|
raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ }
return false if raw_option_value.length < 3 && option_type == :string
end
end
Enable Labs
!32
32
@mark_menard
WiteSmallThings - February 8, 2014
33. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
can return the value (FAILED - 1)
return nil if not in argv (FAILED - 2)
!
Failures:
!
1) CommandLineOptions string options can return the value
Failure/Error: expect(options.value(:e)).to eq("foo")
NoMethodError:
undefined method `value' for #<CommandLineOptions:0x00000101cbb6f8>
# ./spec/command_line_options_spec.rb:31:in `block (3 levels) in <top (required)>'
!
2) CommandLineOptions string options return nil if not in argv
Failure/Error: expect(options.value(:e)).to be_nil
NoMethodError:
undefined method `value' for #<CommandLineOptions:0x00000101cba2a8>
# ./spec/command_line_options_spec.rb:35:in `block (3 levels) in <top (required)>'
!
Finished in 0.00206 seconds
5 examples, 2 failures
Enable Labs
!33
33
@mark_menard
WiteSmallThings - February 8, 2014
34. 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 raw_option_value[2..-1] if option_type == :string
end
Enable Labs
!34
34
@mark_menard
WiteSmallThings - February 8, 2014
35. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
can return the value
return nil if not in argv
!
Finished in 0.00236 seconds
5 examples, 0 failures
Enable Labs
!35
35
@mark_menard
WiteSmallThings - February 8, 2014
36. def valid?
options.each do |option_flag, option_type|
raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ }
return false if raw_option_value.length < 3 && option_type == :string
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 raw_option_value[2..-1] if option_type == :string
end
Enable Labs
!36
36
@mark_menard
WiteSmallThings - February 8, 2014
37. Extract Method Refactoring
def print_invoice_for_amount (amount)
print_header
puts "Name: #{@name}"
puts "Amount: #{amount}"
end
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
Move this to here
High level of abstraction
Low level of abstraction
@mark_menard
Enable Labs
37
WiteSmallThings - February 8, 2014
38. !
!
def valid?
options.each do |option_flag, option_type|
return false if option_type == :string && raw_value_for_option(option_flag).length < 3
end
end
def value (option_flag)
raw_option_value = raw_value_for_option(option_flag)
return nil unless raw_option_value
option_type = options[option_flag]
return raw_option_value[2..-1] if option_type == :string
end
private def raw_value_for_option (option_flag)
argv.find { |arg| arg =~ /-#{option_flag}/ }
end
Enable Labs
!38
38
@mark_menard
WiteSmallThings - February 8, 2014
39. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
can return the value
return nil if not in argv
!
Finished in 0.00236 seconds
5 examples, 0 failures
Enable Labs
!39
39
@mark_menard
WiteSmallThings - February 8, 2014
40. class CommandLineOptions
!
!
!
!
!
!
!
attr_accessor :argv
attr_reader :options
def initialize (argv = [], &block)
@options = {}
@argv = argv
instance_eval &block
end
def has (option_flag)
options.include?(option_flag) && argv.include?("-#{option_flag}")
end
def option (option_flag, option_type = :boolean)
options[option_flag] = option_type
end
def valid?
options.each do |option_flag, option_type|
return false if option_type == :string && raw_value_for_option(option_flag).length < 3
end
end
def value (option_flag)
raw_option_value = raw_value_for_option(option_flag)
return nil unless raw_option_value
option_type = options[option_flag]
return raw_option_value[2..-1] if option_type == :string
end
private def raw_value_for_option (option_flag)
argv.find { |arg| arg =~ /-#{option_flag}/ }
end
end
Enable Labs
!40
40
@mark_menard
WiteSmallThings - February 8, 2014
41. !
!
def valid?
options.each do |option_flag, option_type|
return false if option_type == :string && raw_value_for_option(option_flag).length < 3
end
end
def value (option_flag)
raw_option_value = raw_value_for_option(option_flag)
return nil unless raw_option_value
option_type = options[option_flag]
return raw_option_value[2..-1] if option_type == :string
end
private def raw_value_for_option (option_flag)
argv.find { |arg| arg =~ /-#{option_flag}/ }
end
Enable Labs
!41
41
@mark_menard
WiteSmallThings - February 8, 2014
44. describe "integer options" do
it "must have content"
it "must be an integer"
it "can return the value as an integer"
it "returns nil if not in argv"
end
Enable Labs
!44
44
@mark_menard
WiteSmallThings - February 8, 2014
45. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
can return the value
return nil if not in argv
integer options
must have content (PENDING: No reason given)
must be an integer (PENDING: Not yet implemented)
can return the value as an integer (PENDING: Not yet implemented)
returns nil if not in argv (PENDING: Not yet implemented)
Enable Labs
!45
45
@mark_menard
WiteSmallThings - February 8, 2014
46. let(:options) { CommandLineOptions.new { option :i, :integer } }
!
it "must have content" do
options.argv = [ "-i" ]
expect(options.valid?).to be_false
end
Enable Labs
!46
46
@mark_menard
WiteSmallThings - February 8, 2014
47. def valid?
options.each do |option_flag, option_type|
return false if option_type == :string &&
raw_value_for_option(option_flag).length < 3
end
end
Enable Labs
!47
47
@mark_menard
WiteSmallThings - February 8, 2014
48. def valid?
options.each do |option_flag, option_type|
return false if (option_type == :string || option_type == :integer)&&
raw_value_for_option(option_flag).length < 3
end
end
Enable Labs
!48
48
@mark_menard
WiteSmallThings - February 8, 2014
49. it "must be an integer" do
options.argv = [ "-inot_an_integer" ]
expect(options.valid?).to be_false
end
Enable Labs
!49
49
@mark_menard
WiteSmallThings - February 8, 2014
50. def valid?
options.each do |option_flag, option_type|
return false if (option_type == :string || option_type == :integer) &&
raw_value_for_option(option_flag).length < 3
return false unless option_type == :integer &&
(Integer(raw_value_for_option(option_flag)[2..-1]) rescue false)
end
end
Enable Labs
!50
50
@mark_menard
WiteSmallThings - February 8, 2014
51. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
can return the value
return nil if not in argv
integer options
must have content
must be an integer
can return the value as an integer (PENDING: Not yet implemented)
returns nil if not in argv (PENDING: Not yet implemented)
Enable Labs
!51
51
@mark_menard
WiteSmallThings - February 8, 2014
52. class CommandLineOptions
!
!
!
!
!
!
!
!
attr_accessor :argv
attr_reader :options
def initialize (argv = [], &block)
@options = {}
@argv = argv
instance_eval &block
end
def has (option_flag)
options.include?(option_flag) && argv.include?("-#{option_flag}")
end
def option (option_flag, option_type = :boolean)
options[option_flag] = option_type
end
def valid?
options.each do |option_flag, option_type|
return false if (option_type == :string || option_type == :integer) &&
raw_value_for_option(option_flag).length < 3
return false unless option_type == :integer &&
(Integer(raw_value_for_option(option_flag)[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 raw_option_value[2..-1] if option_type == :string
end
private def raw_value_for_option (option_flag)
argv.find { |arg| arg =~ /-#{option_flag}/ }
end
end
Enable Labs
!52
52
@mark_menard
WiteSmallThings - February 8, 2014
53. def valid?
options.each do |option_flag, option_type|
return false if (option_type == :string || option_type == :integer) &&
raw_value_for_option(option_flag).length < 3
return false unless option_type == :integer &&
(Integer(raw_value_for_option(option_flag)[2..-1]) rescue false)
end
end
Enable Labs
!54
54
@mark_menard
WiteSmallThings - February 8, 2014
55. !
def valid?
options.each do |option_flag, option_type|
case(option_type)
when :string
return false if raw_value_for_option(option_flag).length < 3
when :integer
return false if raw_value_for_option(option_flag).length < 3
return false unless (Integer(raw_value_for_option(option_flag)[2..-1]) rescue false)
end
end
end
Enable Labs
!56
56
@mark_menard
WiteSmallThings - February 8, 2014
56. def valid?
options.each do |option_flag, option_type|
case(option_type)
when :string
return false if raw_value_for_option(option_flag).length < 3
when :integer
return false if raw_value_for_option(option_flag).length < 3
return false unless (Integer(raw_value_for_option(option_flag)[2..-1]) rescue false)
when :boolean
end
end
end
Enable Labs
!57
57
@mark_menard
WiteSmallThings - February 8, 2014
57. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
can return the value
return nil if not in argv
integer options
must have content
must be an integer
can return the value as an integer (PENDING: Not yet implemented)
returns nil if not in argv (PENDING: Not yet implemented)
Enable Labs
!58
58
@mark_menard
WiteSmallThings - February 8, 2014
58. def valid?
options.each do |option_flag, option_type|
case(option_type)
when :string
return false unless string_option_valid?(raw_value_for_option(option_flag))
when :integer
return false unless integer_option_valid?(raw_value_for_option(option_flag))
when :boolean
end
end
end
!
private def string_option_valid? (raw_value)
extract_value_from_raw_value(raw_value).length > 0
end
!
private def integer_option_valid? (raw_value)
extract_value_from_raw_value(raw_value).length > 0 &&
(Integer(extract_value_from_raw_value(raw_value)) rescue false)
end
Enable Labs
!59
59
@mark_menard
WiteSmallThings - February 8, 2014
60. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
can return the value
return nil if not in argv
integer options
must have content
must be an integer
can return the value as an integer (PENDING: Not yet implemented)
returns nil if not in argv (PENDING: Not yet implemented)
Enable Labs
!61
61
@mark_menard
WiteSmallThings - February 8, 2014
61. def valid?
options.each do |option_flag, option_type|
return false unless send("#{option_type}_option_valid?", raw_value_for_option(option_flag))
end
end
Enable Labs
!62
62
@mark_menard
WiteSmallThings - February 8, 2014
62. def valid?
options.each do |option_flag, option_type|
return false unless option_valid?(option_type, raw_value_for_option(option_flag))
end
end
!
private def option_valid? (option_type, raw_value)
send("#{option_type}_option_valid?", raw_value)
end
Enable Labs
!63
63
@mark_menard
WiteSmallThings - February 8, 2014
63. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
is valid when there is content
can return the value
return nil if not in argv
integer options
must have content
must be an integer
can return the value as an integer
returns nil if not in argv (PENDING: Not yet implemented)
!
Pending:
CommandLineOptions integer options returns nil if not in argv
# Not yet implemented
# ./spec/command_line_options_spec.rb:61
!
Finished in 0.00291 seconds
10 examples, 0 failures, 1 pending
Enable Labs
!64
64
@mark_menard
WiteSmallThings - February 8, 2014
64. !
!
class CommandLineOptions
!
!
!
!
!
!
!
!
!
!
!
attr_accessor :argv
attr_reader :options
def initialize (argv = [], &block)
@options = {}
@argv = argv
instance_eval &block
end
def has (option_flag)
options.include?(option_flag) && argv.include?("-#{option_flag}")
end
def valid?
options.each do |option_flag, option_type|
return false unless option_valid?(option_type, raw_value_for_option(option_flag))
end
end
def value (option_flag)
raw_option_value = raw_value_for_option(option_flag)
return nil unless raw_option_value
option_type = options[option_flag]
return extract_value_from_raw_value(raw_option_value) if option_type == :string
end
private def option (option_flag, option_type = :boolean)
options[option_flag] = option_type
end
private def option_valid? (option_type, raw_value)
send("#{option_type}_option_valid?", raw_value)
end
private def string_option_valid? (raw_value)
extract_value_from_raw_value(raw_value).length > 0
end
private def integer_option_valid? (raw_value)
extract_value_from_raw_value(raw_value).length > 0 && (Integer(extract_value_from_raw_value(raw_value)) rescue false)
end
private def boolean_option_valid? (raw_value)
true
end
private def extract_value_from_raw_value (raw_value)
raw_value[2..-1]
end
private def raw_value_for_option (option_flag)
argv.find { |arg| arg =~ /-#{option_flag}/ }
end
end
Enable Labs
!65
65
@mark_menard
WiteSmallThings - February 8, 2014
65. def has (option_flag)
options.include?(option_flag) && argv.include?("-#{option_flag}")
end
!
def valid?
options.each do |option_flag, option_type|
return false unless option_valid?(option_type, raw_value_for_option(option_flag))
end
end
!
def value (option_flag)
raw_option_value = raw_value_for_option(option_flag)
return nil unless raw_option_value
option_type = options[option_flag]
return extract_value_from_raw_value(raw_option_value) if option_type == :string
end
Enable Labs
!66
66
@mark_menard
WiteSmallThings - February 8, 2014
67. 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
@mark_menard
Enable Labs
68
WiteSmallThings - February 8, 2014
68. 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!
Dependencies are injected
@mark_menard
Enable Labs
69
WiteSmallThings - February 8, 2014
69. describe StringOption do
let(:string_option) { StringOption.new('s', '-sfoo') }
!
it
it
it
it
end
"has a flag"
"is valid when it has a value"
"can return it's value when present"
"returns nil if the flag has no raw value"
Enable Labs
!70
70
@mark_menard
WiteSmallThings - February 8, 2014
70. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
is valid when there is content
can return the value
return nil if not in argv
integer options
must have content
must be an integer
can return the value as an integer
returns nil if not in argv (PENDING: Not yet implemented)
!
StringOption
has a flag (PENDING: No reason given)
is valid when it has a value (PENDING: Not yet implemented)
can return it's value when present (PENDING: Not yet implemented)
returns nil if the flag has no raw value (PENDING: Not yet implemented)
Enable Labs
!71
71
@mark_menard
WiteSmallThings - February 8, 2014
71. class StringOption
!
attr_reader :flag
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
end
!
end
Enable Labs
!72
72
@mark_menard
WiteSmallThings - February 8, 2014
72. it "is valid when it has a value" do
expect(string_option.valid?).to be_true
end
Enable Labs
!73
73
@mark_menard
WiteSmallThings - February 8, 2014
73. class StringOption
class CommandLineOptions
!
!
!
private def string_option_valid? (raw_value)
extract_value_from_raw_value(raw_value).length > 0
end
!
end
!
!
!
attr_reader :flag, :raw_value
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
end
def valid?
extract_value_from_raw_value.length > 0
end
private def extract_value_from_raw_value
raw_value[2..-1]
end
end
Enable Labs
!74
74
@mark_menard
WiteSmallThings - February 8, 2014
74. class StringOption
!
attr_reader :flag, :raw_value
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
end
!
def valid?
extract_value_from_raw_value.length > 0
end
!
private def extract_value_from_raw_value
raw_value[2..-1]
end
!
end
Enable Labs
!75
75
@mark_menard
WiteSmallThings - February 8, 2014
75. private def string_option_valid? (raw_value)
StringOption.new("", raw_value).valid?
end
Enable Labs
!76
76
@mark_menard
WiteSmallThings - February 8, 2014
76. class IntegerOption
!
attr_reader :flag, :raw_value
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
end
!
def valid?
extract_value_from_raw_value.length > 0 && real_value_is_integer?
end
!
private def extract_value_from_raw_value
raw_value[2..-1]
end
!
private def real_value_is_integer?
(Integer(extract_value_from_raw_value) rescue false)
end
!
end
Enable Labs
!77
77
@mark_menard
WiteSmallThings - February 8, 2014
77. class StringOption
!
attr_reader :flag, :raw_value
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
end
!
def valid?
extract_value_from_raw_value.length > 0
end
!
private def extract_value_from_raw_value
raw_value[2..-1]
end
!
end
Enable Labs
!78
78
@mark_menard
WiteSmallThings - February 8, 2014
78. class OptionWithContent
!
attr_reader :flag, :raw_value
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
end
!
def valid?
extract_value_from_raw_value.length > 0
end
!
private def extract_value_from_raw_value
raw_value[2..-1]
end
!
end
Enable Labs
!79
79
@mark_menard
WiteSmallThings - February 8, 2014
79. class StringOption < OptionWithContent
end
!
class IntegerOption < OptionWithContent
!
def valid?
super && real_value_is_integer?
end
!
private def real_value_is_integer?
(Integer(extract_value_from_raw_value) rescue false)
end
!
end
Enable Labs
!80
80
@mark_menard
WiteSmallThings - February 8, 2014
80. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content
is valid when there is content
can return the value
return nil if not in argv
integer options
must have content
must be an integer
can return the value as an integer
returns nil if not in argv (PENDING: Not yet implemented)
!
StringOption
has a flag
is valid when it has a value
can return it's value when present (PENDING: Not yet implemented)
returns nil if the flag has no raw value (PENDING: Not yet implemented)
Enable Labs
!81
81
@mark_menard
WiteSmallThings - February 8, 2014
82. How do we deal with
Dependencies?
•
•
Dependency Injection!
Depend on Abstractions
@mark_menard
Enable Labs
82
WiteSmallThings - February 8, 2014
83. private def option (option_flag, option_type = :boolean)
options[option_flag] = option_type
end
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
private def option (option_flag, option_type = :boolean)
option_class = "#{option_type}_option".camelize.constantize
options[option_flag] = option_class.new(option_flag, nil)
end
Enable Labs
!84
84
@mark_menard
WiteSmallThings - February 8, 2014
84. How do we isolate abstractions?
Separate the “what”
from the “how”.
@mark_menard
Enable Labs
85
WiteSmallThings - February 8, 2014
85. CommandLineOptions
boolean options
are true if present
are false if absent
string options
must have content (FAILED - 1)
is valid when there is content (FAILED - 2)
can return the value (FAILED - 3)
return nil if not in argv
integer options
must have content (FAILED - 4)
must be an integer (FAILED - 5)
can return the value as an integer
returns nil if not in argv (PENDING: Not yet implemented)
!
StringOption
has a flag
is valid when it has a value
can return it's value when present (PENDING: Not yet implemented)
returns nil if the flag has no raw value (PENDING: Not yet implemented)
Enable Labs
!86
86
@mark_menard
WiteSmallThings - February 8, 2014
86. def valid?
options.each do |option_flag, option_type|
return false unless option_valid?(option_type, raw_value_for_option(option_flag))
end
end
def valid?
options.values.all?(&:valid?)
end
Enable Labs
!87
87
@mark_menard
WiteSmallThings - February 8, 2014
87. 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 (FAILED - 1)
return nil if not in argv
integer options
must have content
must be an integer
can return the value as an integer
returns nil if not in argv (PENDING: Not yet implemented)
!
StringOption
has a flag
is valid when it has a value
can return it's value when present (PENDING: Not yet implemented)
returns nil if the flag has no raw value (PENDING: Not yet implemented)
Enable Labs
!88
88
@mark_menard
WiteSmallThings - February 8, 2014
88. 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 raw_option_value[2..-1] if option_type == :string
end
def value (option_flag)
options[option_flag].value
end
Enable Labs
!89
89
@mark_menard
WiteSmallThings - February 8, 2014
89. 1) CommandLineOptions string options can return the value
Failure/Error: expect(options.value(:e)).to eq("foo")
NoMethodError:
undefined method `value' for #<StringOption:0x00000101b85b30 @flag=:e, @raw_value="-efoo">
# ./lib/command_line_options.rb:26:in `value'
# ./spec/command_line_options_spec.rb:36:in `block (3 levels) in <top (required)>'
Enable Labs
!90
90
@mark_menard
WiteSmallThings - February 8, 2014
90. # OptionWithContent
def value
return nil if option_unset?
valid? ? extract_value_from_raw_value : nil
end
# IntegerOption
def value
return if option_unset?
Integer(extract_value_from_raw_value)
end
Enable Labs
!91
91
@mark_menard
WiteSmallThings - February 8, 2014
91. 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
!
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
!92
92
@mark_menard
WiteSmallThings - February 8, 2014
97. 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
!98
98
@mark_menard
WiteSmallThings - February 8, 2014
98. 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
!99
99
@mark_menard
WiteSmallThings - February 8, 2014