Write Small Things (Code)

100
Enable Labs @mark_menard Write Small Things Mark Menard Los Angeles Ruby Conf 2014 @mark_menard Enable Labs 1 WiteSmallThings - February 8, 2014

description

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.

Transcript of Write Small Things (Code)

Page 1: Write Small Things (Code)

Enable Labs @mark_menard

Write Small ThingsMark Menard

Los Angeles Ruby Conf 2014

@mark_menard Enable Labs

1 WiteSmallThings - February 8, 2014

Page 2: Write Small Things (Code)

Enable Labs @mark_menard

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

2 WiteSmallThings - February 8, 2014

Page 3: Write Small Things (Code)

Enable Labs @mark_menard

‘The great thing about writing shitty code that “just works,” is that it is too risky and too expensive to change, so it lives forever.’!

-Reginald Braithwaite @raganwald

3 WiteSmallThings - February 8, 2014

Page 4: Write Small Things (Code)

Enable Labs @mark_menard

Introduction

4 WiteSmallThings - February 8, 2014

Page 5: Write Small Things (Code)

Enable Labs @mark_menard

What is meant by small?

5 WiteSmallThings - February 8, 2014

Page 6: Write Small Things (Code)

Enable Labs @mark_menard

It’s not about total line count.

6 WiteSmallThings - February 8, 2014

Page 7: Write Small Things (Code)

Enable Labs @mark_menard

It’s not about method count.

7 WiteSmallThings - February 8, 2014

Page 8: Write Small Things (Code)

Enable Labs @mark_menard

It’s not about class count.

8 WiteSmallThings - February 8, 2014

Page 9: Write Small Things (Code)

Enable Labs @mark_menard

So, what do I mean by small things?

Small methods! Small classes

9 WiteSmallThings - February 8, 2014

Page 10: Write Small Things (Code)

Enable Labs @mark_menard

Why should we strive for small code?• We don’t know what the future will bring!• Raise the level of abstraction!• Create composable components!• Prefer delegation over inheritance

Enable Future Change

10 WiteSmallThings - February 8, 2014

Page 11: Write Small Things (Code)

Enable Labs @mark_menard

What are the challenges of small code?• Dependency Management!• Context Management

11 WiteSmallThings - February 8, 2014

Page 12: Write Small Things (Code)

Enable Labs @mark_menard

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

12 WiteSmallThings - February 8, 2014

Page 13: Write Small Things (Code)

Enable Labs @mark_menard

Our Three Main Tools

• Extract Method!

• Extract Class!

• Composed Method

13 WiteSmallThings - February 8, 2014

Page 14: Write Small Things (Code)

Enable Labs @mark_menard

Methods

14 WiteSmallThings - February 8, 2014

Page 15: Write Small Things (Code)

Enable Labs @mark_menard

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

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

15 WiteSmallThings - February 8, 2014

Page 16: Write Small Things (Code)

Enable Labs @mark_menard

The first rule of methods:

16 WiteSmallThings - February 8, 2014

Page 17: Write Small Things (Code)

Enable Labs @mark_menard

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

17 WiteSmallThings - February 8, 2014

Page 18: Write Small Things (Code)

Enable Labs @mark_menard

Use Descriptive Names

18 WiteSmallThings - February 8, 2014

Page 19: Write Small Things (Code)

Enable Labs @mark_menard

The fewer arguments the better.

19 WiteSmallThings - February 8, 2014

Page 20: Write Small Things (Code)

Enable Labs @mark_menard

Separate Queries from Commands

20 WiteSmallThings - February 8, 2014

Page 21: Write Small Things (Code)

Enable Labs @mark_menard

Don’t Repeat Yourself

21 WiteSmallThings - February 8, 2014

Page 22: Write Small Things (Code)

Enable Labs @mark_menard

Let’s Build a Command Line Option Library

22 WiteSmallThings - February 8, 2014

Page 23: Write Small Things (Code)

Enable Labs @mark_menard!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

23 WiteSmallThings - February 8, 2014

Page 24: Write Small Things (Code)

Enable Labs @mark_menard!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

24 WiteSmallThings - February 8, 2014

Page 25: Write Small Things (Code)

Enable Labs @mark_menard!25

CommandLineOptions options are true if present are false if absent !

Finished in 0.00206 seconds 2 examples, 2 failures

25 WiteSmallThings - February 8, 2014

Page 26: Write Small Things (Code)

Enable Labs @mark_menard!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 ! def option (option_flag) options << option_flag end !end

options = CommandLineOptions.new(ARGV) do option :c option :v option :e end

26 WiteSmallThings - February 8, 2014

Page 27: Write Small Things (Code)

Enable Labs @mark_menard!27

CommandLineOptions options are true if present are false if absent !

Finished in 0.00206 seconds 2 examples, 0 failures

27 WiteSmallThings - February 8, 2014

Page 28: Write Small Things (Code)

Enable Labs @mark_menard

Done!

28 WiteSmallThings - February 8, 2014

Page 29: Write Small Things (Code)

Enable Labs @mark_menard!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

29 WiteSmallThings - February 8, 2014

Page 30: Write Small Things (Code)

Enable Labs @mark_menard

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

!30

30 WiteSmallThings - February 8, 2014

Page 31: Write Small Things (Code)

Enable Labs @mark_menard

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 seconds5 examples, 3 failures

!31

31 WiteSmallThings - February 8, 2014

Page 32: Write Small Things (Code)

Enable Labs @mark_menard!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

32 WiteSmallThings - February 8, 2014

Page 33: Write Small Things (Code)

Enable Labs @mark_menard

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 seconds5 examples, 2 failures

!33

33 WiteSmallThings - February 8, 2014

Page 34: Write Small Things (Code)

Enable Labs @mark_menard!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

34 WiteSmallThings - February 8, 2014

Page 35: Write Small Things (Code)

Enable Labs @mark_menard!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 seconds5 examples, 0 failures

35 WiteSmallThings - February 8, 2014

Page 36: Write Small Things (Code)

Enable Labs @mark_menard

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

!36

36 WiteSmallThings - February 8, 2014

Page 37: Write Small Things (Code)

Enable Labs @mark_menard

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

def print_invoice_for_amount (amount) print_header print_details (amount) end !def print_details (amount) puts "Name: #{@name}" puts "Amount: #{amount}" end

High level of abstraction

Low level of abstraction

Same level of abstraction

Move this to here

37 WiteSmallThings - February 8, 2014

Page 38: Write Small Things (Code)

Enable Labs @mark_menard!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

38 WiteSmallThings - February 8, 2014

Page 39: Write Small Things (Code)

Enable Labs @mark_menard!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 seconds5 examples, 0 failures

39 WiteSmallThings - February 8, 2014

Page 40: Write Small Things (Code)

Enable Labs @mark_menard!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

40 WiteSmallThings - February 8, 2014

Page 41: Write Small Things (Code)

Enable Labs @mark_menard!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

41 WiteSmallThings - February 8, 2014

Page 42: Write Small Things (Code)

Enable Labs @mark_menard

Done!

42 WiteSmallThings - February 8, 2014

Page 43: Write Small Things (Code)

Enable Labs @mark_menard!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

43 WiteSmallThings - February 8, 2014

Page 44: Write Small Things (Code)

Enable Labs @mark_menard!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

44 WiteSmallThings - February 8, 2014

Page 45: Write Small Things (Code)

Enable Labs @mark_menard!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)

45 WiteSmallThings - February 8, 2014

Page 46: Write Small Things (Code)

Enable Labs @mark_menard!46

let(:options) { CommandLineOptions.new { option :i, :integer } } ! it "must have content" do options.argv = [ "-i" ] expect(options.valid?).to be_false end

46 WiteSmallThings - February 8, 2014

Page 47: Write Small Things (Code)

Enable Labs @mark_menard!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

47 WiteSmallThings - February 8, 2014

Page 48: Write Small Things (Code)

Enable Labs @mark_menard!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

48 WiteSmallThings - February 8, 2014

Page 49: Write Small Things (Code)

Enable Labs @mark_menard!49

it "must be an integer" do options.argv = [ "-inot_an_integer" ] expect(options.valid?).to be_false end

49 WiteSmallThings - February 8, 2014

Page 50: Write Small Things (Code)

Enable Labs @mark_menard!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

50 WiteSmallThings - February 8, 2014

Page 51: Write Small Things (Code)

Enable Labs @mark_menard!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)

51 WiteSmallThings - February 8, 2014

Page 52: Write Small Things (Code)

Enable Labs @mark_menard!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

52 WiteSmallThings - February 8, 2014

Page 53: Write Small Things (Code)

Enable Labs @mark_menard!54

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

54 WiteSmallThings - February 8, 2014

Page 54: Write Small Things (Code)

Enable Labs @mark_menard!55

Integer("foo") rescue false #=> false "foo".to_i #=> 0 !!!!!!!

55 WiteSmallThings - February 8, 2014

Page 55: Write Small Things (Code)

Enable Labs @mark_menard!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) ! end end end

56 WiteSmallThings - February 8, 2014

Page 56: Write Small Things (Code)

Enable Labs @mark_menard!57

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

57 WiteSmallThings - February 8, 2014

Page 57: Write Small Things (Code)

Enable Labs @mark_menard!58

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)

58 WiteSmallThings - February 8, 2014

Page 58: Write Small Things (Code)

Enable Labs @mark_menard!59

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

59 WiteSmallThings - February 8, 2014

Page 59: Write Small Things (Code)

Enable Labs @mark_menard!60

private def boolean_option_valid? (raw_value) true end

60 WiteSmallThings - February 8, 2014

Page 60: Write Small Things (Code)

Enable Labs @mark_menard!61

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)

61 WiteSmallThings - February 8, 2014

Page 61: Write Small Things (Code)

Enable Labs @mark_menard!62

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

62 WiteSmallThings - February 8, 2014

Page 62: Write Small Things (Code)

Enable Labs @mark_menard!63

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

63 WiteSmallThings - February 8, 2014

Page 63: Write Small Things (Code)

Enable Labs @mark_menard!64

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 seconds10 examples, 0 failures, 1 pending

64 WiteSmallThings - February 8, 2014

Page 64: Write Small Things (Code)

Enable Labs @mark_menard!65

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

65 WiteSmallThings - February 8, 2014

Page 65: Write Small Things (Code)

Enable Labs @mark_menard!66

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

66 WiteSmallThings - February 8, 2014

Page 66: Write Small Things (Code)

Enable Labs @mark_menard

Classes

67 WiteSmallThings - February 8, 2014

Page 67: Write Small Things (Code)

Enable Labs @mark_menard

How do we write small classes?

• Write small methods!

• Talk to the class!

• Find a good name!

• Isolate Responsibilities!

• Find cohesive sets of variables/properties!

• Extract Class!

• Move method

68 WiteSmallThings - February 8, 2014

Page 68: Write Small Things (Code)

Enable Labs @mark_menard

What are the characteristics of a well designed small class?

• Single responsibility!

• Cohesive properties!

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

• Implements a single Use Case if possible!

• Primary logic is expressed in a composed method!

• Dependencies are injected

69 WiteSmallThings - February 8, 2014

Page 69: Write Small Things (Code)

Enable Labs @mark_menard!70

describe StringOption do let(:string_option) { StringOption.new('s', '-sfoo') } ! it "has a flag" it "is valid when it has a value" it "can return it's value when present" it "returns nil if the flag has no raw value" end

70 WiteSmallThings - February 8, 2014

Page 70: Write Small Things (Code)

Enable Labs @mark_menard!71

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)

71 WiteSmallThings - February 8, 2014

Page 71: Write Small Things (Code)

Enable Labs @mark_menard!72

class StringOption !

attr_reader :flag !

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

end

72 WiteSmallThings - February 8, 2014

Page 72: Write Small Things (Code)

Enable Labs @mark_menard!73

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

73 WiteSmallThings - February 8, 2014

Page 73: Write Small Things (Code)

Enable Labs @mark_menard!74

class CommandLineOptions ! private def string_option_valid? (raw_value) extract_value_from_raw_value(raw_value).length > 0 end !end

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

74 WiteSmallThings - February 8, 2014

Page 74: Write Small Things (Code)

Enable Labs @mark_menard!75

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

75 WiteSmallThings - February 8, 2014

Page 75: Write Small Things (Code)

Enable Labs @mark_menard!76

private def string_option_valid? (raw_value) StringOption.new("", raw_value).valid? end

76 WiteSmallThings - February 8, 2014

Page 76: Write Small Things (Code)

Enable Labs @mark_menard!77

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

77 WiteSmallThings - February 8, 2014

Page 77: Write Small Things (Code)

Enable Labs @mark_menard!78

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

78 WiteSmallThings - February 8, 2014

Page 78: Write Small Things (Code)

Enable Labs @mark_menard!79

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

79 WiteSmallThings - February 8, 2014

Page 79: Write Small Things (Code)

Enable Labs @mark_menard!80

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

80 WiteSmallThings - February 8, 2014

Page 80: Write Small Things (Code)

Enable Labs @mark_menard!81

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)

81 WiteSmallThings - February 8, 2014

Page 81: Write Small Things (Code)

Enable Labs @mark_menard

Dependencies

83 WiteSmallThings - February 8, 2014

Page 82: Write Small Things (Code)

Enable Labs @mark_menard

How do we deal with Dependencies?• Dependency Injection!

• Depend on Abstractions

82 WiteSmallThings - February 8, 2014

Page 83: Write Small Things (Code)

Enable Labs @mark_menard!84

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

84 WiteSmallThings - February 8, 2014

Page 84: Write Small Things (Code)

Enable Labs @mark_menard

How do we isolate abstractions?

Separate the “what” from the “how”.

85 WiteSmallThings - February 8, 2014

Page 85: Write Small Things (Code)

Enable Labs @mark_menard!86

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)

86 WiteSmallThings - February 8, 2014

Page 86: Write Small Things (Code)

Enable Labs @mark_menard!87

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

87 WiteSmallThings - February 8, 2014

Page 87: Write Small Things (Code)

Enable Labs @mark_menard!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 (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)

88 WiteSmallThings - February 8, 2014

Page 88: Write Small Things (Code)

Enable Labs @mark_menard!89

def value (option_flag) options[option_flag].value 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

89 WiteSmallThings - February 8, 2014

Page 89: Write Small Things (Code)

Enable Labs @mark_menard!90

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

90 WiteSmallThings - February 8, 2014

Page 90: Write Small Things (Code)

Enable Labs @mark_menard!91

# 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

91 WiteSmallThings - February 8, 2014

Page 91: Write Small Things (Code)

Enable Labs @mark_menard!92

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

92 WiteSmallThings - February 8, 2014

Page 92: Write Small Things (Code)

Enable Labs @mark_menard

Done!

93 WiteSmallThings - February 8, 2014

Page 93: Write Small Things (Code)

Enable Labs @mark_menard!94

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

94 WiteSmallThings - February 8, 2014

Page 94: Write Small Things (Code)

Enable Labs @mark_menard!95

!

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

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

95 WiteSmallThings - February 8, 2014

Page 95: Write Small Things (Code)

Enable Labs @mark_menard

Then

96 WiteSmallThings - February 8, 2014

Page 96: Write Small Things (Code)

Enable Labs @mark_menard!97

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

97 WiteSmallThings - February 8, 2014

Page 97: Write Small Things (Code)

Enable Labs @mark_menard!98

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

98 WiteSmallThings - February 8, 2014

Page 98: Write Small Things (Code)

Enable Labs @mark_menard!99

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

99 WiteSmallThings - February 8, 2014

Page 99: Write Small Things (Code)

Enable Labs @mark_menard

Done!

100 WiteSmallThings - February 8, 2014

Page 100: Write Small Things (Code)

Enable Labs @mark_menard

Start Todayhttp://www.enablelabs.com/

[email protected]

866-895-8189

Enable Labs@mark_menard

101 WiteSmallThings - February 8, 2014