Skip to content

Instantly share code, notes, and snippets.

  • Save eterekhin/7506305f5f8a3cca4707b3cbb2cac970 to your computer and use it in GitHub Desktop.
Save eterekhin/7506305f5f8a3cca4707b3cbb2cac970 to your computer and use it in GitHub Desktop.
Martin Fowler.Replace Temp with Query.Introduce Explaining Variable.Split Temporary Variable.Remove Assignments to Parameters.Replace Method with Method Object.Substitute Algorithm

Replace Temp With Query

Замена переменной вызовом метода. Полезно, когда количество локальных переменных в методе настолько много, что сложно применить 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# есть более удобный механизм чем метод

Introduce Explaining Variable

Когда в методе происходят очень сложные вычисления, то нужно пояснять каждый их шаг, тоже самое касается и длинных алгоритмов:

        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();
        }

Если первое сообщение должно открыть декстопное соединение, то вернется сообщение об использовании декстопного апи, иначе мобильного

Split Temporary Variable

Это простая концепция, заключается в том, что не нужно использовать одну переменную для всех вычислений:

        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

Remove Assignments to Parameters

Еще один очевидный прием, не стоит менять значение переменных в методе, конечно Фаулер оговаривается, что не считает изменение поля(но не ссылки) ссылочной переменной чем-то плохим, в основном речь идет о типах значениях, в длинных методах это может привести к ошибкам, когда программист не ожидает, что параметр может где-то измениться, также это противоречит концепции параметра, потому что он передается в функцию и должен быть неизменяемым во время ее выполнения(оговорка, про ссылочные переменные): Фаулер против такого:

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 указывает на него

Replace Method with Method Object

Мощный прием, разбивающий запутанный метод на несколько других, тем самым улучшая читаемость кода:

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

Substitute Algorithm

Прием замены старого алгоритма на новый, прямой замены с удалением старого

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