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 MethodInline MethodInline TempReplace Temp with QueryReplace Temp with ChainIntroduce Explaining VariableSplit Temporary VariableRemove Assignments to ParametersReplace Method with Method ObjectSubstitute AlgorithmReplace Loop with Collection Closure MethodExtract Surrounding MethodIntroduce Class AnnotationIntroduce Named ParameterRemove Named ParameterRemove Unused Default ParameterDynamic Method DefinitionReplace Dynamic Receptor with Dynamic Method DefinitionIsolate Dynamic ReceptorMove Eval from Runtime to Parse Time

Moving Features Between Objects

Move MethodMove FieldExtract ClassInline ClassHide DelegateRemove Middle Man

Organizing Data

Self Encapsulate FieldReplace Data Value with ObjectChange Value to ReferenceChange Reference to ValueReplace Array with ObjectReplace Hash with ObjectChange Unidirectional Association to BidirectionalChange Bidirectional Association to UnidirectionalReplace Magic Number with Symbolic ContentEncapsulate CollectionReplace Record with Data ClassReplace Type Code with PolymorphismReplace Type Code with Module ExtensionReplace Type Code with State-StrategyReplace Subclass with FieldsLazily Initialized AtributeEagerly Initialized Attribute

Simplifying Conditional Expressions

Decompose ConditionalRecompose ConditionalConsolidate Conditional ExpressionConsolidate Duplicate Conditional FragmentsRemove Control FlagReplace Nested Conditional with Guard ClausesReplace Conditional with PolymorphismIntroduce Null ObjectIntroduce Assertion

Making Method Calls Simpler

Rename MethodAdd ParameterRemove ParameterSeparate Query from ModifierParameterize MethodReplace Parameter with Explicit MethodsPreserve Whole ObjectReplace Parameter with MethodIntroduce Parameter ObjectRemove Setting MethodHide MethodReplace Constructor with Factory MethodReplace Error Code with ExceptionReplace Exception with TestIntroduce GatewayIntroduce Expression Builder

Dealing with Generalization

Pull Up MethodPush Down MethodExtract ModuleInline ModuleExtract SubclassIntroduce InheritanceCollapse HierarchyForm Template MethodReplace Inheritance with DelegationReplace Delegation with HierarchyReplace Abstract Superclass with Module

Big Refactorings

Tease Apart InheritanceConvert Procedural Design to ObjectsSeparate Domain from PresentationExtract Hierarchy