За 9 лет разработки ПО  я периодически выступал в  роли ментора и сталкивался с проблемой, которую недавно озвучил начинающий программист после онлайн-курсов:

«Не понимаю, как делить код на классы».

Оказалось, на курсах учили языку, но не программированию. А ведь язык — лишь инструмент, и принципы проектирования кода универсальны для разных языков программирования.

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

Так и родилась идея написать статью «Шаблоны и принципы деления кода на классы».

Типы классов и шаблоны

Все классы я делю на 3 основных типа:

  1. Дата-класс

  2. Класс-управленец

  3. Класс-исполнитель

Дата-класс

Самый простой тип класса. Его объекты статичны — они ничего не делают в программе, все действия происходят над самим объектом.

Назначение:

хранить данные

Аналогия
из жизни:

ящик с предметами

Характеристики:

- отсутствует бизнес-логика, зависимости от других классов и взаимодействие с внешними системами;

- в полях классах хранятся данные;

- методы класса — это setter'ы и getter'ы, иногда вспомогательные методы для работы с данными внутри такого класса.

Популярные шаблоны

Data Access Object (DAO)

Дата-класс, описывающий схему хранилища данных (БД). Упрощает работу с БД, особенно в ORM–фреймворках, и отделяет бизнес-модели данных от моделей данных БД.

Data Transfer Object (DTO)

Дата-класс, описывающий модель данных для передачи между программами. Отделяет внутреннюю модель данных программы от модели передачи.

Value Object

Дата-класс, состояние которого не меняется после создания.

Класс-управленец

Класс, который координирует выполнение действий в программе.

Назначение:

- описывать бизнес-процессы или алгоритмы на языке программирования; 

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

Аналогия
из жизни:

конвейер

Характеристики:

- хранит ссылки на объекты классов-исполнителей и(или) на другие классы-управленцы;

- не хранит никакие данные;

- ничего не выполняет сам, только управляет процессом через делегирование.

Популярные шаблоны

Controller

Класс-управленец с несколькими точками входа, где каждая запускает отдельную функцию. «Запускает функцию» значит, что класс делегирует выполнение другим объектам, обычно сервисам.

Service

Класс-управленец, описывающий набор операций (процессов, алгоритмов) в рамках одного домена. Не делает действия сам, а делегирует их классам-исполнителям.

Pipeline

Класс-управленец с одной точкой входа, реализующий последовательное выполнение шагов без привязки к домену. Важно! Класс сам шаги не выполняет, он делегирует их выполнение другим классам (исполнителям или сервисам).

Класс-исполнитель

Класс, выполняющий конкретную работу в рамках одного действия процесса.

Назначение:

выполнить работу в рамках одного действия и опционально вернуть результат

Аналогия
из жизни:

почтальон

Характеристики:

- может хранить данные в полях класса, необходимые для выполнения работы;

- может хранить ссылки на другие классы-исполнители;

- может взаимодействовать с внешними системами;

- выполняет реальные действия в программе.

Популярные шаблоны

Класс-утилита

Класс-исполнитель с множеством методов, объединённых одним доменом. Не привязан к конкретному классу и может использоваться несколькими классами.

Класс-помощник (класс-компаньон)

Класс-исполнитель, аналогичный классу-утилите, но привязанный к конкретному классу (обычно одному).

Lambda-объект

Класс-исполнитель, созданный с помощью специального синтаксиса под названием lambda-выражение. Особенность — наличие всего одного абстрактного метода, действие которого определяется при создании экземпляра. Вызов одного и того же метода у разных экземпляров может запускать выполнение абсолютно разных действий.

Схема типов классов

Этих трёх типов достаточно для организации программы любой сложности.

(дополнительно) Объединение типов (классы-гибриды)

Когда есть разделение на типы, то рано или поздно возникает вопрос: :

а можно ли их объединять?

Отвечу так:

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

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

Пример хорошего объединения типов

Класс Result

Назначение:

позволяет выполнить действие над объектом, хранящимся внутри Result, если предыдущее действие завершилось успешно.

Какие типы объединяет:

Дата-класс (Value Object) и Класс-исполнитель (Класс-компаньон)

Характеристики:

- не меняет своё состояние после создания (Value Object)

- выполняет определённые действия над дата-классом и привязан только к нему (Класс-компаньон)

Читатель, который знаком с функциональным программированием, сразу узнает один из функциональных типов Either (Либо). Несмотря на функциональную природу, он отлично вписывается в предложенную типизацию классов.

Принципы деления кода на классы

К текущему моменту я описал типы классов, несколько популярных шаблонов и немного раскрыл тему объединения типов классов. Теперь настало время поговорить о принципах. Их всего два:

  1. Деление по домену

  2. Деление по роли

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

И есть два важных (!) момента, без которых деление корректно работать не будет:

  • принципы деления нужно применять всегда при создании нового класса и определении его назначения;

  • нужно применять оба принципа вместе, а не по отдельности.

Деление по домену

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

Сложность этого деления заключается в том, что слово «домен» имеет довольно обобщённое значение и с английского языка означает «область интересов». А интерес — это очень относительное понятие, которое зависит от ситуации и человека, выполняющего деление. Причём, человек в разной ситуации может по-разному сделать деление кода по домену.

Но не смотря на это, чаще всего деление по домену — это деление по классу (типу) предмета. Например, вынести в отдельный класс весь код, который работает только с сущностью Customer. Или например, вынести весь код в отдельный класс, который отправляет REST-запросы, а в другой класс — SOAP-запросы.

Вооружившись этим принципом, уже можно делить код на классы и организовывать структуру проекта. Но есть проблема...

Деление по домену — это деление большими кусками.

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

Обычно используется другой подход, а именно, вокруг крупного класса создаются классы-компаньоны, которые забирают часть его кода. Такой процесс напоминает разделение монолита на микросервисы, только микросервисы со временем заменяют собой монолит, и у каждого свой домен. А в случае классов, монолит становится тоньше, но обрастает свитой классов-компаньонов, которые просто забирают часть кода, оставаясь в том же домене. Такой «класс-король» со своей свитой капризен в поддержке, тестировании и добавлении новых функций. Я не рекомендую этот подход.

Как же тогда уменьшить объём кода в классе после деления по домену?

Дать новому классу специализацию, другими словами, определить его роль.

Деление по роли

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

У класса должна быть только одна роль, максимально специфичная. Соблюдение этого правила означает следование принципу единственной ответственности (Single Responsibility Principle).

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

Приведу пример роли для класса. Возьму роль Client. С разными доменами можно создать множество классов: RestClient, SoapClient, Repository (тоже Client, только для работы с БД). Другими словами, роли могут повторяться в разных доменах, как в RestClient и SoapClient, где Rest, Soap — домены, а Client — роль.

Пример

Теперь расcмотрю пример, чтобы продемонстрировать работу принципов и описанные типы классов.

Представим проект:

Веб-сервис (веб-приложение), позволяющий пользователю выполнять математические операции.

Пользовательский сценарий:
  1. Пользователь заходит на сайт.

  2. Выбирает из списка математическую операцию.

  3. Заполняет необходимые поля.

  4. Нажимает кнопку «Выполнить».

Ожидаемый результат:

  • в поле «Результат» отобразится текст с результатом операции.

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

Теперь представим задачу:

Реализовать первую в математическом домене операцию — «умножение двух чисел». Требования: на входе — строка, на выходе — тоже строка.

(Задача специально упрощена, чтобы сосредоточиться на разделении кода по классам, а не на деталях реализации)

Коддинг

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

Первая версия кода (по структуре) обычно выглядит так:

public class MathService {

    public String multiply(String strNumber1, String strNumber2) {
        int num1 = Integer.parseInt(strNumber1);
        int num2 = Integer.parseInt(strNumber2);
        int result = num1 * num2;
        String strResult = new Integer(result).toString();
        return strResult;
    }
}

Как образовался этот класс?

Разработчик взял домен основной бизнес-операции веб-приложения и обозначил его словом Math, затем назначил ему роль Service.

Выделение кода в класс произошло, как только определились домен и роль будущего класса.  Казалось бы, отличная реализация —  код выглядит лаконично  и просто. Но опытные разработчики могут не согласиться. Почему? Попробуем определить тип класса.

Какой тип у получившегося класса?

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

Почему объединение типов класс-управленец и класс-исполнитель не самое удачное?

Объединив эти два типа, вы получаете играющего тренера, который и руководит игроками, и сам играет. Другими словами, класс получает две роли.

А в принципе «Деление по роли», я делал важное уточнение:

у класса должна быть только одна роль, максимально специфичная. Соблюдение этого правила означает следование принципу единственной ответственности (Single Responsibility Principle).

В приведённом примере разработчик взял слишком обобщённую роль — веб-сервис. Именно этот смысл заключён в слове Service в названии класса. Поэтому текущий класс следовало бы переименовать в MathWebService, чтобы точнее обозначить его домен и роль:

public class MathWebService {

    public String multiply(String strNumber1, String strNumber2) {
        int num1 = Integer.parseInt(strNumber1);
        int num2 = Integer.parseInt(strNumber2);
        int result = num1 * num2;
        String strResult = new Integer(result).toString();
        return strResult;
    }
}

Какие роли у класса MathWebService?

Если смотреть на реализацию метода multiply, то можно увидеть, что метод делает два вида операций:

  • парсинг строк в числа и обратно;

  • выполнение математической операции.

Эти две операции — роли классов-исполнителей. Однако разработчики часто упускают другую роль, которая неявно присутствует в этом методе — процесс (алгоритм) выполнения операции multiplyс точки зрения веб-сервиса:

  1. распарсить входные данные;

  2. выполнить математическую операцию умножения;

  3. преобразовать результат в строку;

  4. вернуть ответ в виде строки.

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

У каждого процесса (алгоритма) есть шаги. Каждый шаг с точки зрения класса-управленца всегда отвечает на вопрос «Что делать?», но не включает в себя информацию «Как делать?». Перечитайте шаги процесса операции multiply и задайте к каждому пункту эти два вопроса: «Что делать?», «Как делать?».

Ни один пункт процесса не отвечает на вопрос «Как делать?» — так и должно быть. Но тогда возникает вопрос: кто же будет отвечать на вопрос «Как делать?»

Ответственность за «Как делать» лежит на  классе-исполнителе.

Таким образом, у класса MathWebService фактически три роли:

  1. описание процесса (алгоритма) работы метода выполнения пользовательского запроса (Управленец);

  2. реализация шага процесса: парсинг (Исполнитель);

  3. реализация шага процесса: математическая операция (Исполнитель).

Как же тогда поделить MathWebService на классы?

Самую сложную задачу мы уже выполнили: выделили три специфичные роли. Обычно с определением домена сложностей не возникает — чаще проблема в определении роли класса и его зоны ответственности.

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

Первое, что сделаю: выделю операцию конвертации «строка -> число, число -> строка» в два отдельных метода:

public class MathWebService {

    public String multiply(String strNumber1, String strNumber2) {
        int num1 = toInt(strNumber1);
        int num2 = toInt(strNumber2);
        int result = num1 * num2;
        String strResult = toString(result);
        return strResult;
    }

    private int toInt(String strNumber) {
        return Integer.parseInt(strNumber);
    }

    private int toString(Integer number) {
        return number.toString();
    }
}

Напомню, что я определил тип класса MathWebService как класс-управленец, где каждый метод — это название процесса. Методы toInt(String) и toString(Integer) не являются процессами веб-сервиса, они делают определённую работу внутри этих процессов (в данном случае, операции multiply), а значит они должны относиться к классу-исполнителю. Поэтому пора выделить их в отдельный класс. Согласно принципам, при создании класса нужно сделать два действия: определить домен и роль. Я определил их так: домен — «работа с числами», роль «конвертер».

дополнительное пояснение

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

Поэтому класс назову NumberConverter. Вот так выглядит код:

public class NumberConverter {

    public int toInt(String strNumber) {
        return Integer.parseInt(strNumber);
    }

    public int toString(Integer number) {
        return number.toString();
    }
}

NumberConverter — типичный класс-исполнитель. Теперь добавлю экземпляр класса NumberConverter в MathWebService и перепишу его код так, чтобы он делегировал выполнение новому классу.

public class MathWebService {
    private final NumberConverter numberConverter = new NumberConverter(); // для простоты DI не буду использовать в примере.

    public String multiply(String strNumber1, String strNumber2) {
        int num1 = numberConverter.toInt(strNumber1);
        int num2 = numberConverter.toInt(strNumber2);
        int result = num1 * num2;
        String strResult = numberConverter.toString(result);
        return strResult;
    }
}

Теперь класс MathWebService полностью делегирует задачи парсинга классу-исполнителю NumberConverter. Осталось делегировать выполнение операции умножения. Буду действовать по уже проверенной схеме: вынести в отдельный метод, а затем —  в отдельный класс.

public class MathWebService {
    private final NumberConverter numberConverter = new NumberConverter(); // для простоты DI не буду использовать в примере.

    public String multiply(String strNumber1, String strNumber2) {
        int num1 = numberConverter.toInt(strNumber1);
        int num2 = numberConverter.toInt(strNumber2);
        int result = multiply(num1, num2);
        String strResult = numberConverter.toString(result);
        return strResult;
    }

    private int multiply(int num1, int num2) {
        return num1 * num2;
    }
}

Хотя имя метода multiply(int, int) совпадает с методом multiply(String, String), в Java это разные методы из-за разных типов аргументов. Чтобы не путаться, можно переименовать multiply(String, String), например в multiplyFeature(String, String):

public class MathWebService {
    private final NumberConverter numberConverter = new NumberConverter(); // для простоты DI не буду использовать в примере.

    public String multiplyFeature(String strNumber1, String strNumber2) {
        int num1 = numberConverter.toInt(strNumber1);
        int num2 = numberConverter.toInt(strNumber2);
        int result = multiply(num1, num2);
        String strResult = numberConverter.toString(result);
        return strResult;
    }

    private int multiply(int num1, int num2) {
        return num1 * num2;
    }
}

Теперь вынесу метод multiply(int, int) в отдельный класс. Определю его домен как «математика», и роль как «операции», поэтому назову класс MathOperations:

public class MathOperations {

    public int multiply(int num1, int num2) {
        return num1 * num2;
    }
}

MathOperations — типичный класс-исполнитель. Теперь добавлю экземпляр класса MathOperations в MathWebService и перепишу код так, чтобы он делегировал выполнение новому классу.

public class MathWebService {
    private final NumberConverter numberConverter = new NumberConverter(); // для простоты DI не буду использовать в примере.
    private final MathOperations mathOperations = new MathOperations(); // для простоты DI не буду использовать в примере.

    public String multiplyFeature(String strNumber1, String strNumber2) {
        int num1 = numberConverter.toInt(strNumber1);
        int num2 = numberConverter.toInt(strNumber2);
        int result = mathOperations.multiply(num1, num2);
        String strResult = numberConverter.toString(result);
        return strResult;
    }
}

Теперь MathWebService стал полноценным классом-управленцем. Он описывает процесс выполнения фичи, но не содержит деталей реализации,  и делегируя выполнение шагов классам-исполнителям. Согласно типизации, MathWebService соответствует шаблону Сервис, а NumberConverter и MathOperations следуют шаблону Класс-утилита.

На этом пример деления кода на классы завершён.

Кто-то возразит: зачем создавать столько классов ради 4 строк кода в методе?!

(дополнительно) мотивация на классовое деление

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

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

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

Возможно, первая мысль — добавить новый метод в класс MathWebService, ведь конвертация весов — тоже математическая операция. Но представим, что в MathWebService уже есть куча методов: сложение, вычитание, корень квадратный, возведение в степень и логарифм. Эти операции явно соответствуют домену «математика» и роли «сервис для математических операций». Конвертация килограммов в граммы ещё как-то подходит по домену, но не по роли.

Поэтому логичнее создать новый класс. Я определил для него домен «веса», а роль «сервис конвертации величин». Так класс и назвал MeasureConverterWebService.

public class MeasureConverterWebService {
    private final NumberConverter numberConverter = new NumberConverter(); // для простоты DI не буду использовать в примере.
    private final MathOperations mathOperations = new MathOperations(); // для простоты DI не буду использовать в примере.

    public String kilosToGramFeature(String strKilos) {
        int kilos = numberConverter.toInt(strKilos);

        int result = mathOperations.multiply(kilos, 1000);

        String strResult = numberConverter.toString(result);
        return strResult;
    }
}

Обратите внимание, что для создания MeasureConverterWebService в проекте уже были все необходимые компоненты: NumberConverter и MathOperations. Разработчик собрал метод kilosToGramFeature из имеющегося кода, как конструктор. Более того, к моменту написания этого метода код NumberConverter и MathOperations уже был протестирован и есть уверенность в его работоспособности. А любой новый код ещё нужно протестировать и проверить реальными пользователями.

Заключение

В заключение подсвечу основные моменты:

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

  • Дата-класс нужен для обмена данными между классами и внешним миром, класс-управленец — для описания процесса (алгоритма) фичи в программе, класс-исполнитель — для выполнения реальных действий.

  • С помощью трёх типов классов можно организовать хорошую структуру программы любой сложности.

  • Принципы деления кода на классы рекомендую использовать всегда при создании нового класса.

важно на заметку

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

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

Возможно кто-то удивится, что я не упомянул в статье принципы объектно-ориентированного программирования (ООП), DRY или SOLID. Это важные принципы, но у них немного другое назначение, хотя они тоже могут быть связаны с делением кода на классы. Кто-то, посмотрев на пример, может кинуться в меня принципами KISS или YAGNI. Что ж скажу... это очень дискуссионная тема, и вы можете написать на неё свою статью, а моя на этом заканчивается.

Всем доброго времени суток.

Комментарии (6)


  1. akardapolov
    30.10.2025 13:03

    Все классы я делю на 3 основных типа:

    1. Дата-класс

    2. Класс-управленец

    3. Класс-исполнитель

    Получается дата-класс - это данные без логики, т.е. анемичная модель. Как быть тогда с rich объектами (в терминах DDD) - куда их отнести в данной классификации? Или мы заходим на территорию другой парадигмы?


  1. SergeyEgorov
    30.10.2025 13:03

    Просто не делите код на классы и жизнь станет проще и приятнее.


    1. evgenyk
      30.10.2025 13:03

      Это же Java, там так нельзя!


  1. WieRuindl
    30.10.2025 13:03

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


  1. Emelian
    30.10.2025 13:03

    Все классы я делю на 3 основных типа:

    1. Дата-класс

    2. ласс-управленец

    3. Класс-исполнитель

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

    1. Менеджер потоков

    2. Менеджер интерфейса

    3. Менеджер событий

    Точка входа: «Main.cpp», в функции «WinMain()» – одинаковой во всех проектах, которая создаёт и вызывает экземпляр класса «CApp» и ничего более.

    В классе «CApp» создается экземпляр класса «CMainWindow», инициализируются необходимые библиотеки, вроде, «GDIplus», класс (менеджер) потоков, при необходимости, цикл, либо менеджер событий и класс (менеджер) интерфейса (видов – дочерних окон, занимающих всю клиентскую область главного окна, разного рода компонентов и элементов).

    В классе «CMainWindow» создаются и инициализируются компоненты главного окна и функция их отображения при изменении размеров окна приложения.

    Каждый используемый компонент (класс) размещается в отдельной паре файлов: *.cpp и *.h;

    Для большей структуризации проекта могут использоваться отдельные каталоги. Например, можно создать отдельную папку для опенсорсного кода консольного видео-проигрывателя, переделанного под оконный интерфейс (см. скриншот моей программы «МедиаТекст»: http://scholium.webservis.ru/Pics/MediaText.png ).

    Пример простого приложения, разработанного в этой парадигме, см. в моей статье: «Минималистский графический интерфейс, на C++ / WTL, для консольного загрузчика» – https://habr.com/ru/articles/955838/ ).


  1. KoIIIeY
    30.10.2025 13:03

    А состояние DTO меняется после создания?)

    В целом пример сам по себе плохой: постоянные конвертации данных туда обратно, обертка для конвертации строки в инт, при том что внутри и так обертка для этой же конвертации, DI в примерах не используется "для простоты", хотя без DI с вами ныне даже hr разговаривать не станет(их для примера можно было в параметпы конструктора засунуть и не париться откуда они там, это же пример как раз) , сплошные абстракции ради абстракций в плохом смысле помноженные на беспричинное снижение скорости операций.