Файл: Зачем нужен рефакторинг.docx

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 04.02.2024

Просмотров: 38

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Зачем нужен рефакторинг

Стройный, хорошо структурированный код легко читается и быстро дорабатывается. Но редко удаётся сразу сделать его таким. Разработчики спешат, в процессе могут меняться требования к задаче, тестировщики находят баги, которые нужно быстро исправить, или возникают срочные доработки, и их приходится делать второпях.

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

Чтобы решить все эти проблемы, делается рефакторинг программы. В новом проекте он нужен, чтобы:

  • сохранить архитектуру проекта, не допустить потери структурированности;

  • упростить будущую жизнь разработчиков, сделать код понятным и прозрачным для всех членов команды;

  • ускорить разработку и поиск ошибок.

Но любое приложение со временем устаревает: язык программирования совершенствуется, появляются новые функции, библиотеки, операторы, делающие код проще и понятнее. То, что год назад требовало пятидесяти строк, сегодня может решаться всего одной.

Поэтому даже идеальная когда-то программа со временем требует нового рефакторинга, обновляющего устаревшие участки кода.

Программный код предназначен не только для компьютера, но и для человека, который будет его дорабатывать. Плохо, если ему придётся неделю разбираться в исходниках, чтобы изменить в программе несколько строк. И не исключено, что этим человеком окажетесь вы сами.

Чем рефакторинг отличается от оптимизации

Рефакторинг — не оптимизация, хотя и может быть с нею связан. Часто его проводят одновременно с оптимизацией, поэтому понятия кажутся синонимами. Но у этих процессов разные цели.

Цель оптимизации — улучшение производительности программы, а рефакторинга — улучшение понятности кода. После оптимизации исходный код может стать сложнее для понимания.

После рефакторинга программа может начать работать быстрее, но главное — её код становится проще и понятнее.

Когда нужно срочно улучшать код


Признаки, показывающие, что назрела необходимость в рефакторинге:

  • Программа работает, но даже небольшие доработки сильно затягиваются из-за того, что каждый раз приходится долго разбираться в коде.

  • Разработчик постоянно не может точно сказать, сколько времени ему нужно на выполнение задачи, потому что “там надо вначале разбираться”.

  • Одинаковые изменения приходится вносить в разные места текста программы.

Такой код нужно срочно рефакторить, иначе он будет тормозить реализацию проекта и затруднять внесение правок.

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

Как делают рефакторинг

Рефакторинг — это маленькие последовательные улучшения кода. Чистить можно всё, но в первую очередь найдите эти проблемы:

  1. Мёртвый код. Переменная, параметр, метод или класс больше не используются: требования к программе изменились, но код не почистили. Мёртвый код может встретиться и в сложной условной конструкции, где какая-то ветка никогда не исполняется из-за ошибки или изменения требований. Такие элементы или участки текста нужно удалить.

  2. Дублирование. Один и тот же код выполняет одно и то же действие в нескольких местах программы. Вынесите эту часть в отдельную функцию.

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

    Примеры корректных имен: totalScore — переменная, означающая итоговый счёт в игре, maxWeight — максимальный вес. Для функций и методов лучше использовать глаголы, например: saveScore () — сохранить счет, setSize () — задать размер, getSpeed () — получить скорость.

  4. Слишком длинные функции и методы. Оптимальный размер этих элементов — 2-3 десятка строк. Если получается больше, разделите функцию на несколько маленьких и добавьте одну общую. Пусть маленькие выполняют по одной операции, а общая функция их вызывает.

  5. Слишком длинные классы. То же самое. Оптимальная длина класса — 20–30 строк. Разбейте длинный класс на несколько маленьких и включите их объекты в один общий класс.

  6. Слишком длинный список параметров функции или метода. Они только запутывают, а не помогают. Если все эти параметры действительно нужны, вынесите их в отдельную структуру или класс с понятным именем, а в функцию передайте ссылку на него.

  7. Много комментариев. Плохой код часто прикрывается обильными комментариями. Если почувствовали желание пояснить какой-то участок кода, попробуйте сначала его переписать, чтобы и так стал понятным. Бесполезные комментарии загромождают программу, а устаревшие и неактуальные вводят в заблуждение.


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

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

Код чистят и на этапе тестирования, когда всё уже готово и проверяется работоспособность программы. Тут разработчик выполняет требования тестировщиков и одновременно проводит рефакторинг.

Как правило, руководители проектов понимают важность рефакторинга и делают его элементом разработки. Особое место он занимает в экстремальном программировании, когда программисты попеременно то пишут код и разрабатывают тесты, то проводят рефакторинг написанного.

Не страдайте перфекционизмом! Если вы поправили какой-то кусочек кода, не надо перетряхивать всю программу, разыскивая, что ещё можно улучшить. Стремление к совершенству вечно, но лучше обойтись без фанатизма.

В чём опасности рефакторинга

Мы всё-таки меняем рабочий код. Тут можно не только всё упростить, но и сильно напортачить. Небрежный рефакторинг может отбросить выполнение проекта на дни и недели.

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

Рефакторьте постоянно и по чуть-чуть.

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

Пример рефакторинга кода на C#


Давайте рассмотрим на примере.

Задача:
В файл ODBC.INI добавить настройки принтера для устанавливаемой программы. Если настройки для данной программы уже имеются, то заменить их.
Имеется
решение:

void InstallDriver(string drive)
{
    string PathToODBCINI = Environment.GetEnvironmentVariable("windir",  EnvironmentVariableTarget.Machine) + @"\ODBC.INI";
    if (!File.Exists(PathToODBCINI))
    {
       try
       {
          File.Create(PathToODBCINI).Close();
       }
       catch (Exception e)
       {
          MessageBox.Show(e.Message);
       }
    }
    StreamReader sr = new StreamReader(PathToODBCINI,    System.Text.ASCIIEncoding.Default);
    string content = sr.ReadToEnd();
    sr.Close();
    int index = content.IndexOf("[ODBC Data Sources]");
    if (index >= 0)
    {
       int lastIndex = content.IndexOf("QEWSD=34751", index) + 11;
       try
       {
           content = content.Remove(index, lastIndex - index);
       }
       catch
       {
           MessageBox.Show(ERROR_WRONG_PREVIOUS_INSTALLATION);
       }
    };
    string path_to_driver = Helpers.AppExecFolder + "files\\driver.txt";
    if (File.Exists(path_to_driver))
    {
        sr = new StreamReader(path_to_driver);
        string driver_text = sr.ReadToEnd();
        driver_text = driver_text.Replace("{1}", drive);
        sr.Close();
        try
        {
            StreamWriter sw = new StreamWriter(File.OpenWrite(PathToODBCINI), System.Text.ASCIIEncoding.Default);
            string config = String.Format("{0}{1}", driver_text, content);
            sw.Write(config);
            sw.Close();
            MessageBox.Show("Installation completed");
        }
        catch
        {
            MessageBox.Show(e1.Message);
        }
     }
     else
     {
        MessageBox.Show(String.Format("Cannot find file {0}", path_to_driver));
     }
}

Один метод делает всё... Хороший пример того, как не надо писать код. Сколько разных запахов тут смешалось воедино...
Давайте рассмотрим, что делает этот код, в виде последовательности действий:
0. Находит (или создает, если файл не существует) ODBC.INI
1. Получает содержимое ODBC.INI
2. Проверяет наличие настроек для программы и удаляет их
3. Формирует новые настройки

4. Записывает новые настройки в ODBC.INI
Давайте для начала разобъём этот метод на 5 отдельных методов в соответствии с выделенными действиями. Что получается:

string GetODBCINIPath()
{
    string _PathToODBCINI = Environment.GetEnvironmentVariable("windir", EnvironmentVariableTarget.Machine) + @"\ODBC.INI";
    if (!File.Exists(_PathToODBCINI))
    {
        File.Create(_PathToODBCINI).Close();
    }
    return _PathToODBCINI;
}
string GetODBCINIContent(string PathToODBCINI)
{
    using (StreamReader sr = new StreamReader(PathToODBCINI, System.Text.ASCIIEncoding.Default))
    {
        string content = sr.ReadToEnd();
        return content;
    }
}
string GetClearDriverSettings(string commonSettings)
{
    string clearSettings = commonSettings;
    int index = commonSettings.IndexOf("[ODBC Data Sources]");
    if (index >= 0)
    {
        int lastIndex = commonSettings.IndexOf("QEWSD=34751", index) + 11;
        clearSettings = commonSettings.Remove(index, lastIndex - index);
    };
    return clearSettings;
}
string MakeNewSettings(string drive, string oldSettings)
{
    string driver_text = String.Empty;
    string path_to_driver = Path.Combine(Helpers.AppExecFolder, "files\\driver.txt");
    if (File.Exists(path_to_driver))
    {
       using(StreamReader sr = new StreamReader(path_to_driver))

       {
          string driver_text = sr.ReadToEnd();
          driver_text = driver_text.Replace("{1}", drive);
       }
    }
    string newSettings = String.Concat(driver_text, oldSettings);
    return newSettings;
}
bool SetNewDriverSettings(string new_settings, string PathToODBCINI)
{
    bool result = true;

    using (StreamWriter sw = new StreamWriter(File.OpenWrite(PathToODBCINI), System.Text.ASCIIEncoding.Default))
    {
       try
       {
           sw.Write(new_settings);
       }
       catch
       {
           result = false;
       }
    }
    return result;
}
void InstallDriver(string drive)
{
    string PathToODBCINI = GetODBCINIPath();
    string commonSettings = GetODBCINIContent(PathToODBCINI);
    string clearSettings = GetClearDriverSettings(commonSettings);
    string newSettings = MakeNewSettings(drive, clearSettings);
    if (SetNewDriverSettings(newSettings, PathToODBCINI))
    {
        MessageBox.Show("Installation completed");
    }
    else
    {
        MessageBox.Show("Error");
    }
}