Привет, меня зовут Артём Коршунов, я программист в ЮMoney. В нашей команде много разработчиков, и все они пишут огромное количество кода. Его нужно проверять, но встроенных валидаций для проверки не всегда хватает, из-за чего могут возникать проблемы. Рассказываю, с какими сталкивались мы, пока не попробовали DacFx с объектной моделью и не автоматизировали валидацию.
Топ-5 проблем при ревью кода
С описанием таблицы. Для всех таблиц мы пишем описание, потому что, когда их становится очень много в каждом проекте, разработчикам сложно разобраться, что значат те или иные сущности. И, когда в одной таблице хранится 50 столбцов вместо четырёх, ревьюер должен пройтись глазами по всем и сравнить, есть ли для них соответствующее описание. Это всё утомительно, неинтересно, и кажется, что лучше потратить время на изучение технического задания. Часто это приводит к разногласиям между разработчиками и обидам на ревьюера, поэтому такие проверки лучше возложить на машину.
С упорядочиванием сортировки. Некоторые запросы нужно сопровождать сортировкой строк, потому что можно получить детерминированный результат. И это не только в теории — такая проблема часто встречается и на практике, когда пользователь получает разные результаты в конечных продуктах, таких как веб-приложения, Excel, отчёты, если не указан порядок сортировки. За всем этим стоят SQL-запросы. Если не указать порядок сортировки (ORDER BY), то при каждом выполнении одного и того же запроса можно получить разные результаты.
С переключением клавиатуры. Проблема, с которой сталкиваются не только программисты, но и обычные пользователи, — буквы на клавиатуре при переключении раскладки. Когда мы не сразу переключаемся с одного языка на другой, не замечаем этого и используем русские буквы вместо английских и наоборот.
Два символа, которые выглядят одинаково, но в памяти компьютера они хранятся по-разному, — буква С. Это порождает разные проблемы в комментариях, внутри процедур. Например, когда мы объявляем псевдонимы для наших объектов. Если мы допустили такую ошибку в названии или описании объекта, исправлять её придётся долго, и сделать это будет непросто. Поэтому надо быть внимательнее.
С наименованием объектов. В современных языках программирования по умолчанию есть конвенции наименования объектов, но в транзакции TSQL такой конвенции нет, и каждый пишет как хочет. Вот как это выглядит в современных языках:
А вот примеры того, что получилось у Microsoft — это всё есть внутри СУБД MS SQL:
Чтобы предотвратить подобную анархию, нам хотелось придумать какие-то общие правила, которые можно было бы использовать у себя.
Мы попробовали поискать правила в интернете и быстро обнаружили страницу Константина Таранова, который предлагает вот что:
Все эти правила можно организовать в виде регулярных выражений и проводить соответствующую проверку в проекте.
При указании названий схем. Обращаясь к объекту, мы всегда указываем название схем. Но иногда бывает так, что оно отсутствует или программист указывает лишнюю информацию, которая там не нужна, например название БД.
Чтобы исправить все эти проблемы, нам нужно было найти подходящий инструмент, и выбор пал на два парсера TSQL: ANTLR и DacFx.
ANTLR: плюсы и минусы парсера
ANTLR — классный проект, у него куча плюсов:
Живая и активная страница на GitHub.
Много контрибьюторов и коммитов.
Грамматики и диалекты для большинства языков программирования — содержит порядка 240 языков.
Языки, на которых пишут анализаторы: Java, C#, Python, JavaScript, Go, C++, Swift, Dart, PHP.
Только для одного SQL есть девять разных диалектов. Но проблема в том, что грамматика языка транзакции TSQL закрыта и точную её реализацию знает только компания Microsoft. Из-за этого некоторые наши запросы выдавали синтаксическую ошибку. Но я делал pull request, и ребята на стороне ANTLR очень быстро всё апрувили.
Также у этого инструмента много плагинов на разных IDE, что позволяет строить синтаксическое дерево для запросов и подсвечивать синтаксис грамматик.
Ещё есть исчерпывающая документация, которую постоянно обновляют.
Из минусов ANTLR я бы выделил то, что здесь нет объектной модели и полного покрытия грамматики транзакций SQL. Из-за этого в некоторых местах разработчики будут получать синтаксические ошибки.
DacFx: плюсы и минусы парсера
Из плюсов:
Поддерживает грамматику всех версий TSQL.
Есть объектная модель.
Минусы:
Нет плагина, который строил бы синтаксическое дерево. Но это и необязательно, как мы выяснили позже.
Анализатор можно использовать только в dotnet. У команды ЮMoney есть экспертиза в dotnet, поэтому нас это не остановило.
Очень мало примеров на GitHub.
Официальная документация скудная и устаревшая: последний раз её обновляли около десяти лет назад.
В итоге, несмотря на то что в DacFx минусов оказалось больше, чем плюсов, мы выбрали его. Потому что в нём есть объектная модель. Разберём, что это такое и почему она так важна.
Что такое объектная модель
Представим, что наша таблица хранится в файле MY_TABLE.sql и состоит из двух столбцов:
У нас есть второй файл MY_TABLE.INDEX.sql, и в нём хранится создание индекса на эту же таблицу:
Формально это один и тот же объект, но он хранится в разных файлах, для которых будет два разных синтаксических дерева. И если бы у нас не было объектной модели, которую нам предоставляет библиотека DacFx, то пришлось бы писать больше проверок в Visitor, чтобы найти все связи. Но библиотека DacFx делает это за нас.
Давайте посмотрим, как выглядит объектная модель для нашей таблицы:
Если мы хотим найти описание для таблицы, необходимо отфильтровать ObjectType, равный ExtendedProperty, в названии которого должен присутствовать MS_Description.
Если такое свойство не найдено, это значит, что у наших таблиц нет описания, в итоге мы получаем ошибку.
Нам было важно понять, что валидации, которые мы написали, не сильно усугубят время компиляции нашего проекта. Для этого я по 10 раз проверял каждую из групп валидации на своей локальной машине, отключил все программы, которые могли помешать, и 10 раз выполнял команду msbuild.
Вот что у меня получилось:
Без проверок компиляция занимает 64 секунды.
В базовом виде вместе с проверками Microsoft — 145 секунд.
Пять валидаций, которые я написал, занимают 125 секунд.
И на всё вместе (компиляция + мои пять проверок + MS SQL) ушло 158 секунд.
Сокращение производительности ухудшилось на 10%. Но не стоит забывать, что проект был достаточно большим, с большим количеством таблиц. Если этим будет заниматься программист, у него уйдёт на проверку больше времени, чем у машины.
Вывод
Не бойтесь писать свои валидаторы. Если у вас есть ошибки, которые повторяются из раза в раз, оптимизируйте их, благо для этого есть прекрасные инструменты, такие как DacFx. Команда разработки ЮMoney рекомендует. ;)
А если вам нужны примеры с кодом простейшего валидатора, заглядывайте ко мне на GitHub.