RefactorMCP catalog

Parameterise Method

Replaces several methods that do the same thing with different literal values by one method that takes those values as parameters, and makes every call pass the values of the method it called.

Recipe

  1. introduce-parameter on each literal of the first method that differs between the methods, in the order they appear. Its calls now pass the literal.
  2. rename the first method to the new name.
  3. For each other method, extract-method on its body with name the new name. Its body is the parameterised method's body with other literals, so Extract Method makes it a call of that method passing them.
  4. inline-method on the other method, so its callers call the parameterised method directly.
"steps": [
  { "refactoring": "introduce-parameter", "target": { "file": "Employee.cs", "selection": "marker" }, "arguments": { "name": "factor" } },
  { "refactoring": "rename", "target": { "symbol": "M:Payroll.Employee.TenPercentRaise(System.Decimal)" }, "arguments": { "name": "Raise" } },
  { "refactoring": "extract-method", "target": { "file": "Employee.cs", "range": "19:13-19:30" }, "arguments": { "name": "Raise" } },
  { "refactoring": "inline-method", "target": { "symbol": "M:Payroll.Employee.FivePercentRaise" } }
]

The plan's recipe changes the first method's signature with Change Signature; Introduce Parameter does that and replaces the literal in one step. Its "redirect the similar methods" is Extract Method named after the parameterised method, which turns code matching an existing method's body into a call of it.

Arguments

ArgumentMeaning
methodsthe similar methods, by name; the first becomes the parameterised method
namethe name of the parameterised method
parameterNamesa name for each literal that differs between the methods, in the order the literals appear

The target is the file declaring the methods: "target": { "file": "Employee.cs" }.

Precondition

  • At least two methods are named, each a block-bodied method of the same class with the same return type, parameters and static modifier, and none of them generic.
  • Their bodies are the same token for token, except for literals; a literal that differs has the same type in every method.
  • There is one parameter name for each position where a literal differs.
  • Each step's precondition holds: the parameter names are free in the first method, and the other methods are only called, never virtual, overridden or used as method groups.

Transformation

  • The first method takes a parameter for each differing literal, after its required parameters, with the literal's type, and is renamed. Its body uses the parameters where the literals were.
  • Each call of any of the methods calls the parameterised method, passing the literals of the method it called.
  • The other methods are deleted.

Preserved

  • What each call does: the parameterised method runs the same code with the values the called method had.
  • Other members of the class and their callers.

Limitations

  • Only literals can differ. Methods that differ in a name, an operator or a constant are refused.
  • A literal that appears more than once gets one parameter per appearance.
  • The methods must be declared in one file.

Error codes

CodeMeaning
too-few-methodsfewer than two methods are named
signatures-differthe methods differ in class, return type, parameters or static modifier
bodies-differthe bodies differ in more than literals of the same type
same-bodyno literal differs, so there is nothing to make a parameter
parameter-countthe number of parameter names differs from the number of differing literals
no-block-bodya method has no block body
name-conflicta parameter name is already used in the first method
polymorphic-methoda similar method is virtual, an override or an interface implementation
method-group-referencea similar method is used without being called

Cases

·

Diffs show each file from before to after. /*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.

#bodies-differ-rejected

Refuses methods that differ in more than literal values

refusal
TargetEmployee.cs
Arguments
methods["Raise", "Cut"]
name"Adjust"
parameterNames["factor"]
Refusesbodies-differ; every file is left unchanged
Employee.csinput
1 namespace Payroll
2 {
3 public class Employee
4 {
5 private decimal _salary;
6
7 public void Raise()
8 {
9 _salary *= 1.1m;
10 }
11
12 public void Cut()
13 {
14 _salary /= 1.1m;
15 }
16 }
17 }

#method-group-rejected

Refuses when a similar method is used as a method group, which Inline Method cannot redirect, undoing the steps already taken

refusal
TargetEmployee.cs
Arguments
methods["TenPercentRaise", "FivePercentRaise"]
name"Raise"
parameterNames["factor"]
Refusesmethod-group-reference; every file is left unchanged
Employee.csinput
1 using System;
2
3 namespace Payroll
4 {
5 public class Employee
6 {
7 private decimal _salary;
8
9 public void TenPercentRaise()
10 {
11 _salary *= 1.10m;
12 }
13
14 public void FivePercentRaise()
15 {
16 _salary *= 1.05m;
17 }
18
19 public Action Later()
20 {
21 TenPercentRaise();
22 return FivePercentRaise;
23 }
24 }
25 }

#parameter-count-rejected

Refuses when the number of parameter names does not match the number of literals that differ

refusal
TargetTariff.cs
Arguments
methods["MiddleBand", "TopBand"]
name"WithinBand"
parameterNames["top"]
Refusesparameter-count mentioning “2 literal(s) differ”; every file is left unchanged
Tariff.csinput
1 using System;
2
3 namespace Energy
4 {
5 public class Tariff
6 {
7 public int Charge(int usage)
8 {
9 return MiddleBand(usage) * 5 + TopBand(usage) * 7;
10 }
11
12 private int MiddleBand(int usage)
13 {
14 return Math.Max(0, Math.Min(usage, 200) - 100);
15 }
16
17 private int TopBand(int usage)
18 {
19 return Math.Max(0, Math.Min(usage, 1000) - 200);
20 }
21 }
22 }

#recipe

Runs the recipe: Introduce Parameter on the first method's literal, Rename it, Extract Method onto it from the similar method, then Inline Method on the similar method

successrecipe
Recipe
  1. introduce-parameter on Employee.cs, the /*[*/ … /*]*/ selection
    name"factor"
  2. rename on symbol M:Payroll.Employee.TenPercentRaise(System.Decimal)
    name"Raise"
  3. extract-method on Employee.cs, range 19:13-19:30
    name"Raise"
  4. inline-method on symbol M:Payroll.Employee.FivePercentRaise
Employee.csmodified
11 namespace Payroll
22 {
33 public class Employee
44 {
55 private decimal _salary;
66
77 public Employee(decimal salary)
88 {
99 _salary = salary;
1010 }
1111
12− public void TenPercentRaise()
12+ public void Raise(decimal factor)
1313 {
14− _salary *= /*[*/1.10m/*]*/;
15− }
16−
17− public void FivePercentRaise()
18− {
19− _salary *= 1.05m;
14+ _salary *= factor;
2015 }
2116
2217 public decimal Salary()
2318 {
2419 return _salary;
2520 }
2621 }
2722 }
Review.csmodified
11 namespace Payroll
22 {
33 public class Review
44 {
55 public void Apply(Employee employee, bool outstanding)
66 {
77 if (outstanding)
8− employee.TenPercentRaise();
8+ employee.Raise(1.10m);
99 else
10− employee.FivePercentRaise();
10+ employee.Raise(1.05m);
1111 }
1212 }
1313 }

#recipe-name-conflict-rejected

The recipe stops at its first step when Introduce Parameter refuses a parameter name the method already uses, and nothing changes

refusalrecipe
Recipe
  1. introduce-parameter on Employee.cs, the /*[*/ … /*]*/ selection
    name"bonus"
  2. inline-method on symbol M:Payroll.Employee.FivePercentRaise(System.Decimal)
Refuseserror mentioning “is already in use in”; every file is left unchanged
Employee.csinput
1 namespace Payroll
2 {
3 public class Employee
4 {
5 private decimal _salary;
6
7 public void TenPercentRaise(decimal bonus)
8 {
9 _salary = _salary * /*[*/1.10m/*]*/ + bonus;
10 }
11
12 public void FivePercentRaise(decimal bonus)
13 {
14 _salary = _salary * 1.05m + bonus;
15 }
16 }
17 }

#several-literals

Methods that return a value and take a parameter differ in two literals, which become parameters after the existing one, in the order they appear

success
TargetTariff.cs
Arguments
methods["MiddleBand", "TopBand"]
name"WithinBand"
parameterNames["top", "bottom"]
Tariff.csmodified
11 using System;
22
33 namespace Energy
44 {
55 public class Tariff
66 {
77 public int Charge(int usage)
88 {
9− return MiddleBand(usage) * 5 + TopBand(usage) * 7;
9+ return WithinBand(usage, 200, 100) * 5 + WithinBand(usage, 1000, 200) * 7;
1010 }
1111
12− private int MiddleBand(int usage)
12+ private int WithinBand(int usage, int top, int bottom)
1313 {
14− return Math.Max(0, Math.Min(usage, 200) - 100);
15− }
16−
17− private int TopBand(int usage)
18− {
19− return Math.Max(0, Math.Min(usage, 1000) - 200);
14+ return Math.Max(0, Math.Min(usage, top) - bottom);
2015 }
2116 }
2217 }

#similar-methods

Replaces two methods differing in one literal with one taking it as a parameter, as the recipe does; a caller in another file passes each method's literal

success
TargetEmployee.cs
Arguments
methods["TenPercentRaise", "FivePercentRaise"]
name"Raise"
parameterNames["factor"]
Employee.csmodified
11 namespace Payroll
22 {
33 public class Employee
44 {
55 private decimal _salary;
66
77 public Employee(decimal salary)
88 {
99 _salary = salary;
1010 }
1111
12− public void TenPercentRaise()
12+ public void Raise(decimal factor)
1313 {
14− _salary *= 1.10m;
15− }
16−
17− public void FivePercentRaise()
18− {
19− _salary *= 1.05m;
14+ _salary *= factor;
2015 }
2116
2217 public decimal Salary()
2318 {
2419 return _salary;
2520 }
2621 }
2722 }
Review.csmodified
11 namespace Payroll
22 {
33 public class Review
44 {
55 public void Apply(Employee employee, bool outstanding)
66 {
77 if (outstanding)
8− employee.TenPercentRaise();
8+ employee.Raise(1.10m);
99 else
10− employee.FivePercentRaise();
10+ employee.Raise(1.05m);
1111 }
1212 }
1313 }