Рефакторинг большой функции

234
06 марта 2017, 12:37

Дана функция на 200 строк, обрабатывающая большое количество данных, принимающая много параметров. Путём неимоверных усилий функция была порезана на 4 отдельных функции, но появилась следующая проблема: у каждой из функций одинаково большой список параметров. Если я вдруг захочу что-то изменить, то придётся исправлять список параметров во всех 4 функциях, а не в одной + общее решение уже 300 строк. Что можно сделать в такой ситуации? Какой рефакторинг использовать?

Оригинальная сигнатура (идентична натуральной):

template<typename value_type, typename some_other_type>
auto original_foo(const std::vector<value_type>& data_1, const std::map<int, some_other_type>& data_2, int param_1, int param_2, std::pair<int, int>& indirect_result) {
 //200 long long lines...
}

Моя попытка отрефакторить вырождается в подобные 4 функции:

template<typename value_type, typename some_other_type>
    auto solve_1_task(const std::vector<value_type>& data_1, const std::map<int, some_other_type>& data_2, int param_1, int param_2, std::pair<int, int>& indirect_result) {
     //50 lines...
     solve_2_task(data_1, data_2, param_1, param_2, indirect_result);
    }
Answer 1

Если вы делаете правильный рефакторинг, то следуя принципу Single Responsibility при описании методов, количество аргументов редко превышает значения 1, но не следует упираться в эту рекомендацию выключая логику и нарушая все здравые смыслы.

Я вижу, что обработка данных в каждой функции затрагивает у вас все данные. Для начала, посмотрите, нельзя ли как-то обрабатывать данные по отдельности? Вы разбили большую функцию на маленькую, но алгоритм остался тот же, может есть возможность не затрагивать все данные? Ведь то, что вы сделали не поменяло суть проблемы...

Лечим ваш код:

Переосмысление функций:

  1. Функция, в идеале, должна выполнять только одну операцию. Она должна выполнять ее хорошо и ничего другого она делать не должна. Чтобы убедиться в том, что функция "выполняет только одну операцию", необходимо проверить, что все команды функции находятся на одном уровне абстракции.

Рефакторинг переменных:

Если функция должна получать более двух или трех аргументов, весьма вероятно, что некоторые из этих аргументов стоит упаковать в отдельном классе. Рассмотрим следующие два объявления:

Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius)

Сокращение количества аргументов посредством создания объектов может показаться жульничеством, но это не так. Если переменные передаются совместно как единое целое (как переменные x и y в этом примере), то, скорее всего, вместе они образуют концепцию, заслуживающую собственного имени.

Далее, нужно посмотреть, нельзя ли в самом классе выполнять какие-то действия с переданными параметрами, чтобы не выполнять их в функции?

Некоторые правила рефакторинга переменных

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

  2. Вместо того чтобы передавать группу данных, полученных из другого объекта в качестве параметров, в метод можно передать сам объект, используя передачу всего объекта.

  3. Если есть несколько несвязанных элементов данных, иногда их можно объединить в один объект-параметр, применив замену параметров объектом. Предположим вы передавали start, end в функцию генерирующую отчет, а теперь просто передавайте обьект DateRange, где есть параметры start, end.

Способов рефакторинга вашего кода очень много и зависит от самого кода и выполняемой задачи, тяжело что-то сказать, когда не видишь кода, Вам стоит почитать книжки по рефакторингу кода, например "Чистый код. Создание, анализ и рефакторинг" Р. Мартина или книги по шаблонам проектирования, чтобы все правильно разнести на классы.

Answer 2

Сошлюсь на авторитета :) Но сначала - вы не просто механически поделили функцию на четыре? Слишком уж на это намекает то, что все параметры нужны во всех функциях...

Ну, а теперь - слово Фаулеру. О длинных списках параметров он пишет:

В длинных списках параметров трудно разобраться. Они зачастую противоречивы и сложны в применении, а кроме того, их приходится вечно изменять по мере возникновения необходимости в новых данных. Если же передавать методам объекты, то изменений потребуется меньше, так как для получения новых данных, скорее всего, хватит пары запросов.

Рефакторинг “Замена параметра вызовом метода” пригоден тогда, когда можно получить данные в одном параметре путем вызова метода объекта, который уже известен. Этот объект может быть полем или другим параметром. Рефакторинг “Сохранение всего объекта” позволяет заменить набор данных, получаемых от объекта, самим этим объектом. Если же имеется ряд элементов данных без логического объекта, можно воспользоваться рефакторингом “Введение объекта параметра”.

Есть одно важное исключение, когда такие изменения вносить не следует. Это случай, когда мы не хотим создавать явную зависимость между вызываемым и более крупным объектами. В таких случаях разумно распаковать данные и передавать их по отдельности в виде параметров, но отдавать себе отчет о стоимости этого решения. Если список параметров оказывается слишком длинным или модификации слишком частыми, лучше пересмотреть структуру зависимостей.

Answer 3

Почему последовательные вызовы задач образовали цепочку?
Лучше бы было вызывать ваши четыре задачи из оригинальной функции, последовательно выделяя некоторый код в отдельную функцию:

original_foo(params) {
  solve_1_task(params);
  solve_2_task(params);
  ...
}

По поводу параметров:
Если параметры действительно нужны всюду, лучшим решением, возможно, было бы выделение этой функциональности в класс.

Просто передайте все параметры в конструктор класса, и в методах пользуйтесь членами класса.

original_foo(params) {
  Foo<value_type, some_other_type> foo(params);
  foo.solve_1_task();
  ...
}  
READ ALSO
Найти вариант обхода графа длинной k C++

Найти вариант обхода графа длинной k C++

Есть матрица смежности a[n * m][n * m]Есть первая вершина v0 Есть число k

239
Проблема в совместном использовании curl и exe

Проблема в совместном использовании curl и exe

Я написал программу,которая работает с текстовым файлом и должна отправить его по emailДля отправки по email использовал curl

353
Реализация &ldquo;Жизни&rdquo; Конвея на JavaScript и DOM(Ошибка)

Реализация “Жизни” Конвея на JavaScript и DOM(Ошибка)

Добрый день, уважаемые господа! Написал тут реализацию "Жизни" Конвея кривую под стать своему навыку, выглядит все работающим, но на деле при...

238
Как распарсить сложный json?

Как распарсить сложный json?

Помогите пожалуйста распарсить jsonСначала опишу рабочий пример для простого json, а потом нерабочий пример для сложного json

297