Ревью Scala-кода

Содержание

Ревью Scala-кода#

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

Производительность и качество кода#

Качество кода и производительность#

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

При ревью необходимо критически подходить к реализации:

  • как часто вызывается код;

  • можно ли сократить количество операций;

  • используется ли пакетная обработка;

  • применяется ли кэширование;

  • оптимальны ли запросы и структуры данных;

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

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

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

Цель: Обеспечить высокую производительность и масштабируемость кода, предотвратить проблемы на продуктивной среде.

JVM и Scala-компилятор#

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

Избегайте вложенных case-классов

Не следует объявлять case-классы внутри других классов или трейтов. Компилятор Scala генерирует значительный объём вспомогательного кода («синтаксического сахара») для обеспечения удобства разработки, однако в контексте JVM это приводит к неоптимальному байт-коду. В частности, дублирование кода происходит для каждого наследника объекта.

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

  • При создании вложенного case-класса компилятор дублирует стандартные методы case-класса (apply, copy, equals и т.д.) в каждом наследнике класса (или трейта), что приводит к раздуванию байт-кода.

  • Вложение case-классов в объект является оптимальным, так как компилятор создаёт статический вложенный класс без ссылки на мастера, следовательно без дублирования кода.

Цель: Снизить объём генерируемого байт-кода, скорение компиляции и повышение производительности рантайма.

Рекомендации:

  • Размещение case-классов на верхнем уровне файла или внутри object

  • Для ограничения видимости используйте модификаторы доступа: private[package-name] case class

Примеры:

Не оптимальный вариант:

class Btk_GroupEditPkg extends Pkg {
   case class GroupEdit_AddAttr(
                              caption: String,
                              fieldType: FieldType
                            )
   // ...
}

Оптимальный вариант:

class Btk_GroupEditPkg extends Pkg {
   // ...
}

case class GroupEdit_AddAttr(
                              caption: String,
                              fieldType: FieldType
                            )

Метаописания и Структура Классов#

Именование классов#

Имя класса должно быть уникальным, на латинице в формате <Модуль>_<Имя> (единственное число, именительный падеж, например, Lbr_Book). Имена сущностей регистрозависимы и должны точно совпадать с ожиданиями фреймворка (CamelCase).

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

Именование атрибутов#

Атрибуты должны иметь осознанные имена, без транслита, соответствующие содержанию поля (camelCase).

Цель: Улучшить читаемость и документировать структуру данных, соответствовать стилю Scala и GSF.

Типы атрибутов#

Для каждого поля должно быть указано имя, тип данных и связь. Простые поля используют типы NLong, NNumber, NString, NDate и т.д.; ссылочные поля – Reference, VariableReference или GID. Проверьте, что все Reference указывают на существующий класс и тип правильный (например, GID для переменной ссылочности). Типы полей должны быть корректно указаны и соответствовать БД (например, NLong, NNumber, NBigDecimal, NString, NGid, NDate и т.п.).

Цель: Обеспечить корректную работу с null-значениями, безопасность типов, интеграцию с фреймворком и согласованность схемы БД и метаданных.

Соответствие БД и метаданных#

Названия и типы полей в метаданных должны совпадать с колонками в БД (NLong > bigint, NString > varchar/text, NDate > date). При джоинах убедитесь в совместимости типов (избегайте неявных преобразований идентификаторов и используйте корректные приведения).

Цель: Обеспечить корректную работу ORM, избежать ошибок типов и обеспечить производительность запросов.

Связи#

Проверить корректность внешних ключей/ссылок, отраженных в метаданных. Использовать GID для переменной ссылочности в одном поле. Убедиться, что все связи отражены в метаданных (поля помечены как Reference или VariableReference).

Цель: Обеспечить целостность данных, корректную навигацию по объектной модели и работоспособность связанных функций GSF.

Хранение и извлечение гибких структур данных#

Если используется динамическое расширение (JSONB), проверьте настройку контейнера (тип Json) и работающую схему ключ–значение. Также проверьте необходимость индексов.

Цель: Обеспечить эффективное хранение и извлечение гибких структур данных, при необходимости — повысить производительность поиска.

Отсутствие дубликатов#

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

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

project.yaml#

Конфигурация Модулей GSF#

Проверьте секции конфигурации проекта (версии Scala/Java, параметры сборки, список модулей). Например: scalaFeatureRelease, sbtPlugin, modules с описанием каждого модуля – имя, source (репозиторий), branch, флаг isPublish. Убедитесь, что указаны все нужные модули (например, calculation, source, target, custom при наличии) и их настройки верны.

  • Проверьте соответствие шаблонам проекта (src, resources, тесты), что бы зависимости между модулями были описаны явно, циклические зависимости отсутствовали.

  • Проверьте, что конфигурации сборки (например, sbt) и плагины были корректны. Названия модулей и подпроектов соответствуют соглашениям.

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

Структура каталогов#

Поддерживайте корректную структуру каталогов (как сгенерировано Configurator), включая папки src, resources, тесты и пр.

Цель: Облегчить навигацию по коду, упростить настройку IDE и сборочных скриптов, повысить согласованность между проектами.

Зависимости#

Зависимости между модулями должны быть описаны явно в build.sbt, а так же продублированы в module-info.xml как метаданные

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

Конфигурации сборки/публикации#

Конфигурации сборки (например, sbt) и плагины должны быть корректны. Названия модулей и подпроектов должны соответствовать соглашениям.

Цель: Обеспечить стабильность и воспроизводимость процесса сборки и публикации, улучшить читаемость и поддержку проекта.

Использование стандартных компонентов GSF#

Использование сервисов GSF#

  • Встроенные сервисы фреймворка:

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

    • Для фильтрации классов включайте «Универсальный фильтр» — он позволяет фильтровать по полям класса и его коллекций на уровне БД.

    • Группировка объектов должна реализовываться через механизмы группировки GSF.

  • Кэширование и запросы:

    • Если используется кэширование GSF (Shared-cache), для часто запрашиваемых сущностей настройте cache-index в ORM.

    • При запросах используйте .unique().

    • Применяйте объектные запросы (OQuery) с tryCacheQueryResults() там, где это целесообразно.

    • Обязательно проверьте стратегии инвалидирования кэша.

  • Логирование:

    • Для записи логов используйте LogTransaction.

    • При большом количестве записей применяйте commitByInterval().

Цель: Снизить вероятность ошибок, ускорить разработку, обеспечить единообразие функциональности, упростить поддержку и обновление.

Кэширование и запросы#

Если используется кэширование GSF (Shared-cache), убедитесь, что для часто запрашиваемых сущностей настроены cache-index в ORM и при запросах используется .unique(). Используйте объектные запросы (OQuery) с tryCacheQueryResults() там, где это целесообразно. Проверьте стратегии инвалидирования кэша.

Цель: Повысить производительность за счёт снижения обращений к БД, обеспечить актуальность данных в кэше.

Логирование#

Для записи логов в базу задействуйте встроенный LogTransaction (выделяет отдельную лог-сессию). При большом количестве записей выполняйте commitByInterval() (например, каждые 1000 записей). Такой подход изолирует логи от основной транзакции и снижает нагрузку на БД.

Цель: Изолировать логи от основной транзакции, избежать потери логов при откате, снизить вероятность конфликтов и нагрузку на БД.

Обработка Ошибок и Зависимости#

Прикладные исключения#

В коде обрабатывайте только «прикладные» исключения — наследники AppException. Системные исключения не перехватывайте, а выбрасывайте дальше, пропускайте (так фреймворк сможет корректно прервать сессию).

Цель: Чётко разделить бизнес-логику ошибок и системные сбои, предотвратить скрытие серьёзных проблем и нарушение целостности данных.

Объявление исключений#

Создавайте свои исключения как подклассы AppException и заводите для них фабрику (например, object ExceptionName extends ExceptionFactory(new ExceptionName(_))). Выбрасывайте исключения через throw AppException(…) или e.raise(…) (сохранит стек вызовов). При необходимости логирования ошибки используйте отдельную лог-транзакцию.

Цель: Обеспечить единообразие, упростить обработку, легко идентифицировать тип ошибки, гарантировать запись лога при откате основной транзакции.

Логирование ошибок#

При необходимости логирования ошибки используйте отдельную лог-транзакцию. В try/catch используйте минимальное информативное логирование; причина исключения не скрывается.

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

Транзакции и зависимые задачи#

Избегайте слишком длинных транзакций (несколько минут и более) – PostgreSQL плохо их переносит. Разбивайте большие операции на логические блоки и выполняйте session.commitWorkAuto() после каждой партии изменений. Например, при массовой загрузке в цикле вызывайте commitWorkAuto() через заданный интервал, чтобы регулярно сбрасывать пакет в БД. При этом используйте flush() / commit() осмотрительно: не делайте неожиданных коммитов внутри server-side методов, чтобы не прерывать ожидания отката.

Цель: Снизить вероятность конфликтов, рост WAL, улучшить общую производительность БД, уменьшить вероятность сбоев, облегчить откат, сохранить согласованность данных.

Требования к Структуре Scala-Кода#

Соглашения по стилю#

Соблюдайте стандарты именования Scala (классы – CamelCase, методы/переменные – camelCase) по руководству Scala. Global3-FrameWork регистрозависим, поэтому имена сущностей (классов, полей, методов) должны точно совпадать с тем, как они объявлены. Не используйте подчёркивания в именах (за исключением разделителя модуля и имени класса).

Цель: Обеспечить корректную работу фреймворка, повысить читаемость кода, облегчить его понимание и поддержку.

D/A-паттерн кода#

При генерации классов учтите, что фреймворк генерирует две иерархии: Domain- и Application-части. Доменные классы (ClassDpi, ClassDvi) перезаписываются при генерации и содержат «скелет» логики, а прикладные классы (ClassApi, ClassAvi) – расширяют их и используются для ручной бизнес-логики. Логика должна писаться только в прикладных классах (Api/Avi), доменные файлы не редактируйте.

Цель: Обеспечить стабильность при регенерации кода, изолировать бизнес-логику от автоматически генерируемого кода.

Null-типы GSF#

Для работы со значениями из БД используйте расширенные типы GSF (NLong, NNumber, NGid, NDate, NString, NBigDecimal и т.д.), которые безопасно обрабатывают null. Например, идентификаторы – NLong (фабрика nl), числовые поля – NNumber или NBigDecimal (фабрика nr). Важное правило: для финансовых расчётов используйте NBigDecimal (BigDecimal), чтобы избежать ошибок двоичной арифметики. При сравнении nullable-значений в скриптах GSF применяйте оператор === (предотвращает NPE) вместо стандартного ==.

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

Общие Принципы и Стиль Кода#

Комментарии#

Код должен сопровождаться понятными комментариями.

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

Именование#

Переменным и атрибутам необходимо давать осознанные имена, транслит необходимо избегать. Следуйте scala-конвенциям наименования сущностей.

Цель: Улучшить читаемость, документировать структуру данных, соответствовать стилю Scala и GSF.

Форматирование#

sql и scala код должны быть отформатированы и не превращаться в «кашу».

Цель: Улучшить читаемость, облегчить понимание и сопровождение кода.

Официальная документацию к API#

Писать scaladoc к методам, классам и переменным.

Цель: Предоставить официальную документацию к API, облегчить поддержку и использование кода другими разработчиками.

Безопасность и Надёжность#

Исключить использование null#

Использование null должно быть полностью исключено для всех ссылочных типов.

Цель: Предотвратить NullPointerException, использовать безопасные GSF-типы.

Обработка пустых значений#

Для значимых типов (AnyVal), таких как JObject, запрещено использовать null или его приведение (null.asInstanceOf[JObject]). Вместо этого необходимо использовать пустые экземпляры (например, JObject()), а проверку на наличие данных выполнять через .nonEmpty.

Цель: Предотвратить NullPointerException при работе с такими типами.

Безопасный доступ к данным#

Необходимо избегать использования head или get; вместо них следует использовать headOption или getOption, чтобы избежать NPE.

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

Обработка всех сценариев#

При ветвлении кода if-else необходимо указывать ветку else; для pattern-matching обязателен случай case _ =>.

Цель: Гарантировать обработку всех возможных сценариев, предотвратить MatchError, сделать код более надёжным.

Производительность#

Оптимизация работы с коллекциями#

При использовании нескольких комбинаторов подряд (например, arr.map.flatMap.filter) необходимо добавлять .view в начало, чтобы не создавались временные коллекции.

Цель: Улучшить производительность за счёт снижения расхода памяти.

Читаемость при обработке данных#

При использовании комбинаторов по карте необходимо применять pattern matching для деструктуризации кортежей.
Плохо: any_map.foreach { tpe => tpe._1 + tpe._2 }.
Хорошо: any_map.foreach { case (key, value) => key + value }.

Цель: Сделать код более выразительным, типобезопасным и читаемым.

Изменяемые коллекции#

В параметрах методов и возвращаемых значениях не должны использоваться изменяемые коллекции collection.mutable.

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

Выбор эффективной коллекции#

Необходимо использовать ArrayBuffer вместо ListBuffer.

Цель: Обеспечить лучшую производительность для операций вставки и доступа по индексу.

Выбор коллекции#

При выборе коллекции (List, Array, Vector, ArrayBuffer) необходимо учитывать асимптотическую сложность операций (вставка, доступ по индексу), чтобы обеспечить оптимальную производительность.

Цель: Использовать подходящие структуры данных для ускорения выполнения операций.

Большие реестры#

Получение данных из больших реестров должно выполняться реляционными запросами в БД по максимальному количеству условий с предварительным session.flush(), чтобы не перегружать ОЗУ сервера.

Цель: Снизить нагрузку на память сервера приложений, повысить производительность за счёт фильтрации на уровне БД.

Работа с API и ROP#

HTTP-пакеты#

Прямое обращение к http пакетам запрещено; необходимо использовать btk_httppkg.

Цель: Обеспечить централизованное управление HTTP-вызовами, соблюдение стандартов безопасности и логирования.

Файлы#

При работе с файлами необходимо использовать стандартное api — btk_fileapi.

Цель: Гарантировать корректную обработку файлов, безопасность и совместимость с инфраструктурой.

Оптимизация расхода памяти#

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

Цель: Предотвратить чрезмерный расход памяти, особенно при работе с большими объектами или в циклах.

Загрузка ROP по gid#

При загрузке ропов по gid необходимо использовать Btk_Pkg.loadByGid (обёрнутый в TryApp). Полученный rop должен матчиться с нужным API, например: case Btk_ClassApi(rop) =>.

Цель: Обеспечить безопасную загрузку объектов и правильную их типизацию.

Поиск по коду#

Для поиска по коду необходимо использовать методы ____Api().findByMnemoCode(""), а не прямые запросы. Объекты API должны быть объявлены через lazy val.

Цель: Использовать встроенные механизмы GSF, повысить читаемость и поддерживаемость кода.

Надёжность получения данных#

Вместо конструкции api().load(getVar("idAnyClass").asNLong) необходимо использовать метод get, так как нет гарантии, что getVar вернёт значение вместо null.

Цель: Обеспечить более безопасное и надёжное получение данных.

Использование source-generated конструкций#

Для доступа к атрибутам необходимо использовать конструкции, сгенерированные генератором источников (например, rop.get(_.attr)), а не методы getByAttrName и getAttrByName, так как последние используют рефлексию, ошибки которой могут быть обнаружены только во время выполнения.

Цель: Обеспечить безопасность на этапе компиляции, предотвратить ошибки, связанные с рефлексией.

Работа с Базой Данных и SQL (Дополнения)#

Вложенные циклы#

Запросы внутри вложенных циклов использовать нельзя.

Цель: Избежать экспоненциального роста числа запросов к БД, сохранить производительность.

Эффективное обновление кэша#

В onRefresh необходимо использовать refreshByKey(parent) вместо byKey(parent).

Цель: Обновить данные в кэше, повысить эффективность.

Предотвращение конфликтов сессии#

При необходимости использования onRefresh на основе selectStatement в детализации документа необходимо отключать @FlushBefore, чтобы избежать ошибок при создании документа с обязательными атрибутами.

Цель: Предотвратить конфликты сессии и ошибки при инициализации документа.

Архитектура и Дизайн#

Эффективная инициализация полей#

При объявлении переменных в классе необходимо использовать ключевое слово lazy, чтобы они инициализировались только при первом использовании.

Цель: Улучшить производительность, избежать ненужных вычислений.

Читаемость кода установки#

При регистрации типов, закладок, атрибутов, функциональных настроек, процедур в dataInstall необходимо добавлять lazy val с мнемокодами и id для зарегистрированных значений.

Цель: Улучшить читаемость и поддержку кода установки данных.

Изменение параметров#

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

Цель: Критично для стабильности API, предотвратить ошибки в использующем коде.

Новые параметры#

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

Цель: Избежать необходимости изменять весь код, который вызывает этот метод.

Сложная логика в AVI#

Сложную логику не нужно расписывать в операции Avi, если она может быть вынесена в API/PKG. AVI-операция должна сводиться к вызову метода из API/PKG, это упрощает поддержку и минимизирует дублирование кода.

Цель: Улучшить поддерживаемость кода, разделить ответственности, тестировать бизнес-логику отдельно от UI-логики.

Scala-классы с компаньоном#

При создании структур Scala-классов с объектом-компаньоном необходимо использовать решение, позволяющее проектное переопределение: def list(): List = { new List {...} }.

Цель: Обеспечить более гибкую настройку поведения классов на уровне проекта.

Работа с Атрибутами и Полями (Дополнения)#

Source-generated доступ#

Для доступа к атрибутам необходимо использовать конструкции, сгенерированные генератором источников (например, rop.get(_.attr)), а не методы getByAttrName и getAttrByName, так как последние используют рефлексию, ошибки которой могут быть обнаружены только во время выполнения.

Цель: Обеспечить безопасность на этапе компиляции, предотвратить ошибки, связанные с рефлексией.

Безопасность на этапе компиляции#

В выборках при обращении к стандартным атрибутам необходимо использовать getSelfVar(A.idPerson.name) или просто A.idPerson.asNLong вместо getSelfVar("idPerson"), это позволяет поймать ошибки во время компиляции при удалении атрибута.

Цель: Обеспечить безопасность типов на этапе компиляции.

Хранимые поля в AVI#

Хранимые поля класса в AVI должны получаться через объект А, сгенерированный генератором источников, чтобы избежать ошибок времени выполнения при переименовании атрибута в классе.

Цель: Сделать код устойчивым к переименованиям атрибутов.

Корректность обновления данных#

В case class’ах, которые возвращаются в onRefresh, атрибуты должны быть мутабельными (var).

Цель: Обеспечить корректную работу с UI и обновление данных.

Строки и JSON#

Интерполяция строк#

При сборке строк из статических и динамических элементов необходимо использовать интерполяцию s"text${value}otherText" или многострочный вариант: s"""1-st line |2-nd line |.... |last line""".stripMargin. Интерполяция должна использоваться вместо конкатенации, так как она оптимальнее и более читаема.

Цель: Повысить читаемость, обеспечить оптимизацию компилятором.

Читаемость многострочных строк#

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

Цель: Улучшить читаемость многострочных строк в коде.

Эффективная работа со строками#

При сборке очень больших строк необходимо использовать StringBuilder вместо pos.map(s"...").mkString().

Цель: Обеспечить более эффективную работу с большими строками.

Выбор безопасных JSON-типов#

scala.util.parsing.json использовать нельзя, предпочтительнее JObject.

Цель: Использовать интегрированный с GSF и более безопасный тип.

Предотвращение несовместимости#

Использование Btk_JsonPkg необходимо избегать из-за зафиксированного разного поведения при его использовании с JObject.

Цель: Предотвратить неожиданное поведение и ошибки.

Специфика UI: AVI и Списочные Отображения#

Безопасное получение значений в списках#

В Avi.checkWorkAbility и других AVI-операциях списочных отображений значения полей необходимо получать через getVar, getSelfVar или A, а не через thisRop(), чтобы избежать ошибки load id = null в пустом списке.

Цель: Предотвратить ошибки при работе с пустыми списками.

Поля с большим объёмом данных#

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

Цель: Повысить производительность UI и улучшить пользовательский опыт.

Контроль сброса сессии#

В LookUp-отображениях для onRefresh необходимо добавлять аннотацию @FlushBefore(mode = FlushBeforeMode.Disabled).

Цель: Предотвратить нежелательные сбросы сессии при обновлении данных в LookUp.

Корректная деактивация операций#

Вместо oper.isActive = someCondition необходимо использовать DefaultRep#deactivateOper для деактивации операций при невыполнении условий, это обеспечивает корректную работу при различных перекрытиях.

Цель: Использовать встроенный механизм фреймворка для корректной деактивации операций.

Управление доступностью кнопок в списках#

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

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

Структурирование настроек выборки#

Сортировка по умолчанию и передача макросов фильтрации должны быть указаны в prepareSelectStatement, а не в selectStatement.

Цель: Улучшить структуру и читаемость кода настройки выборки.

Специфика UI: Кэширование, Логика и Настройки#

Переходы состояний#

Переходы состояний должны определяться сравнением nOrder и формированием условия из двух частей (из какого в какое), например: nvStateFrom < 100.nn && nvStateTo >= 100.nn, это обеспечивает работоспособность логики даже при удалении перехода.

Цель: Сделать логику переходов устойчивой к изменениям структуры состояний.

Синхронизация данных с интерфейсом#

Аннотация @Setter(refreshAfter = true) должна выполнять selection.refreshItem() в конце сеттера.

Цель: Обеспечить обновление UI после изменения значения.

Кастомные выборки#

Редактируемые кастомные выборки должны быть реализованы объектным запросом, чтобы собрать пул изменений и отправить их в БД одной транзакцией.

Цель: Повысить эффективность, обеспечить целостность данных.

Идентификатор в результатах выборки#

При реализации кастомных выборок на объектном запросе результат выборки должен содержать поле id для работы onRefreshExt.

Цель: Обеспечить корректную работу механизма обновления расширенных данных.

Предпочтительные способы получения данных#

Использование AdditionalInfo необходимо дважды обдумывать. Предпочтительнее получать данные через onRefreshExt.

Цель: Улучшить структуру и производительность, избегать избыточного кода.

Структура данных при переопределении#

При переопределении отображения, в котором уже используется AdditionalInfo, новые вычисляемые атрибуты не должны добавляться в новый AdditionalInfo2. В OnRefresh необходимо использовать несколько case-классов: thisApi().byParent(getIdMaster).map { rop => (rop, getAdditionalInfo(rop), getAdditionalInfo2(rop)) }.

Цель: Улучшить структуру и читаемость кода обновления данных.

Отражение (Reflection)#

Обоснование#

Использование reflection должно быть оправдано; в общем случае оно запрещено.

Цель: Повысить безопасность типов, производительность, упростить сопровождение кода.

Выбор стабильных библиотек ввода-вывода#

Использовать нельзя, предпочтительнее Java-классы.

Цель: Обеспечить лучшую стабильность и совместимость.

Специфика Модулей и Проектов#

Модуль STK#

Для получения остатков необходимо использовать ru.bitec.app.stk.Stk_Pkg#getRemainsMulti. Для получения цены необходимо использовать ru.bitec.app.stk.Stk_Pkg#getnPrCostConsSum.

Цель: Использовать стандартные, оптимизированные методы для получения данных из модуля STK.

Модуль ACT#

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

Цель: Гарантировать корректность и полноту данных об оборотах.

Проектные модули#

При открытии MR необходимо указывать соответствующую ветку с учётом проекта, на котором разрабатывается. Например, для проекта СНГ разработка ведётся на sng-internal-dev > ветка проектного модуля gs; pdev > dev.

Цель: Обеспечить правильную интеграцию изменений в проектную ветку.

Безопасность и Целостность Данных#

SQL-инъекции#

Все входные данные параметризованы, нет риска SQL-инъекций.

Цель: Защитить от SQL-инъекций, ускорить выполнение за счёт переиспользования планов.

Ограничения целостности#

Ограничения целостности (foreign key, unique) соответствуют метаданным.

Цель: Обеспечить корректность и согласованность данных на уровне БД.

Валидации#

Валидации на уровне приложения дублируют, но не заменяют БД-ограничения.

Цель: Обеспечить дополнительный уровень защиты и пользовательскую обратную связь.

Чувствительные данные#

Чувствительные данные маскируются в логах и не попадают в открытые сообщения об ошибках.

Цель: Защитить конфиденциальную информацию от утечки.

Соответствие Best Practices#

Документация#

Убедитесь, что код и конфигурации соответствуют рекомендациям из официального руководства (например, настройка логирования, работа с транзакциями, параллельные вычисления и т.д.). Всегда проверяйте разделы «Практика разработки» в документации GlobalFramework и сверяйтесь с примерами.

Цель: Повысить качество, поддерживаемость и безопасность кода, облегчить его понимание новыми разработчиками.

Чистота кода#

Код должен быть читабельным и понятным (часто читают больше, чем пишут). Держите методы короткими, избегайте дублирования. Имена должны ясно отражать назначение сущности. Следуйте общим принципам GSF: не используйте служебные символы (например, «!», «?», «@») в идентификаторах, оформляйте документацию и комментарии по необходимости.

Цель: Улучшить читаемость, облегчить понимание и сопровождение кода.

Критические ошибки#

Целостность данных при модификации#

DML (INSERT/UPDATE/DELETE) выполняется через ASQL. — Блокирующее

Цель: Предотвратить нарушение целостности данных из-за автокоммита ASQL.

Долгая транзакция#

Долгая транзакция, захватывающая большое число строк без разбивки. — Блокирующее

Цель: Избежать проблем с производительностью и блокировками БД.

Функции GSF в массовых выборках#

Массовое использование getattribute/getmnemocode/getheadline в больших выборках/отчётах. — Блокирующее

Цель: Избежать чрезмерной нагрузки на БД.

Арифметика с N-типами#

Арифметика с N-типами без проверки nullable (.isNotNull). — Блокирующее

Цель: Предотвратить NullPointerException и ошибки времени выполнения.

Конкатенация строк SQL#

SQL-строки формируются конкатенацией с пользовательскими данными. — Блокирующее

Цель: Предотвратить уязвимость к SQL-инъекциям.

Дублирование логики#

Необъяснимое и несогласованное дублирование логики в разных модулях. — Блокирующее

Цель: Упростить сопровождение, снизить вероятность ошибок, упростить внесение изменений.