Skip to content

Instantly share code, notes, and snippets.

@anonyo
Last active August 29, 2015 14:23
Show Gist options
  • Save anonyo/61a0a2dd2923a5fde0cc to your computer and use it in GitHub Desktop.
Save anonyo/61a0a2dd2923a5fde0cc to your computer and use it in GitHub Desktop.
Removing class argument dependency
class Gear
  attr_reader :chainring, :cog, :wheel 
  
  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog       = cog
    @wheel     = wheel
  end
...
end

Senders of new depend on the order of the arguments as they are specified in Gear’s initialize method. If that order changes, all the senders will be forced to change. Unfortunately, it’s quite common to tinker with initialization arguments.

Use Hashes for Initialization Arguments

class Gear
  attr_reader :chainring, :cog, :wheel 
  def initialize(args)
    @chainring = args[:chainring]
    @cog       = args[:cog]
    @wheel     = args[:wheel]
  end
...
end

The initialize method now takes just one argument, args, a hash that contains all of the inputs. The method has been changed to extract its arguments from this hash.

The above technique has several advantages. The first and most obvious is that it removes every dependency on argument order. Gear is now free to add or remove initialization arguments and defaults, secure in the knowledge that no change will have side effects in other code.

This technique adds verbosity. In many situations verbosity is a detriment, but in this case it has value. The verbosity exists at the intersection between the needs of the present and the uncertainty of the future. Using fixed-order arguments requires less code today but you pay for this decrease in volume of code with an increase in the risk that changes will cascade into dependents later.

Explicitly Define Defaults

def initialize(args)
  @chainring = args[:chainring] || 40 
  @cog = args[:cog] || 18 
  @wheel = args[:wheel]
end

This is a common technique but one you should use with caution; there are situations in which it might not do what you want. The || method acts as an or condition; it first evaluates the left-hand expression and then, if the expression returns false or nil, proceeds to evaluate and return the result of the right-hand expression. The use of || above therefore, relies on the fact that the [] method of Hash returns nil for missing keys.

You can also specify defaults using fetch

def initialize(args)
  @chainring = args.fetch(:chainring, 40) 
  @cog = args.fetch(:cog, 18) 
  @wheel = args[:wheel]
end

You can also completely remove the defaults from initialize and isolate them inside of a separate wrapping method. The defaults method below defines a second hash that is merged into the options hash during initialization. In this case, merge has the same effect as fetch; the defaults will get merged only if their keys are not in the hash.

def initialize(args)
  args = defaults.merge(args) 
  @chainring = args[:chainring]
  # ...
end

def defaults
  {chainring: 40, cog: 18}
end

This isolation technique is perfectly reasonable for the case above but it’s especially useful when the defaults are more complicated. If your defaults are more than simple numbers or strings, implement a defaults method.

##Isolate Multiparameter Initialization

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment