Функциональная композиция

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

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

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

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

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

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

Функциональная композиция может быть как благом, так и анти-паттерном (функциональная декомпозиция). Например, у нас в программе может появится класс работающий с файлом. Назовём его SaveLoader, он открывает файл и начинает самостоятельно работать со сценой, проставляя загруженные данные объектам. Корректно ли это? Чаще всего нет, так как в нём много других ответственностей. Хоть мы и разобьём его на композицию различных методов, все же эти методы будут расположены совершенно не там, из-за чего мы прибьём ОО дизайн, и с помощью функций раздекомпозируем систему. Подробней об этом мы поговорим в следующих главах.

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

private static bool TryOpenDoor()
{
    Console.WriteLine("Привет! Сколько тебе лет?");
    var age = 0;

    while (int.TryParse(Console.ReadLine(), out age) == false)
    {
        Console.WriteLine("Это не число!");
    }

    if (age <= 18)
    {
        Console.WriteLine("Дверь не для тебя!");
        return false;
    }
    else
    {
        return true;
    }
}

Что мы можем сказать о нём? Во-первых этот метод сильно связан с консолью, во-вторых сам метод пестрит деталями. Его суть заключается в том, что дверь не открывается для тех, кому нет 18 лет. Как мы можем это представить?

Для начала давайте выделим метод чтения числа. Метод уже подсократился, не правда ли?

private static bool TryOpenDoor()
{
    Console.WriteLine("Привет! Сколько тебе лет?");
    var age = ReadInt();

    if (age <= 18)
    {
        Console.WriteLine("Дверь не для тебя!");
        return false;
    }
    else
    {
        return true;
    }
}

private static int ReadInt()
{
    int result = 0;
    while (int.TryParse(Console.ReadLine(), out result) == false)
    {
        Console.WriteLine("Это не число!");
    }

    return result;
}

Делаем ещё шажок:

private static void InteractWithDoor()
{
    Console.WriteLine("Привет! Сколько тебе лет?");
    var age = ReadInt();

    if (TryOpenDoor(age) == false)
    {
        Console.WriteLine("Дверь не для тебя!");
    }
}

private static bool TryOpenDoor(int age)
{
    return age >= 18;
}

private static int ReadInt()
{
    int result = 0;
    while (int.TryParse(Console.ReadLine(), out result) == false)
    {
        Console.WriteLine("Это не число!");
    }

    return result;
}

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

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

Если вы нашли ошибку, пожалуйста выделите её и нажмите Ctrl+Enter.


Leave a Reply

Your email address will not be published. Required fields are marked *