DevGang
Авторизоваться

Рефакторинг кода — укрощение спагетти

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

Прежде чем мы начнем, что такое спагетти-код?

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

Спагетти-код
Спагетти-код

Давайте рассмотрим пример спагетти-кода:

using System;
class Program {
    static void Main(string[] args) {
        var random = new Random();
        var flag = random.Next(1, 100) > 50;
        while (true) {
            Console.Write("Enter a number: ");
            var input = Console.ReadLine();
            if (input == "exit") break;
            if (!int.TryParse(input, out int number)) {
                Console.WriteLine("Input is not a number. Try again.");
                continue;
            }
            if (flag) {
                if (number % 2 == 0) {
                    Console.WriteLine("The number is even.");
                    for (var i = 0; i <= 10; i++) {
                        if (i == 5) break;
                        Console.Write(i + " ");
                    }
                } else {
                    Console.WriteLine("The number is odd.");
                    for (var i = 0; i <= 10; i++) {
                        if (i == 7) break;
                        Console.Write(i + " ");
                    }
                }
            } else {
                if (number % 3 == 0) {
                    Console.WriteLine("The number is even.");
                    for (var i = 0; i <= 10; i++) {
                        if (i == 5) break;
                        Console.Write(i + " ");
                    }
                } else {
                    Console.WriteLine("The number is odd.");
                    for (var i = 0; i <= 10; i++) {
                        if (i == 7) break;
                        Console.Write(i + " ");
                    }
                }
            }
            Console.WriteLine();
        }
    }
}

Давайте разберемся в ситуации шаг за шагом и попытаемся распутать эту путаницу.

Определите проблему

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

Отсутствие модульности в коде проявляется в слишком большой функции Main, которая выполняет несколько задач. Чтобы решить эту проблему, мы разделим функцию Mainи выделим отдельные функции. Они будут извлечены в свои собственные функции или классы. Такой подход позволяет нам сгруппировать связанную логику вместе, способствует повторному использованию кода и улучшает общую структуру кодовой базы.

Другим важным аспектом, который нуждается в улучшении, является ограниченная обработка ошибок кода. Эффективная обработка ошибок имеет решающее значение для выявления неожиданных ситуаций и корректного выхода из них. Чтобы решить эту проблему, мы рассмотрим возможные исключения, которые могут возникнуть во время выполнения кода. Затем мы внедрим соответствующие механизмы обработки ошибок, такие как блоки try-catch, чтобы убедиться, что код плавно обрабатывает исключения и выдает полезные сообщения об ошибках.

Код содержит множество сложных условий if-else, что затрудняет его чтение и понимание. Чтобы упростить это, мы будем использовать такие методы, как операторы switch, полиморфизм или шаблоны проектирования, такие как шаблон стратегии. Эти подходы помогают оптимизировать условную логику и уменьшить ее сложность. Тем самым мы улучшаем читабельность кода, обслуживаемость и упрощаем его модификацию в будущем.

Разберите его на части

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

if (flag) {
    if (number % 2 == 0) {
        Console.WriteLine("The number is even.");
        for (var i = 0; i <= 10; i++) {
            if (i == 5) break;
            Console.Write(i + " ");
        }
    } else {
        // Other similar codes...
    }
}

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

  1. Создайте функцию или класс с именем DefinitionNumberType, которые принимают число в качестве входных данных и обрабатывают логику для определения типа числа. Эта функция/класс должны анализировать число и возвращать его тип, например «четное», «нечетное», «простое» или любые другие соответствующие категории.
  2. Затем создайте отдельную функцию или класс с именем PrintNumbersUpTo, который обрабатывает логику вывода чисел до заданного предела. Эта функция/ класс должны принимать ограничение в качестве входных данных и выполнять итерацию по числам, вызывая функцию DefinitionNumberType для каждого числа и выводя результаты.
static string DetermineNumberType(int number) { /* logic here */ }
static void PrintNumbersUpTo(int terminationNumber) { /* logic here */ }

Используйте описательные имена

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

До:

var random = new Random();
var flag = random.Next(1, 100) > 50;

После:

private static readonly Random randomNumberGenerator = new Random();
bool isStrategyForEvenNumbers = randomNumberGenerator.Next(1, 100) > 50;

Используйте шаблоны и принципы проектирования

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

До:

if (flag) {
    if (number % 2 == 0) {
        // some code
    } else {
        // some code
    }
} else {
    // more code
}

После:

private delegate bool NumberClassificationStrategy(int number);

private static readonly Dictionary<bool, NumberClassificationStrategy> numberTypeDeterminationStrategies = 
new Dictionary<bool, NumberClassificationStrategy>
{
    { true, IsEven },
    { false, IsDivisibleByThree }
};

Используя Strategy Pattern, мы получаем более модульную и удобную в сопровождении структуру кода. Это упрощает сложную условную логику, улучшает читаемость кода и упрощает расширение или изменение поведения в будущем.

Улучшите обработку ошибок

Исходный код имел минимальную обработку ошибок. Давайте улучшим это.

До:

if (!int.TryParse(input, out int number)) {
    Console.WriteLine("Input is not a number. Try again.");
    continue;
}

После:

try
{
    if (!int.TryParse(input, out int number)) {
        Console.WriteLine("Input is not a number. Try again.");
        continue;
    }
}
catch (Exception ex)
{
    Console.WriteLine($"An error occurred: {ex.Message}");
}

Последние штрихи

Мы можем улучшить читаемость, используя интерполяцию строк.

До:

Console.Write(i + " ");

После:

Console.Write($"{i} ");

И вот оно! Наш окончательный код после рефакторинга стал четким, модульным и «легким» для понимания.

using System;
using System.Collections.Generic;

class Program
{
    private const int MaxPrintLimit = 10;
    private const int ExitCommandCode = -1;
    private const int RandomThresholdForNumberType = 50;
    private const string EvenNumberIndicator = "even";
    private const string OddNumberIndicator = "odd";

    private static readonly Random randomNumberGenerator = new Random();

    private delegate bool NumberClassificationStrategy(int number);

    private static readonly Dictionary<bool, NumberClassificationStrategy> numberTypeDeterminationStrategies = 
    new Dictionary<bool, NumberClassificationStrategy>
    {
        { true, IsEven },
        { false, IsDivisibleByThree }
    };

    static void Main()
    {
        try
        {
            while (true)
            {
                int userInput = RetrieveUserInput();
                if (userInput == ExitCommandCode) break;

                string determinedNumberType = DetermineNumberType(userInput);
                Console.WriteLine($"The number is {determinedNumberType}.");
                PrintNumberSequence(determinedNumberType);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }

    static int RetrieveUserInput()
    {
        while (true)
        {
            try
            {
                Console.Write("Enter a number: ");
                string input = Console.ReadLine();

                if (input == "exit") return ExitCommandCode;

                if (int.TryParse(input, out int parsedNumber)) return parsedNumber;

                throw new FormatException("Input is not a number. Try again.");
            }
            catch (FormatException fe)
            {
                Console.WriteLine(fe.Message);
            }
        }
    }

    static string DetermineNumberType(int number)
    {
        bool randomFlag = randomNumberGenerator.Next(1, 100) > RandomThresholdForNumberType;

        if (numberTypeDeterminationStrategies.TryGetValue(randomFlag, out NumberClassificationStrategy numberClassificationMethod))
        {
            return numberClassificationMethod(number) ? EvenNumberIndicator : OddNumberIndicator;
        }
        else
        {
            throw new KeyNotFoundException("The strategy for number type determination could not be found.");
        }
    }

    static bool IsEven(int number)
    {
        return number % 2 == 0;
    }

    static bool IsDivisibleByThree(int number)
    {
        return number % 3 == 0;
    }

    static void PrintNumberSequence(string numberType)
    {
        int terminationNumber = numberType == EvenNumberIndicator ? 5 : 7;

        PrintNumbersUpTo(terminationNumber);
        Console.WriteLine();
    }

    static void PrintNumbersUpTo(int terminationNumber)
    {
        for (int i = 0; i <= MaxPrintLimit; i++)
        {
            if (i == terminationNumber)
            {
                return;
            }
            Console.Write($"{i} ");
        }
    }
}

Цикломатическая сложность

Цикломатическая сложность (CC) — это показатель, который измеряет сложность программы. Он определяет количество независимых путей через исходный код программы. Для простых конструкций if-else вы можете оценить цикломатическую сложность, подсчитав точки принятия решения (например, if, while для операторов) и добавив единицу.

В старом коде:

  • 5 if блоков
  • 1 while цикл
  • 2 for цикла

Суммируя их и прибавляя единицу, цикломатическая сложность равна 9.

В новом коде:

  • 2 if блока
  • 1 while цикл
  • Нет циклов for(инкапсулированы в метод)

Цикломатическая сложность нового кода равна 4.

Уменьшение цикломатической сложности упрощает код, снижает вероятность ошибок и улучшает удобство обслуживания.

Напоследок

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

Помните, что хороший код зависит не от того, насколько сложным вы можете его создать, а от того, насколько простым вы можете его сделать. Как сказал Альберт Эйнштейн:  “Все должно быть сделано настолько простым, насколько это возможно, но не проще”. Счастливого кодирования!

Источник:

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу