In a blog post about unconditional programming Michael Feathers shows how limiting if
statements can be used as a tool for reducing code complexity.
He uses a specific example to illustrate his point. Now, I've been thinking about other specific examples that could help me learn more about unconditional/if
less/for
less programming.
For example, using OptionParser I made a cat clone that will upcase the stream if the --upcase
switch is set:
#!/usr/bin/env ruby
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: cat [options] [file ...]"
opts.on("-u", "--upcase", "Upcase stream") do
options[:upcase] = true
end
end.parse!
if options[:upcase]
puts ARGF.read.upcase
else
puts ARGF.read
end
How would I handle that switch without an if..else
block?
Also interested in links to other illustrative specific examples.
Try this,
#!/usr/bin/env ruby
require 'optparse'
options = { :transform => :itself }
OptionParser.new do |opts|
opts.banner = "Usage: cat [options] [file ...]"
opts.on("-u", "--upcase", "Upcase stream") do
options[:transform] = :upcase
end
# add more options for downcase, reverse, etc ...
end.parse!
puts ARGF.read.send(options[:transform])
This worked quite well, I am actually surprised how well that worked.
What has been changed?
- The option is internally renamed to
:transform
- The internal default value is
:itself
- The command line switch sets the internal option to
:upcase
- Call the method with
send
Not all if
statements can be improved upon like this though. I would guess the idea of unconditional programming is to prefer a combination of meaningful default values, as I did above, and intention revealing functions whenever it seems reasonable but not at all costs.
Here are some examples of intention revealing functions,
max
min
Hash#fetch
Enumerable#detect
Enumerable#select
Enumerable#chunk
Enumerable#drop_while
Enumerable#slice_when
Enumerable#take_while
- etc...
Another related practice is for
less programming.
If you want to practice unconditional and for
less programming best look for examples that process arrays and strings and make use of the many "functional" methods in Ruby's enumerable module.
Here is an example of string justification without for
and if
,
str = 'This is an example to be aligned to both margins'
words = str.split
width, remainder = (50 - words.map(&:length).inject(:+)).divmod(words.length - 1)
words.take(words.length - 1).each { |each| width.times { each << 32 }}
words.take(words.length - 1).shuffle.take(remainder).each { |each| each << 32 }
p words.join
# => "This is an example to be aligned to both margins"
Eliminating conditions is a tool for reducing complexity, not an end goal. I explained that better in my other answer. In this case the condition must be there because whether or not options[:upcase]
is set is part of the logic. But you can at least eliminate the duplication.
Because the else clause is the same as the if clause but just adds a step, you can remove the repetition and make your code linear by unconditionally doing the common bit, and then conditionally doing the extra stuff.
data = ARGF.read;
data.upcase! if options[:upcase];