Введение
Мы часто говорим об алгоритмах как о "сердце" любой сложной системы. В контексте нашей платформы Гриндаты алгоритмы — это не просто код. Представьте, что данные — это деньги или документы клиентов, а алгоритмы — это инструкции и правила, которые позволяют быстро находить, сортировать, обрабатывать и сохранять эти "деньги/документы".
Процесс создания алгоритма для бизнес-логики всегда начинается с декомпозиции действий. Далее мы переводим эти действия на алгоритмический язык, подбирая оптимальные пути решения. Но чтобы делать это быстро и эффективно, необходима прочная концептуальная база.
Формулы удачного написания алгоритма не существует, поскольку задачи всегда разного толка и сложности. Однако понимание основных парадигм программирования — это первый и главный ключ к написанию оптимально работающих алгоритмов. Наши алгоритмы являются мультипарадигменными, поэтому рассмотрим два основных подхода, лежащих в их основе: ООП и ФП.
Концептуальная основа: ООП (Объектно-ориентированное программирование)
Концепция ООП лежит в основе структуры и окружения нашей платформы. Понимание ООП необходимо аналитику для:
Качества обработки требований: Умение выделять сущности, их свойства и связи.
Понимания платформы: Лучшее понимание архитектурных решений, предлагаемых разработчиками.
Проектирования гибких систем: Создание решений, которые легко изменять и переносить.
Рассмотрим ключевые понятия ООП на знакомой банковской аналогии:
-
Класс vs Объект:
Класс — это чертеж, инструкция или шаблон для создания объектов. Аналогия: чистый бланк Кредитной заявки. В шаблоне прописаны пустые поля (ФИО, сумма) и действия/методы ("отправить на проверку", "одобрить").
Объект — это уже конкретная сущность, созданная по шаблону-классу, с реальными данными. Аналогия: заполненный бланк "кредитнаяЗаявка_Иванова" на 500 000 руб..
Интерфейс: Более абстрактное понятие, которое описывает, что объект должен делать, но не описывает, как он это будет делать. Аналогия: Банковское отделение. Интерфейс предоставляет набор услуг ("открыть счет", "выдать наличные"). Реализация может быть разной: центральный офис, дополнительный офис или банкомат. Интерфейс позволяет разным объектам выполнять один и тот же набор действий.
Принципы ООП
Абстракция: Создание простой "модели" сложного предмета, содержащей только самые важные детали для решаемой задачи. Аналогия: форма Заявления на кредит. Вам не нужно знать все процессы скоринга; вы видите только поля (ФИО, доход) — это абстракция всего кредитного процесса.
Инкапсуляция: Объединение данных и методов работы с ними в одной "капсуле" (объекте) с контролируемым доступом. Внутренняя "кухня" скрыта. Аналогия: Банкомат. Вы взаимодействуете через строгий интерфейс (вставили карту, ввели PIN) и не знаете, как он внутри пересчитывает купюры и связывается с сервером. Ваши данные защищены.
Наследование: Создание нового объекта на основе существующего, перенимая его свойства и поведение. Аналогия: банковские продукты. Базовый продукт "Счёт" (номер, баланс). На его основе создается "Депозит", который наследует свойства Счета, но добавляет свои (процентная ставка, срок окончания).
Полиморфизм: Возможность использовать одно и то же действие для работы с разными типами объектов. Аналогия: "Рассчитать проценты". Для "Депозита" расчет происходит по одним правилам (конец срока), а для "Кредитной карты" — по другим (ежемесячно, на остаток долга). Для пользователя это одно действие, но внутри система выполняет разные алгоритмы.
Концептуальная основа: ФП (Функциональное программирование)
Хотя наше окружение основано на ООП, сами алгоритмы часто используют подходы Функционального программирования (ФП), которое обеспечивает предсказуемость и надежность кода. В ФП программа — это набор функций, преобразующих входные данные в выходные. Ключевая идея: функции должны быть похожи на математические, обеспечивая идемпотентность.
Понимание ФП выгодно аналитику, так как:
Помогает формализовать бизнес-правила в виде чистых, атомарных требований, которые легко тестировать.
Способствует описанию бизнес-процессов как цепочки преобразований данных (Data Flow), что делает спецификации четкими.
Ключевые принципы ФП
Неизменяемость данных (Иммутабельность): Исходные данные не изменяются. Для внесения изменений создается новая версия. Аналогия: ошибочная транзакция. Вместо удаления или изменения ошибочной операции, создается новая транзакция на ту же сумму со ссылкой на исходную. Это сохраняет всю историю произошедших событий.
Чистые функции (Идемпотентные): При одних и тех же входных данных, всегда возвращают один и тот же результат и никак не влияют на работу системы (не меняют глобальные переменные, не пишут в лог). Они ТОЛЬКО ВЫЧИСЛЯЮТ. Пример: Функция рассчитатьПроцент(сумма, ставка, срок) всегда вернет одинаковое значение, независимо от того, кто и когда ее запустит..
Функции первого класса: Функции, которые могут выступать в качестве аргумента для других функций. Пример: Функция обработатьСписокКлиентов принимает в качестве аргумента список клиентов и другую функцию, например, ОтправитьEmail или ОтправитьSMS, и применяет ее ко всему списку. Это повышает эффективность.
Отсутствие побочных эффектов: Функция не должна менять что-либо за своими пределами, кроме своего явного предназначения. Пример: Функция проверитьВозможностьКредита должна только проанализировать доход и кредитную историю, и вернуть ответ: "одобрено" или "отклонено". Она НЕ ДОЛЖНА самостоятельно менять статус заявки в БД или отправлять уведомление.
Алгоритмы платформы: "Хорошие" и "Плохие" практики
Алгоритмы в нашей платформе исходно на уровне back-end написаны на языке Groovy. При этом они являются мультипарадигменными: ООП учит нас структурировать окружение (объекты, таблицы), а ФП приучает правильно использовать функции внутри алгоритма.
Качество работы алгоритма, его скорость и понятность напрямую зависят от окружающих настроек и того, как написан сам алгоритм. Плохо спроектированная объектная модель (дублирующие справочники, нелогичные связи) приведет к тому, что даже хороший алгоритм будет работать плохо, требуя "костылей".
Ниже приведен список практик, которые помогут вам писать "хорошие" алгоритмы.
Список, дающий "минус-вайб" вашему алгоритму
Избегайте этих распространенных ошибок, которые ведут к медленной работе и сложному сопровождению:
Чрезмерное использование SQL. Стремитесь минимизировать SQL внутри алгоритма.
Использование функции Objects по таблице, где сотни тысяч или миллионы экземпляров.
Поиск не по индексированным полям в функции filterAlg.
Втюхивать в один алгоритм всю логику.
Составлять алгоритм из бесконечной вложенности других алгоритмов (алгоритм → вложенный алгоритм → ещё один вложенный).
Не пользоваться специализированными функциями, например, для расчета вычислимых показателей или при работе с лимитами.
Не тестировать свои алгоритмы самостоятельно. 5 секунд на тест сэкономят часы срочных работ на продакшене.
Доверять написание кода ИИ. ИИ не знает внутренних ограничений нашей платформы и использует избыточные конструкции, которые не подходят для Groovy в Гриндате.
Как писать "правильные" алгоритмы
Комментарии и осмысленное наименование: Комментарии и логичные имена переменных позволяют вам и вашим коллегам быстро вникнуть в суть происходящего, особенно в сложных конструкциях.
Используйте Структуры данных: Структуры данных (массив, хэш, очередь, стек) позволяют зафиксировать набор элементов в оперативной памяти и значительно сократить время работы алгоритма. Например, они незаменимы при работе с ETL.
Логически дробите алгоритм: Дробление важно и необходимо, но должно быть логически обосновано. Вместо одного огромного алгоритма создавайте несколько маленьких, например, используя вложенный алгоритм в разных местах.
Экранирование данных: При работе с интеграциями, JSON, XML или формировании, например, гиперссылки в SendPopUpMessage, важно экранировать данные (например, с помощью функции text для HTML), чтобы избежать ошибок и чтобы front-end "не съел" теги.
Учитывайте строгую типизацию данных.
valerychesnokov
Доброго дня. Скажите, пожалуйста, чем применение SQL такую немилость вызвало? Ведь грамотно написанный СКЛ источник отработает быстрее, чем в цикле крутить объекты с фильтрами. Особенно если в СКЛ несколько таблиц соединено сложной логикой.
Можно как-то раскрыть эту мысль на примере?
П.С: с платформой GD знаком 3.5 года.
yanushu Автор
Добрый день! Спасибо за ваш комментарий. SQL в немилость я никоем образом не поставил. Я написал непосредственно о "чрезмерном использовании SQL". Ниже по списку укажу тезисы, которые чуть шире раскроют данную тему.
Использовать SQL внутри алгоритмов позволяют 4 функции: filterSours, queryForObjectOrNull, queryForList и script.
Все эти функции применяются в зависимости от поставленной задачи.
Их использование не запрещено (иначе зачем иметь возможность ими пользоваться).
В случае данного тезиса "чрезмерного использовании SQL", идет речь о случаях, когда c помощью функции script проставляют булево значение у логического атрибута, того же Типа объекта, чт�� используется как базовый, что являет собой избыточность.
Также при построении алгоритма, в особенности сложных конструкций, следует не забывать об использовании Структур данных (подробнее про них тут).
Например алгоритмы не могут нативно выделить максимальную и минимальную дату, аналитик начинает использовать SQL, хотя здесь можно использовать Очередь (функция ArrayDeque).
Также если вы используете SQL, то стоит следить не только за самим синтаксисом SQL, но также не стоит забывать про индексирование полей (которые используются в блоке "where"). Аналитики часто об этом забывают.
Если речь идет об очень большом количестве данных, которые по логике должны хранится в таблицах наследованных от Примитивного объекта с суррогатным ключом (или без). И вам необходимо внутри алгоритма через цикл отфильтровать данные, то можете смело использовать filterSours.
Нельзя использовать функцию script для генерации экземпляров у Типов объектов, наследованных от таблицы Объект. Такой подход приводит к возникновению блокировок.
Аналитики не всегда осмысленно пишут SQL запросы, до такой степени, что может привести к декартовому произведению, что создает огромную нагрузку на сервер sql, от чего все запросы и пользовательские и системные встают в очередь и приложение либо не работает вообще, либо работает с большими тормозами. (Был личный опыт в разборе проблемы).
В итоге получается, если подходить разумно и с точки зрения написания алгоритма и с точки зрения написания SQL, то алгоритм будет работать быстро и без ошибок. Чрезмерное использование только SQL со временем будет приводить только к обращениям в техническую поддержку.