Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Конвертер видео

ffmpeg стал существенно проще с того момента, когда я им в первый раз воспользовался. Например, раньше для вырезания звуковой дорожки нужны были какие-то танцы с кодеками, а сейчас просто

ffmpeg -i video.mp4 -ab 320k audio.mp3
СсылкаКомментировать

Хрупкие аннотации Spring

Одна из самых бесючих проблем в Spring — это то, что одной аннотацией можно неочевидно сломать работающий код.

Например, добавление @ComponentScan превратит в тыкву @WebMvcTest.

@Transactional может сломать любой сервис без интерфейса, причем в лучшем случае будет NoSuchBeanDefinitionException, а в худшем, а в худшем, если класс-потребитель не финальный, то будет тупо null вместо бина.

Поставил @TestExecutionListeners без mergeMode = MERGE_WITH_DEFAULTS — и тесты перестают работать.

И подобных примеров еще массу можно напридумывать:(

СсылкаКомментировать

Связи в проекте и Github Action

Очевидно, что связи между артефактами в проекте очень полезны. Код привязан к коммиту, коммит — к тикету и код-ревью/пулл-реквесту, тикет к ТЗ/эпику/истории и/или документации. Причем хорошо, когда эти связи еще и двусторонние: например, чтобы по коду можно посмотреть тикет и зачем он был написан, а по тикету — написанный код. Иначе при смене процесса придется искать эти связи вручную или не напрямую. Кажется, это одна из “продающих” фишек Gitlab или Space.

Мне не хватало связи пулл-реквеста GitHub с тикетом, существующие варианты action’ов показались унылыми, поэтому я написал свой, старался сделать его максимально универсальным.

К сожалению, для написания action’ов есть только два варианта: nodejs или docker-образ. Я по глупости выбрал первый вариант, потому что большинство action’ов были написаны на нем, да и библиотека для работы с GitHub была только для JS. В очередной раз вляпался в экосистему nodejs: обновление npm через него самого сделало его непригодным для использования. Куча пулл-реквестов от Dependabot — это мрак, группировки обновлений нет годами.

TypeScript вроде мощный и классный, можно выражения в шаблонах считать, но нет каких-то банальных вещей вроде безопасного enum или filterNotNull. Но мне понравились линтеры и форматтеры — благодаря им исправилась половина моих ошибок.

Сама библиотека для работы с GitHub сильно разочаровала. Хочешь получить текст коммита в пулл-реквесте? Запрашивай через API. Хочешь имя ветки? Используй окружение или костыли. Конфигурация в yaml, но параметры только строчные. Наконец, добила возможность гонок: все работы из action’ов запускаются параллельно, а контекст запуска статичный, поэтому пришлось менять получение тела из контекста на запрос к API.

СсылкаКомментировать

Что говорит наука о разработке?

Многие “лучшие практики” разработки основаны на мнениях и личном опыте, а не на исследованиях. А исследований про процесс разработки очень мало и к ним есть много вопросов относительно качества. Мне понравился доклад на эту тему.

В качестве примера приводится исследование про полные и сокращенные имена сущностей: оно показало, что при отладке нет разницы, написано ли employer_number или emp_num. Но при изучении самого процесса отладки разработчиками выяснилось, что в двух группах просто использовали разные подходы для отладки, и оба сработали примерно одинаково.

В другом исследовании выяснили, что в говнокоде больше вероятность наличия багов, но при этом его переписывание не коррелирует со снижением числа багов. Еще было исследование исходников кучи проектов на GitHub с результатом, что программы на ФЯП имели меньше багов, чем программы на императивных языках, а на языках с автоматическим управлением памятью — меньше, чем на языках с ручным управлением и т.п. (что вроде очевидно). Однако последующее исследование показало ошибку в методике, а с ее учетом выяснилось, что ни у одного языка нет преимуществ относительно другого. Похожие проблемы были при исследовании TDD, систем типов, парного программирования.

Но есть и вещи, которые более-менее подтверждаются наукой. Эмпирически доказано, что устройство организации сильно влияет на устройство кода. Многие самые дорогие баги — это проблемы архитектуры или требований. Практика, которая работает — код-ревью, она позволяет ловить 60-80% багов. А еще одни из самых существенных факторов для качественной разработки (да и любой работы в целом) — это отсутствие стресса и хороший сон. Бессонная ночь = -50% к производительности. Кранчи делают итоговый продукт хуже по всем метрикам.

Ссылки на статьи из доклада можно найти тут.

СсылкаКомментировать

Насколько уникален UUID?

Напомню, что UUID — это идентификатор вида ac2c6d9a-68d0-4802-948b-da8cc594ac80, который содержит 16 байт или 128 бит. При этом в 4 версии из этих 128 бит только 122 случайны (и генерируются обычно хорошим генератором случайных чисел), остальные 6 — это 4 для номера версии и 2 зарезервированных бита.

Чтобы оценить вероятность совпадения, можно воспользоваться формулой для “парадокса” дней рождения, а именно ее приближением:

p(n, b) =  1 - e^(-n(n-1)/2^(b+1)))

где n — количество сгенерированных элементов, а b — количество случайных бит (2^b — количество вариантов).

Дальше надо прикинуть, сколько будет UUID в системе. Пусть нагрузка на систему — 1000 RPS, и при каждом запросе генерируется новый UUID. Тогда за 1000 лет будет сгенерировано всего 31,5 триллиона идентификаторов, а вероятность, что будет пара одинаковых — всего 9*10^-11, чем можно спокойно пренебречь.

СсылкаКомментировать

Круглые скобки в Scala

Иногда слышу в качестве одной из претензий к Scala “почему для получения элемента из ассоциативного массива используют круглые скобки?”. В одном из докладов услышал интересную гипотезу на эту тему.

Что такое “map”? Вообще это карта, и имеет происхождение от латинского “mappa mundi” — “лист мира”. Если заглянуть в словарь поглубже, то там будет упомянуто еще математическое значение — синоним “mapping”, отображения [из одного множества в другое], что большинстве случаев можно считать синонимом функции.

Ассоциативный массив — это взаимооднозначное соответствие ключей и значений. Т.е. это практически частичная функция, которая записана без всяких обобщений, а тупо набором соответствий. Если ассоциативный массив называется как функция, работает как функция и крякает как функция, то это, вероятно, и есть функция. Поэтому и скобочки как у функции. Можно, конечно, возразить, что можно менять, но это неверно — по умолчанию все иммутабельно.

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

СсылкаКомментировать

Action для коммита в другой репозиторий

У меня на сайте сейчас два способа подключения чего-то стороннего: через git read-tree и через git submodule. Теперь появился еще один: через github action, который пушит коммит в другой репозиторий. Этот вариант оказался самым удобным, жаль, что я на него наткнулся недавно и случайно.

Пример настройки и сгенерированный коммит.

СсылкаКомментировать

Будьте проще

Неплохой, хотя и немного спорный доклад про то, что надо делать вещи простыми, но это нелегко. Простота почти синонимична с изолированностью, модульностью. У простых вещей масса преимуществ — ими проще оперировать, их проще понимать, поддерживать, комбинировать (привет, UNIX-way).

В свою очередь сложные вещи проблемны из-за того, что они “переплетают” между собой несколько сущностей. Например, переменные переплетают между собой значение и время, а циклы — действие и способ его исполнения. Причем можно построить эквивалентную программу с использованием более простых конструкций (но не обязательно менее мощных).

Есть более полная версия, там чуть подробнее и есть юморок.

СсылкаКомментировать

Запросы к jsonb

На SQL можно написать порой ужасные вещи, например:

SELECT id, jsonb_path_query_array(value, '$[*].entityType')
FROM demo WHERE value @? '$[*] ? (@.entityId == 1 &&
    (!exists (@.childrenIds) || exists (@.childrenIds ? (@[*] == 4))))';

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

А в запросе выбираются элементы массива, в которых есть объект с entityId и либо пустым childrenIds, либо содержащим 4. В ответе выдаются только значения полей entityType.

Полный пример
CREATE TABLE demo(
  id int not NULL,
  value jsonb Not NULL
);
INSERT into demo VALUES
  (1, '[{"entityId": 1, "entityType": "type1", "childrenIds": []}, {"entityId": 10, "entityType": "type5", "childrenIds": [30, 20]}]'::jsonb),
  (2, '[{"entityId": 1, "entityType": "type2"}, {"entityId": 15, "entityType": "type1", "childrenIds": [4, 20]}]'::jsonb),
  (3, '[{"entityId": 1, "entityType": "type3", "childrenIds": [5, 4]}, {"entityId": 25, "entityType": "type4", "childrenIds": [30, 4]}]'::jsonb),
  (4, '[{"entityId": 35, "entityType": "type4", "childrenIds": [35, 4]}]'::jsonb);

SELECT id, jsonb_path_query_array(value, '$[*].entityType')
FROM demo WHERE VALUE @? '$[*] ? (@.entityId == 1 && (!exists (@.childrenIds) || exists (@.childrenIds ? (@[*] == 4))))';

Поиграться можно тут. В ответе будет 2 и 3 запись.

СсылкаКомментировать