Refactoring: Refactorings/Introduce Named Parameter

From Notes
Jump to navigation Jump to search

Parameters in a method call cannot be easily remembered or deduced from the name of the method called. Convert the parameter list into a hash of key => value pairs.

Mechanics

  1. Choose the parameters you want to name. If you aren't naming all of the parameters, move the ones you want to name to the end of the parameter list. (That way, calling code doesn't need to wrap named parameters in {}'s)
  2. Test
  3. Replace parameters in the calling code with name/value pairs
  4. Replace parameters with a Hash object in the receiving method. Modify the receiving method to use the new Hash
  5. Test


Example: All Parameters

Before

class SearchCriteria

 attr_reader :author_id, :publisher_id, :isbn
 def initialize(author_id, publisher_id, isbn)
   @author_id = author_id
   @publisher_id = publisher_id
   @isbn = isbn
 end

end

After

Not the best example; Introduce Class Annotation is preferred in this case. class SearchCriteria

 attr_reader :author_id, :publisher_id, :isbn
 def initialize(params)
   @author_id = params[:author_id]
   @publisher_id = params[:publisher_id]
   @isbn = params[:isbn]
 end

end


Example: Some Optional Parameters

Before

class Books

 def self.find(selector, conditions="", *joins)
   sql = ["SELECT * FROM books"]
   joins.each do |join_table|
     sql << "LEFT OUTER JOIN #{join_table} ON"
     sql << "books.#{join_table.to_s.chap}_id"
     sql << " = #{join_table}.id"
   end
   sql << "WHERE #{conditions}" unless conditions.empty?
   sql << "LIMIT 1" if selector == :first
   connection.find(sql.join(" "))
 end

end

  1. Usage

Books.find(:all) Books.find(:all, "title like '%Voodoo Economics'") Books.find(:first, "authors.name = 'Jenny James'", :authors)

After

Fluency can still be improved by Introduce Assertion to validate the keys. class Books

 def self.find(selector, hash={})
   hash[:joins] ||= []
   hash[:conditions] ||= ""
   sql = ["SELECT * FROM books"]
   hash[:joins].each do |join_table|
     sql << "LEFT OUTER JOIN #{join_table} ON"
     sql << "books.#{join_table.to_s.chap}_id"
     sql << " = #{join_table}.id"
   end
   sql << "WHERE #{hash[:conditions]}" unless hash[:conditions].empty?
   sql << "LIMIT 1" if selector == :first
   connection.find(sql.join(" "))
 end

end

  1. Usage

Books.find(:all) Books.find(:all, :conditions => "title like '%Voodoo Economics'") Books.find(:first, :conditions => "authors.name = 'Jenny James'", :joins => [:authors])

After (with assertions)

module AssertValidKeys

 def assert_valid_keys(*valid_keys)
   unknown_keys = keys - [valid_keys].flatten
   if unknown_keys.any?
     raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}")
   end
 end

end

Hash.send(:include, AssertValidKeys)

class Books

 def self.find(selector, hash={})
   hash.assert_valid_keys :conditions, :joins
   hash[:joins] ||= []
   hash[:conditions] ||= ""
   sql = ["SELECT * FROM books"]
   hash[:joins].each do |join_table|
     sql << "LEFT OUTER JOIN #{join_table} ON"
     sql << "books.#{join_table.to_s.chap}_id"
     sql << " = #{join_table}.id"
   end
   sql << "WHERE #{hash[:conditions]}" unless hash[:conditions].empty?
   sql << "LIMIT 1" if selector == :first
   connection.find(sql.join(" "))
 end

end

Refactorings
Composing Methods

Extract Method • Inline Method • Inline Temp • Replace Temp with Query • Replace Temp with Chain • Introduce Explaining Variable • Split Temporary Variable • Remove Assignments to Parameters • Replace Method with Method Object • Substitute Algorithm • Replace Loop with Collection Closure Method • Extract Surrounding Method • Introduce Class Annotation • Introduce Named Parameter • Remove Named Parameter • Remove Unused Default Parameter • Dynamic Method Definition • Replace Dynamic Receptor with Dynamic Method Definition • Isolate Dynamic Receptor • Move Eval from Runtime to Parse Time

Moving Features Between Objects

Move Method • Move Field • Extract Class • Inline Class • Hide Delegate • Remove Middle Man

Organizing Data

Self Encapsulate Field • Replace Data Value with Object • Change Value to Reference • Change Reference to Value • Replace Array with Object • Replace Hash with Object • Change Unidirectional Association to Bidirectional • Change Bidirectional Association to Unidirectional • Replace Magic Number with Symbolic Content • Encapsulate Collection • Replace Record with Data Class • Replace Type Code with Polymorphism • Replace Type Code with Module Extension • Replace Type Code with State-Strategy • Replace Subclass with Fields • Lazily Initialized Atribute • Eagerly Initialized Attribute

Simplifying Conditional Expressions

Decompose Conditional • Recompose Conditional • Consolidate Conditional Expression • Consolidate Duplicate Conditional Fragments • Remove Control Flag • Replace Nested Conditional with Guard Clauses • Replace Conditional with Polymorphism • Introduce Null Object • Introduce Assertion

Making Method Calls Simpler

Rename Method • Add Parameter • Remove Parameter • Separate Query from Modifier • Parameterize Method • Replace Parameter with Explicit Methods • Preserve Whole Object • Replace Parameter with Method • Introduce Parameter Object • Remove Setting Method • Hide Method • Replace Constructor with Factory Method • Replace Error Code with Exception • Replace Exception with Test • Introduce Gateway • Introduce Expression Builder

Dealing with Generalization

Pull Up Method • Push Down Method • Extract Module • Inline Module • Extract Subclass • Introduce Inheritance • Collapse Hierarchy • Form Template Method • Replace Inheritance with Delegation • Replace Delegation with Hierarchy • Replace Abstract Superclass with Module

Big Refactorings

Tease Apart Inheritance • Convert Procedural Design to Objects • Separate Domain from Presentation • Extract Hierarchy