Список активных правил#

AsciiIdentifiersWart#

Запрещает символы вне 7-битной ASCII-кодировки (код > 0x7F) в именах идентификаторов. Правило исключает кириллицу, математические символы и другие не-латинские буквы.

Режим работы по умолчанию:

Правило настроено как ошибка — компиляция останавливается при обнаружении нарушения.

Почему это важно:

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

  • Совместимость: ASCII-идентификаторы гарантированно работают во всех инструментах, редакторах и системах контроля версий.

  • Читаемость командой: единый стандарт именования упрощает чтение кода разработчиками с разными языковыми настройками.

Проверяемые элементы кода:

  • объекты (object);

  • классы, трейты, перечисления (class, trait, case class, enum);

  • псевдонимы типов (type);

  • методы и их параметры (def);

  • поля и локальные переменные (val, var);

  • переменные в паттерн-матчинге (case x =>).

Примеры кода, вызывающего ошибку компиляции:

val сумма = 10                          // кириллица в имени
def рассчитать(): Int = 0              // кириллица в имени метода
case class Документ(id: Int)           // кириллица в имени класса
val userNаme = "x"                     // кириллическая "а" внутри латинского имени
val π = 3.14                           // символ вне ASCII

Корректные примеры:

val sum = 10
def calculate(): Int = 0
case class Document(id: Int)
val userName = "x"

MixedLanguagesWart#

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

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

  • Опечатки в сообщениях: случайное нажатие клавиши переключения раскладки приводит к бессмысленным словам вроде Система недоступнa (латинская a в конце).

  • Поиск и замена: смешанные слова не находятся при поиске по шаблону, что затрудняет локализацию и исправление.

  • Качество локализации: правило помогает поддерживать чистоту текстов в пользовательских интерфейсах и логах.

Проверяемые элементы кода:

  • строковые литералы ("...");

  • части строковых интерполяций (s"...", raw"...", f"...").

Исключения из проверки: регулярные выражения (литералы вида "[a-zа-я]").

Примеры кода, вызывающего предупреждение:

val msg = "приветWorld"                // смешение в одном слове
def test(s: String = "helloМир") = {}  // смешение в параметре по умолчанию
val err = "Система недоступнa"         // латинская "a" в кириллическом слове

Корректные примеры:

val msg = "привет World"               // разделение пробелом
val msg = "hello Мир"                  // разделение пробелом
val msg = "привет123"                  // цифра как разделитель
val regex = "[a-zA-Zа-яА-Я]"           // исключение для регулярных выражений

NetWart#

Запрещает прямое использование HttpURLConnection и HttpClient для выполнения HTTP-запросов. Правило требует применения централизованного инструмента Btk_HttpPkg.

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

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

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

  • Поддерживаемость: Единая точка вызова упрощает обновление версий библиотек и применение изменений в политике работы с внешними сервисами.

Проверяемые элементы кода:

  • вызовы методов на объектах типа java.net.HttpURLConnection;

  • вызовы методов на объектах типа org.apache.http.client.HttpClient.

Примеры кода, вызывающего предупреждение:

val conn = new HttpURLConnection(url)
conn.connect()

val client = HttpClients.createDefault()
val response = client.execute(request)

Корректные примеры:

Btk_HttpPkg().get(url)
Btk_HttpPkg().post(url)

CommitWart#

Предупреждает об использовании ручного управления коммитами транзакций через конструкцию if (i % batchSize == 0). Правило рекомендует применять метод session.commitWorkAuto() для автоматического сохранения изменений при обработке больших объёмов данных.

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

  • Утечки памяти: Ручное управление коммитами легко приводит к ошибкам — пропущенный коммит удерживает записи в кэше сессии, вызывая рост потребления памяти.

  • Некорректная работа с транзакциями: При ошибке в середине батча часть данных может быть закоммичена, а часть — нет, нарушая целостность данных.

  • Сложность поддержки: Автоматический метод commitWorkAuto() инкапсулирует логику батчинга, упрощая чтение и модификацию кода.

Проверяемые элементы кода:

  • конструкции if с оператором остатка от деления (%) и последующим вызовом session.flush(true);

  • конструкции if с оператором остатка от деления (%) и последующим вызовом session.commitWork().

Примеры кода, вызывающего предупреждение:

for ((item, i) <- items.zipWithIndex) {
  process(item)
  if (i % 100 == 0) {
    session.flush(true)
  }
}

for ((item, i) <- items.zipWithIndex) {
  process(item)
  if (i % batchSize == 0) {
    session.commitWork()
  }
}

Корректные примеры:

for ((item, i) <- items.zipWithIndex) {
  process(item)
  session.commitWorkAuto()
}

ManagedWart#

Запрещает использование конструкции resource.managed из библиотеки scala-arm для управления ресурсами с автоматическим освобождением. Правило рекомендует применять стандартный механизм scala.util.Using, встроенный в язык начиная с Scala 2.13.

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

  • Зависимость от сторонней библиотеки: resource.managed требует подключения библиотеки scala-arm, которая не поддерживается активно и создаёт дополнительные зависимости проекта.

  • Стандартизация: scala.util.Using является частью стандартной библиотеки Scala, обеспечивая единообразие кодовой базы и упрощая миграцию между версиями языка.

  • Безопасность ресурсов: Стандартный механизм гарантирует корректное освобождение ресурсов даже при возникновении исключений, с чёткой семантикой и документацией.

Проверяемые элементы кода:

  • вызовы метода managed в контексте управления ресурсами (например, в генераторах for).

Примеры кода, вызывающего предупреждение:

import resource._

for (conn <- managed(getConnection())) {
  // работа с соединением
}

val result = managed(resource).acquireFor { r =>
  r.process()
}

Корректные примеры:

import scala.util.Using

Using.resource(getConnection()) { conn =>
  // работа с соединением
}

Using(getConnection()) { conn =>
  conn.process()
}.get

NDateInterpolation#

Запрещает прямое использование объектов типа NDate в строковых интерполяциях и конкатенациях. Правило требует явного вызова метода .toNString() для преобразования даты в строковое представление.

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

  • Некорректное форматирование: Прямая вставка NDate в строку приводит к неопределённому формату даты, зависящему от реализации toString().

  • Локализация: Метод .toNString() гарантирует единообразное отображение даты в соответствии с настройками локали пользователя.

  • Читаемость кода: Явное преобразование делает намерение разработчика очевидным и упрощает поддержку кода.

Проверяемые элементы кода:

  • строковые интерполяции с использованием NDate (s"...", f"...", raw"...");

  • конкатенация строк с объектами типа NDate.

Примеры кода, вызывающего предупреждение:

val ndate: NDate = getCurrentDate()
val msg = s"Date: $ndate"
val msg2 = "Date: " + ndate

Корректные примеры:

val ndate: NDate = getCurrentDate()
val msg = s"Date: ${ndate.toNString()}"
val msg2 = "Date: " + ndate.toNString()

SelectStatementWart#

Запрещает использование метода getVar внутри строковых интерполяций при формировании SQL-запросов в методах selectStatement, prepareSelectStatement и onRefresh. Правило требует применения bind-параметров для безопасной работы с базой данных.

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

  • Риск SQL-инъекций: Подстановка значений через getVar в строку запроса создаёт уязвимость к инъекциям при недостаточной валидации входных данных.

  • Производительность: Bind-параметры позволяют СУБД кэшировать план выполнения запроса, что повышает производительность при повторных вызовах.

  • Читаемость запросов: Использование :параметр делает структуру SQL-запроса прозрачной и упрощает отладку.

Проверяемые элементы кода:

  • переопределённые методы selectStatement, prepareSelectStatement, onRefresh с возвращаемым типом String;

  • строковые интерполяции внутри этих методов, содержащие вызовы getVar.

Примеры кода, вызывающего предупреждение:

override def selectStatement: String = 
  s"SELECT * FROM table WHERE id = ${getVar("flt_sShema")}"

override def prepareSelectStatement: String = 
  s"SELECT name FROM users WHERE role = ${getVar("role")}"

Корректные примеры:

override def selectStatement: String = 
  "SELECT * FROM table WHERE id = :flt_sShema"

override def prepareSelectStatement: String = 
  "SELECT name FROM users WHERE role = :role"

StaticSizeChunkWart#

Запрещает явное указание параметра chunkSize при вызове метода Btk_BulkProcessPkg.chunkedQuery. Правило требует использования значения по умолчанию для автоматического определения оптимального размера пакета обработки.

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

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

  • Избежание ошибок настройки: Фиксированный размер чанка может привести к переполнению памяти при больших объёмах данных или к неэффективной обработке при малых объёмах.

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

Проверяемые элементы кода:

  • вызовы метода chunkedQuery из пакета Btk_BulkProcessPkg;

  • явная передача параметра chunkSize со значением, отличным от значения по умолчанию.

Примеры кода, вызывающего предупреждение:

Btk_BulkProcessPkg().chunkedQuery(
  api = this,
  chunkSize = 5000,
  sqlText = """SELECT id, gid FROM large_table"""
) { item =>
  process(item)
}

Корректные примеры:

Btk_BulkProcessPkg().chunkedQuery(
  api = this,
  sqlText = """SELECT id, gid FROM large_table"""
) { item =>
  process(item)
}

SqlInterpolationWart#

Запрещает использование обычных строковых интерполяторов (s, f, raw) внутри вызовов SQL-методов (SQL, ASQL, ATSQL). Правило требует применения параметризованных запросов с методом .on() для предотвращения SQL-инъекций.

Режим работы по умолчанию:

Правило работает как предупреждение — компиляция продолжается, но выводится сообщение.

Почему это важно:

  • Безопасность: Параметризованные запросы полностью исключают возможность SQL-инъекций, даже при обработке недоверенных входных данных.

  • Семантическая корректность: Метод .on() гарантирует правильное экранирование и преобразование типов значений перед передачей в СУБД.

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

Проверяемые элементы кода:

  • вызовы методов SQL, ASQL, ATSQL с аргументами, содержащими строковые интерполяции s"...", f"...", raw"...";

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

Примеры кода, вызывающего предупреждение:

val svName = "John"
val query = SQL(s"SELECT * FROM users WHERE name = $svName")

Корректные примеры:

val svName = "John"
val query = SQL"SELECT * FROM users WHERE name = {name}".on("name" -> svName)

val query2 = SQL("SELECT * FROM users WHERE name = {name}").on("name" -> svName)
val query2 = SQL"SELECT * FROM users WHERE name = $svName"