Write Small Things
Mark Menard
@mark_menard

Enable Labs

Los Angeles Ruby Conf 2014
@mark_menard

Enable Labs

1

WiteSma...
Who is Mark Menard
Husband of Sylva!
Father of Ezra and Avi!
From and Resides inTroy, NY!
Owner of Enable Labs a boutique ...
‘The great thing about writing
shitty code that “just works,” is
that it is too risky and too
expensive to change, so it l...
Introduction

@mark_menard

Enable Labs

4

WiteSmallThings - February 8, 2014
What is meant by small?

@mark_menard

Enable Labs

5

WiteSmallThings - February 8, 2014
It’s not about
total line count.

@mark_menard

Enable Labs

6

WiteSmallThings - February 8, 2014
It’s not about
method count.

@mark_menard

Enable Labs

7

WiteSmallThings - February 8, 2014
It’s not about
class count.

@mark_menard

Enable Labs

8

WiteSmallThings - February 8, 2014
So, what do I mean by small things?
Small methods!

Small classes

@mark_menard

Enable Labs

9

WiteSmallThings - Februar...
Why should we strive for small
code?
•
•
•
•

We don’t know what the future will bring!
Raise the level of abstraction!
Cr...
What are the challenges of small
code?
•
•

Dependency Management!
Context Management

@mark_menard

Enable Labs

11

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

@mark_menard

Enable Labs

12

WiteSmallThings ...
Our Three Main Tools
•
•
•

Extract Method!
Extract Class!
Composed Method

@mark_menard

Enable Labs

13

WiteSmallThings...
Methods

@mark_menard

Enable Labs

14

WiteSmallThings - February 8, 2014
“The object programs that live
best and longest are those with
short methods.”!
! ! ! ! ! ! -Refactoring by Fields, Harvey...
The first rule of methods:

@mark_menard

Enable Labs

16

WiteSmallThings - February 8, 2014
Do one thing. Do it well. Do only
that thing.

@mark_menard

Enable Labs

17

WiteSmallThings - February 8, 2014
Use Descriptive Names

@mark_menard

Enable Labs

18

WiteSmallThings - February 8, 2014
The fewer arguments the better.

@mark_menard

Enable Labs

19

WiteSmallThings - February 8, 2014
Separate Queries from Commands

@mark_menard

Enable Labs

20

WiteSmallThings - February 8, 2014
Don’t Repeat Yourself

@mark_menard

Enable Labs

21

WiteSmallThings - February 8, 2014
Let’s Build a Command Line
Option Library

@mark_menard

Enable Labs

22

WiteSmallThings - February 8, 2014
options = CommandLineOptions.new(ARGV) do
option :c
option :v
option :e
end
!

if options.has(:c)
# Do something
end
!

if...
describe CommandLineOptions do
!

describe 'options' do
!

let(:parser) { CommandLineOptions.new { option :c } }
!

it "ar...
CommandLineOptions
options
are true if present
are false if absent
!

Finished in 0.00206 seconds
2 examples, 2 failures

...
class CommandLineOptions
!
attr_accessor :argv
attr_reader :options
!
def initialize (argv = [], &block)
@options = []
@ar...
CommandLineOptions
options
are true if present
are false if absent
!

Finished in 0.00206 seconds
2 examples, 0 failures

...
Done!

@mark_menard

Enable Labs

28

WiteSmallThings - February 8, 2014
# some_ruby_program -v -efoo
!

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

if options.h...
describe CommandLineOptions do
!

describe "boolean options" do
let(:options) { CommandLineOptions.new { option :c } }
it ...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content (FAILED -...
def valid?
options.each do |option_flag, option_type|
raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ }
retu...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
can retu...
def value (option_flag)
raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ }
return nil unless raw_option_value...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
can retu...
def valid?
options.each do |option_flag, option_type|
raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ }
retu...
Extract Method Refactoring
def print_invoice_for_amount (amount)
print_header
puts "Name: #{@name}"
puts "Amount: #{amount...
!

!

def valid?
options.each do |option_flag, option_type|
return false if option_type == :string && raw_value_for_option...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
can retu...
class CommandLineOptions

!
!

!
!
!

!

!

attr_accessor :argv
attr_reader :options
def initialize (argv = [], &block)
@o...
!

!

def valid?
options.each do |option_flag, option_type|
return false if option_type == :string && raw_value_for_option...
Done!

@mark_menard

Enable Labs

42

WiteSmallThings - February 8, 2014
# some_ruby_program -v -efoo -i100
!

options = CommandLineOptions.new(ARGV) do
option :v
option :e, :string
option :i, :i...
describe "integer options" do
it "must have content"
it "must be an integer"
it "can return the value as an integer"
it "r...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
can retu...
let(:options) { CommandLineOptions.new { option :i, :integer } }
!

it "must have content" do
options.argv = [ "-i" ]
expe...
def valid?
options.each do |option_flag, option_type|
return false if option_type == :string &&
raw_value_for_option(optio...
def valid?
options.each do |option_flag, option_type|
return false if (option_type == :string || option_type == :integer)&...
it "must be an integer" do
options.argv = [ "-inot_an_integer" ]
expect(options.valid?).to be_false
end

Enable Labs

!49
...
def valid?
options.each do |option_flag, option_type|
return false if (option_type == :string || option_type == :integer) ...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
can retu...
class CommandLineOptions

!
!

!
!
!

!

!
!

attr_accessor :argv
attr_reader :options
def initialize (argv = [], &block)
...
def valid?
options.each do |option_flag, option_type|
return false if (option_type == :string || option_type == :integer) ...
Integer("foo") rescue false #=> false
"foo".to_i
#=> 0 !!!!!!!

Enable Labs

!55

55

@mark_menard

WiteSmallThings - Febr...
!

def valid?
options.each do |option_flag, option_type|
case(option_type)
when :string
return false if raw_value_for_opti...
def valid?
options.each do |option_flag, option_type|
case(option_type)
when :string
return false if raw_value_for_option(...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
can retu...
def valid?
options.each do |option_flag, option_type|
case(option_type)
when :string
return false unless string_option_val...
private def boolean_option_valid? (raw_value)
true
end

Enable Labs

!60

60

@mark_menard

WiteSmallThings - February 8, ...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
can retu...
def valid?
options.each do |option_flag, option_type|
return false unless send("#{option_type}_option_valid?", raw_value_f...
def valid?
options.each do |option_flag, option_type|
return false unless option_valid?(option_type, raw_value_for_option(...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
is valid...
!
!

class CommandLineOptions

!
!
!
!
!
!
!
!
!
!
!

attr_accessor :argv
attr_reader :options
def initialize (argv = [], ...
def has (option_flag)
options.include?(option_flag) && argv.include?("-#{option_flag}")
end

!
def valid?
options.each do ...
Classes

@mark_menard

Enable Labs

67

WiteSmallThings - February 8, 2014
How do we write small classes?
•
•
•
•
•
•
•

Write small methods!
Talk to the class!
Find a good name!
Isolate Responsibi...
What are the characteristics of a
well designed small class?
•
•
•
•
•
•

Single responsibility!
Cohesive properties!
Smal...
describe StringOption do
let(:string_option) { StringOption.new('s', '-sfoo') }
!

it
it
it
it
end

"has a flag"
"is valid...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
is valid...
class StringOption
!

attr_reader :flag
!

def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
end
!

end...
it "is valid when it has a value" do
expect(string_option.valid?).to be_true
end

Enable Labs

!73

73

@mark_menard

Wite...
class StringOption

class CommandLineOptions

!
!

!

private def string_option_valid? (raw_value)
extract_value_from_raw_...
class StringOption
!

attr_reader :flag, :raw_value
!

def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_valu...
private def string_option_valid? (raw_value)
StringOption.new("", raw_value).valid?
end

Enable Labs

!76

76

@mark_menar...
class IntegerOption
!
attr_reader :flag, :raw_value
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value...
class StringOption
!
attr_reader :flag, :raw_value
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_value
...
class OptionWithContent
!
attr_reader :flag, :raw_value
!
def initialize (flag, raw_value)
@flag = flag
@raw_value = raw_v...
class StringOption < OptionWithContent
end
!

class IntegerOption < OptionWithContent
!

def valid?
super && real_value_is...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
is valid...
Dependencies

@mark_menard

Enable Labs

83

WiteSmallThings - February 8, 2014
How do we deal with
Dependencies?
•
•

Dependency Injection!
Depend on Abstractions

@mark_menard

Enable Labs

82

WiteSm...
private def option (option_flag, option_type = :boolean)
options[option_flag] = option_type
end

private def option (optio...
How do we isolate abstractions?
Separate the “what”
from the “how”.

@mark_menard

Enable Labs

85

WiteSmallThings - Febr...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content (FAILED -...
def valid?
options.each do |option_flag, option_type|
return false unless option_valid?(option_type, raw_value_for_option(...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
is valid...
def value (option_flag)
raw_option_value = argv.find { |arg| arg =~ /-#{option_flag}/ }
return nil unless raw_option_value...
1) CommandLineOptions string options can return the value	
Failure/Error: expect(options.value(:e)).to eq("foo")	
NoMethod...
# OptionWithContent
def value
return nil if option_unset?
valid? ? extract_value_from_raw_value : nil
end

# IntegerOption...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
is valid...
Done!

@mark_menard

Enable Labs

93

WiteSmallThings - February 8, 2014
class CommandLineOptions

!

!
!
!
!
!

!
!
!
!

def initialize (argv = [], &block)
@options = {}
@argv = argv
instance_ev...
!

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

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

!95...
Then

@mark_menard

Enable Labs

96

WiteSmallThings - February 8, 2014
some_ruby_program -v -efoo -i100 -afoo,bar,baz

Enable Labs

!97

97

@mark_menard

WiteSmallThings - February 8, 2014
describe "array options" do
it "can return the value as an array" do
expect(CommandLineOptions.new([ "-afoo,bar,baz" ]) { ...
CommandLineOptions	
boolean options	
are true if present	
are false if absent	
string options	
must have content	
is valid...
Done!

@mark_menard

Enable Labs

100

WiteSmallThings - February 8, 2014
Enable Labs
Start Today

@mark_menard

http://www.enablelabs.com/
info@enablelabs.com
866-895-8189
@mark_menard

Enable La...
Upcoming SlideShare
Loading in …5
×

Write Small Things (Code)

455 views

Published on

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.

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
455
On SlideShare
0
From Embeds
0
Number of Embeds
7
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Write Small Things (Code)

  1. 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. 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. 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
  4. 4. Introduction @mark_menard Enable Labs 4 WiteSmallThings - February 8, 2014
  5. 5. What is meant by small? @mark_menard Enable Labs 5 WiteSmallThings - February 8, 2014
  6. 6. It’s not about total line count. @mark_menard Enable Labs 6 WiteSmallThings - February 8, 2014
  7. 7. It’s not about method count. @mark_menard Enable Labs 7 WiteSmallThings - February 8, 2014
  8. 8. It’s not about class count. @mark_menard Enable Labs 8 WiteSmallThings - February 8, 2014
  9. 9. So, what do I mean by small things? Small methods! Small classes @mark_menard Enable Labs 9 WiteSmallThings - February 8, 2014
  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 @mark_menard Enable Labs 10 WiteSmallThings - February 8, 2014
  11. 11. What are the challenges of small code? • • Dependency Management! Context Management @mark_menard Enable Labs 11 WiteSmallThings - February 8, 2014
  12. 12. The goal: Small units of understandable code that are amenable to change. @mark_menard Enable Labs 12 WiteSmallThings - February 8, 2014
  13. 13. Our Three Main Tools • • • Extract Method! Extract Class! Composed Method @mark_menard Enable Labs 13 WiteSmallThings - February 8, 2014
  14. 14. Methods @mark_menard Enable Labs 14 WiteSmallThings - February 8, 2014
  15. 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. 16. The first rule of methods: @mark_menard Enable Labs 16 WiteSmallThings - February 8, 2014
  17. 17. Do one thing. Do it well. Do only that thing. @mark_menard Enable Labs 17 WiteSmallThings - February 8, 2014
  18. 18. Use Descriptive Names @mark_menard Enable Labs 18 WiteSmallThings - February 8, 2014
  19. 19. The fewer arguments the better. @mark_menard Enable Labs 19 WiteSmallThings - February 8, 2014
  20. 20. Separate Queries from Commands @mark_menard Enable Labs 20 WiteSmallThings - February 8, 2014
  21. 21. Don’t Repeat Yourself @mark_menard Enable Labs 21 WiteSmallThings - February 8, 2014
  22. 22. Let’s Build a Command Line Option Library @mark_menard Enable Labs 22 WiteSmallThings - February 8, 2014
  23. 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. 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. 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. 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. 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
  28. 28. Done! @mark_menard Enable Labs 28 WiteSmallThings - February 8, 2014
  29. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  42. 42. Done! @mark_menard Enable Labs 42 WiteSmallThings - February 8, 2014
  43. 43. # 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 !43 43 @mark_menard WiteSmallThings - February 8, 2014
  44. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  54. 54. Integer("foo") rescue false #=> false "foo".to_i #=> 0 !!!!!!! Enable Labs !55 55 @mark_menard WiteSmallThings - February 8, 2014
  55. 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. 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. 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. 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
  59. 59. private def boolean_option_valid? (raw_value) true end Enable Labs !60 60 @mark_menard WiteSmallThings - February 8, 2014
  60. 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. 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. 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. 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. 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. 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
  66. 66. Classes @mark_menard Enable Labs 67 WiteSmallThings - February 8, 2014
  67. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  81. 81. Dependencies @mark_menard Enable Labs 83 WiteSmallThings - February 8, 2014
  82. 82. How do we deal with Dependencies? • • Dependency Injection! Depend on Abstractions @mark_menard Enable Labs 82 WiteSmallThings - February 8, 2014
  83. 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. 84. How do we isolate abstractions? Separate the “what” from the “how”. @mark_menard Enable Labs 85 WiteSmallThings - February 8, 2014
  85. 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. 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. 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. 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. 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. 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. 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
  92. 92. Done! @mark_menard Enable Labs 93 WiteSmallThings - February 8, 2014
  93. 93. class CommandLineOptions ! ! ! ! ! ! ! ! ! ! def initialize (argv = [], &block) @options = {} @argv = argv instance_eval &block populate_options_with_raw_values end def valid? options.values.all?(&:valid?) end def value (option_flag) options[option_flag].value end private attr_reader :options, :argv def populate_options_with_raw_values options.each do |option_flag, option| option.raw_value = raw_value_for_option(option_flag) end end def option (option_flag, option_type = :boolean) options[option_flag] = get_option_class(option_type).new(option_flag, nil) end def get_option_class (option_type) "#{option_type}_option".camelize.constantize end def raw_value_for_option (option_flag) argv.find { |arg| arg =~ /-#{option_flag}/ } end end Enable Labs !94 94 @mark_menard WiteSmallThings - February 8, 2014
  94. 94. ! def valid? options.values.all?(&:valid?) end ! def value (option_flag) options[option_flag].value end Enable Labs !95 95 @mark_menard WiteSmallThings - February 8, 2014
  95. 95. Then @mark_menard Enable Labs 96 WiteSmallThings - February 8, 2014
  96. 96. some_ruby_program -v -efoo -i100 -afoo,bar,baz Enable Labs !97 97 @mark_menard WiteSmallThings - February 8, 2014
  97. 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. 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
  99. 99. Done! @mark_menard Enable Labs 100 WiteSmallThings - February 8, 2014
  100. 100. Enable Labs Start Today @mark_menard http://www.enablelabs.com/ info@enablelabs.com 866-895-8189 @mark_menard Enable Labs 101 WiteSmallThings - February 8, 2014

×