Сложнее, чем кажется

02 мая 2023
Сложнее, чем кажется

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

Почему тесты в CI глючат чаще

Один и тот же тест может проходить успешно или заканчиваться падением, даже если код не менялся. Такое случается, если в коде приложения или теста проявляется своеобразный недетерминизм. В таком коде 2 + 2 не всегда будет равно 4. Иногда это 3, а иногда 5. При запуске на тестовый прогон CI, ситуации с нестабильностью возникают значительно чаще, чем при локальном прогоне.

Такое случается по причинам:

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

Посмотрим на них с точки зрения выполнения тестов. Если один тест пропускает в глобальную среду какое-то состояние (поменявшийся файл или переменную окружения), то это влияет на последующие тесты. В CI это случается чаще, поскольку тесты запускаются «пачками», а не по одному, как при локальном прогоне. Это даёт тестам больше возможностей мешать друг другу, даже когда CI старается изолировать запуски.

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

Это лишь пара примеров, объясняющих проблемы в процессе тестирования. Чтобы узнать больше деталей, советуем прочитать статью Why tests flake more on CI than locally, написанную американским программистом Джейсоном Светтом (Jason Swett).

Обманчиво простой парсинг CSV

CSV-формату «стукнуло» уже более полувека, но он всё ещё супер-популярный. Его используют для того, чтобы хранить таблицы в текстовом виде. Название CSV расшифровывается, как Comma-Separated Values, дословно «значения, разделённые запятыми». Но это уже немного лукавство, ведь CSV не полностью стандартизирован, так что значения легко могут разделяться точками с запятой или вообще табуляцией.

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

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

Например, высокое использование памяти. Методы read и parse хорошо работают лишь до того момента, пока вам не попадётся действительно большой CSV-файл. Проблема в том, что оба этих метода полностью загружают все данные в память. Чтобы этого избежать, мы рекомендуем использовать метод foreach.

Ещё часто встречаются дублированные заголовки, такие как:

first_name,last_name,email,first_name,age\n
John,Doe,doe@gmail.com,Johnatan,30\n
Tom,Doe,tom@gmail.com,Thomas,40\n

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

data = CSV.read("./duplications.csv", headers: true)
transformed_data = Hash[data.first.to_a]
transformed_data['first_name'] # => “Johnatan”

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

Не привыкайте к deprecation warnings

Для новичка любой warning поначалу кажется чем-то из ряда вон выходящим и требующим внимания. У опытных разработчиков вырабатывается чёткая установка: warning — это всего лишь warning, рутинное предупреждение. Примерно так курильщики реагируют на предупреждающие надписи на пачках сигарет. 

Если мы говорим о deprecation warnings, то в большинстве случаев их можно спокойно игнорировать. Но при переходе на следующую версию того же Rails — это первое, на что стоит обратить внимание. В качестве примера приведём такое предупреждение из Rails 6.0:

Accessing hashes returned from config_for by non-symbol keys is deprecated and will be removed in Rails 6.1. Use symbols for access instead.

Казалось бы ничего страшного. Ну удалят в следующем обновлении возможность использования несимвольных ключей в config_for. Пока что их использовать можно и приложение будет работать. Но как только вы обновитесь до 6.1 — вангуем спецэффекты. 

Вариантов два:

  1. Проигнорировать и надеяться, что потенциальную проблему отловят тесты, и уже тогда с ней разбираться.
  2. Действовать проактивно и исправить это сразу, чтобы потом тратить время не на исправления, а на что-то более интересное.

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

Любой девопс вам скажет, что нет ничего хуже заспамленных логов, в которые попадает куча устаревших версий. Их практическая ценность будет околонулевой. Грамотная обработка — ключ к успеху, благо Rails это позволяет сделать с хирургической точностью. Идея в том, чтобы настроить приложение на :notify при устаревании:

module MyAwesomeApp < Rails::Application
 config.active_support.deprecation = :notify
end

Теперь, когда мы столкнёмся с устареванием, Rails отправит событие deprecation.rails через ActiveSupport::Notifications. Теперь нужно подписаться на эти события и настроить их обработку. Все события устаревания можно условно разделить на допустимые, которые можно смело игнорировать и недопустимые, которые нужно рассматривать, как ошибку. Цель — составление документированного списка всех устаревших версий и дальнейшее распределение их по команде.

Так, понемногу, мы будем заранее приближать приложение к запуску в следующей версии Rails без риска столкнуться с существенными сложностями. Ну а практическую реализацию такого обработчика вы найдёте в статье Stop ignoring your Rails (and Ruby) deprecations!

Интересно посмотреть

Если вы пропустили наш предыдущий Ruby meetup, ничего страшного. Мы уже выложили на наш YouTube-канал записи докладов. Рекомендуем налить себе чашечку кофе и посмотреть их в 4K качестве с отличным студийным звуком.

Александр Елистратов из BGaming сравнил RoR и GoLang с точки зрения подхода к разработке, использования библиотек и фреймворков, основываясь на личном опыте использования в коммерческих проектах:

Артем Соломатин из Samokat.tech рассказал в докладе, что такое distroless-образы, зачем они нужны. Детально разобрал их плюсы и минусы, а также привёл примеры реального использования:

Митапы

Онлайн

Ruby meetup №20

05 июля 2023

Летом мы встретимся с вами на замечательном Ruby Meetup, который пройдёт в Москве. Программа мероприятия формируется и скоро станет доступна на странице мероприятия. Кстати, если вы не успели подать доклад, то можете это сделать прямо в режиме онлайн.

Теперь следить за митапами Evrone стало удобнее. В Telegram-канале Evrone meetups мы выкладываем анонсы с подробными описаниями докладов, а также студийные записи после мероприятий. А ещё, у нас можно выступить, мы поможем оформить вашу экспертизу в яркое выступление. Подписывайтесь и пишите @andrew_aquariuss, чтобы узнать подробности.

Регистрация

Вакансии

Удаленка / Офис

Evrone 

Мы открыты для новых Ruby-разработчиков. В Evrone можно работать удалённо с первого дня, мы поддерживаем и оплачиваем участие в Open-source проектах и выступления на конференциях, а расти в грейдах можно с помощью честной системы проверки навыков и менторства.

Подробнее

Подписаться
на Digest →
Важные новости и мероприятия без спама
Технологии которыми вы владеете и которые вам интересны
Ваш адрес электронной почты в безопасности - вот наша политика конфиденциальности.