Замена переменной вызовом метода. Полезно, когда количество локальных переменных в методе настолько много, что сложно применить Extract Method, когда эти переменные переплелись между собой:
public void PrintStatistic(IEnumerable<Month> months)
{
var avg = MonthDecimal.Average(x => x.Value);
if (avg > 200)
{
Console.WriteLine($"AVG: {avg - 10}");
}
else
{
Console.WriteLine($"AVG: {avg}");
}
}
Тут локально вводится переменная avg, минус которой - локальная область видимости, эта переменная может понадобиться еще где-то в классе.Также ввод дополнительных переменных увеличивает время на чтение кода. Отрефакторим применив замену локальной переменной вызовом метода:
public decimal Avg => MonthDecimal.Average(x => x.Value);
public void PrintStatistic(IEnumerable<Month> months)
{
if (Avg > 200)
{
Console.WriteLine($"AVG: {Avg - 10}");
}
else
{
Console.WriteLine($"AVG: {Avg}");
}
}
Замечу что в данном случае я выделил свойство, т.к в для таких целей в C# есть более удобный механизм чем метод
Когда в методе происходят очень сложные вычисления, то нужно пояснять каждый их шаг, тоже самое касается и длинных алгоритмов:
public string CustomerMessage(string[] messages)
{
if (messages.First() == "/api")
return "Use api";
if (messages.First() == "/mobile")
return "Use mobile";
throw new ArgumentException();
}
public string CustomerMessage(string[] messages)
{
var firstMessageOpenApiConnection = messages.First().Trim().ToLower() == "/api";
var firstMessageOpenMobileConnection = messages.First().Trim().ToLower() == "/mobile";
if (firstMessageOpenApiConnection)
return "Use dekstop api";
if (firstMessageOpenMobileConnection)
return "Use mobile api";
throw new ArgumentException();
}
Если первое сообщение должно открыть декстопное соединение, то вернется сообщение об использовании декстопного апи, иначе мобильного
Это простая концепция, заключается в том, что не нужно использовать одну переменную для всех вычислений:
public void Calculate(int a, int b)
{
var temp = (a + b) / 2;
if (a > b)
{
temp += a;
}
else
{
temp += b;
}
Console.WriteLine(temp);
}
Выделили еще одну временную переменную, тем самым улучшив читаемость кода, за счет рефакторинга имен, следующий шаг - избавление от переменной result и возвращение значения сразу с помощью return statement
public void Calculate(int a, int b)
{
var average = (a + b) / 2;
double result = 0;
if (a > b)
{
result += a;
}
else
{
result += b;
}
Console.WriteLine(result);
}
При рефакторинге длинного метода временные переменные могут мешать Extract Method, поэтому нужно воспользоваться Split Temporary Variable, и только потом Extract Method
Еще один очевидный прием, не стоит менять значение переменных в методе, конечно Фаулер оговаривается, что не считает изменение поля(но не ссылки) ссылочной переменной чем-то плохим, в основном речь идет о типах значениях, в длинных методах это может привести к ошибкам, когда программист не ожидает, что параметр может где-то измениться, также это противоречит концепции параметра, потому что он передается в функцию и должен быть неизменяемым во время ее выполнения(оговорка, про ссылочные переменные): Фаулер против такого:
public SetPerson( Person person){
person = new Person(){Name = "ADAM"};
}
\\\
var p = new Person();
p.Name = "Adam";
p.SetPerson();
Assert.Equal("ADAM",x.Name); // Failure
И он прав, потому что в методе SetPerson я всего лишь поменял указатель на объект, старый объект Person, попрежнему хранится в куче и p указывает на него
Мощный прием, разбивающий запутанный метод на несколько других, тем самым улучшая читаемость кода:
private decimal GetRandomCoeff => 123m;
public decimal SalaryCalculate(IEnumerable<Month> months, decimal coeff)
{
var resultCoef = 0m;
if (coeff < GetRandomCoeff)
resultCoef = GetRandomCoeff;
else
resultCoef = coeff;
var salaryMoreWhen200 = false;
var monthsWhenSalaryMore200 = new List<Month>();
var totalSalary = SalaryByMonth.Sum(x =>
{
decimal value;
if (!months.Contains(x.Key))
return 0;
switch (x.Key)
{
case Month.April:
case Month.December:
value = x.Value;
break;
case Month.May:
value = x.Value + 12;
break;
default:
value = x.Value * 1.5m;
break;
}
if (value > 200)
{
salaryMoreWhen200 = true;
monthsWhenSalaryMore200.Add(x.Key);
}
return value;
});
var name = Name.ToUpper().Take(1).ToString() + Name.Skip(1);
if (salaryMoreWhen200)
return totalSalary * resultCoef;
return totalSalary;
}
Сложный метод, который можно вынести в отдельную сущность:
public class PersonCalculator
{
readonly Person _person;
private readonly IEnumerable<Month> _months;
private readonly decimal _coefficient;
public PersonCalculator(Person person, IEnumerable<Month> months, decimal coefficient)
{
_person = person;
_months = months;
_coefficient = coefficient;
}
public decimal SalaryCalculate(IEnumerable<Month> months, decimal coeff)
{
var salaryMoreWhen200 = false;
var monthsWhenSalaryMore200 = new List<Month>();
var totalSalary = _person.SalaryByMonth.Sum(x =>
{
decimal value;
if (!months.Contains(x.Key))
return 0;
switch (x.Key)
{
case Month.April:
case Month.December:
value = x.Value;
break;
case Month.May:
value = x.Value + 12;
break;
default:
value = x.Value * 1.5m;
break;
}
if (value > 200)
{
salaryMoreWhen200 = true;
monthsWhenSalaryMore200.Add(x.Key);
}
return value;
});
if (salaryMoreWhen200)
return totalSalary * _coefficient;
return totalSalary;
}
}
Теперь этот код можно разбивать на методы, потому что все переменные видимы на уровне класса, также если у класса, есть какие-то private переменные, то нужно передать их в ctor PersonCalculator, самое прекрасное в этом приеме то, что отрефакторив PersonCalculator, можно развязать все зависимости от временных переменных и вернуть метод обратно в класс Person
Прием замены старого алгоритма на новый, прямой замены с удалением старого