Последние месяцы я рецензирую научный софт для JOSS, Journal of Open Source Software. Это настоящий рецензируемый журнал с редколлегией, только статья в нём короткая, около тысячи слов, а главный объект ревью - репозиторий с кодом. Ревью идёт в публичном GitHub-треде под именем рецензента, и весь процесс от заявки до вердикта открыт любому желающему. Я инженер, не учёный: ни PhD, ни публикационного списка у меня нет.

Расскажу, как устроен процесс, чем ревью научного софта отличается от рабочего код-ревью, и покажу на конкретных эпизодах, откуда берутся находки: недекларированные зависимости, пример из README, который молча не делает ничего, шесть миллиардов прогонов ради проверки эталонных таблиц и четыре моих собственных фальстарта подряд при попытке доказать, что мой же тест вообще способен упасть.


Как я туда попал

Никакого приглашения не было. У JOSS все входящие статьи висят в открытых pre-review тредах на GitHub, и предложить себя рецензентом может любой.

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

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

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

Что такое JOSS

Обычный научный журнал устроен вокруг PDF. JOSS устроен вокруг репозитория: короткая статья это скорее аннотация, а рецензируется софт. Опубликованная работа получает Crossref DOI и накапливает цитирования.

Процесс живёт в GitHub-иссью. Редактор командует ботом, бот заводит тред ревью, каждому рецензенту выдаётся чеклист на три десятка пунктов: лицензия, авторство, установка, функциональность, заявления о производительности, документация, качество текста статьи. Галочку нельзя поставить «в целом норм», каждая проверяется отдельно, и весь тред навсегда остаётся публичным.

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

Чем это отличается от код-ревью на работе

На работе я смотрю дифф: стиль, архитектура, граничные случаи, тесты. Здесь дифф не главное. Главное - заявления.

Статья говорит: пакет делает X, работает на платформах Y, показывает производительность Z. Ревью научного софта - это проверка, что заявленное правда. Не «код выглядит разумно», а «я запустил и получил обещанное». Разница примерно как между чтением договора и попыткой по нему пожить.

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

Эпизод первый: соберись из чистого клона

Заметная часть находок добывается в первый же день самым скучным способом: свежая виртуалка, а для чисто питоновских пакетов чистый venv, клон репозитория, установка строго по README, запуск примеров строго как написано. Системные зависимости при сомнении страхует контейнер: venv изолирует только Python-пакеты.

Один рецензируемый пакет при таком прогоне дал сразу две находки. Одна из функций падала с ImportError: пакет использовал научную библиотеку, которой не было в списке зависимостей. У разработчиков она, разумеется, стояла, и по истории иссью более раннего отчёта об этом я не нашёл. А главный пример из README, с которого начинается знакомство с пакетом, молча не записывал выходной файл: код отрабатывал без ошибок, файла не появлялось. Соседний способ запуска из того же README файл писал. Один объект, два способа запуска, разное поведение, и ни строчки об этом в документации.

В другом проекте оба сниппета из getting started падали на чистой машине: работали они только в том окружении, где были написаны.

Авторы всё подтвердили: зависимость объявили, пример починили, сниппеты поправили, часть замечаний ещё в работе. Технически такие проверки простые, вся трудность в дисциплине: чистая машина и буквальное следование чужой документации. У автора проекта всё стоит и всё работает, поэтому сам он в такую ситуацию почти не попадает.

Эпизод второй: воспроизведи опубликованные таблицы

Самое интересное из четырёх ревью - библиотека проверки планарности графов. В репозитории лежат эталонные таблицы: сколько графов на N вершинах планарны, сколько нет, с разбивкой по режимам работы алгоритма.

Галочка называется «функциональность подтверждена». Можно было запустить пример на десяти графах и поставить её. Вместо этого я сгенерировал полные наборы графов и воспроизвёл таблицы целиком. Арифметика такая: на 11 вершинах существует чуть больше миллиарда неизоморфных графов, каждый прогоняется в нескольких режимах алгоритма, плюс все младшие порядки. Суммарно вышло чуть больше шести миллиардов запусков.

Зачем так много. Затем, что самая большая таблица не пересчитывается в публичном CI проекта: полный прогон не влезает в шестичасовой лимит GitHub Actions. Пересчитывал ли её кто-то у себя локально, по открытым следам не видно. Теперь есть хотя бы один независимый пересчёт, до последней цифры.

Итоговые количества планарных графов я дополнительно сверил с последовательностями в OEIS. Это внешняя контрольная сверка: расхождение локализовало бы проблемный порядок N, а вот на чьей стороне ошибка, пришлось бы устанавливать отдельно. Расхождений не нашлось. И это тоже результат: под галочкой теперь лежит не впечатление, а воспроизведённый расчёт.

Эпизод третий: сломай нарочно, или четыре фальстарта подряд

Часть той же проверки шла под санитайзерами: ASan и UBSan, инструменты, которые ловят обращения к чужой памяти и неопределённое поведение в C. Прогнал, всё зелёное. И тут положено задать себе неприятный вопрос: а мой прогон вообще способен покраснеть, или я собрал его так, что он зелёный при любом раскладе.

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

Первая: вставил обращение за границу массива, санитайзер молчит. Оптимизатор на -O1 выкинул мой мёртвый код целиком, проверять стало нечего.

Вторая: решил убедиться, что бинарь вообще слинкован с санитайзером, и посмотрел символы в исполняемом файле. Файл оказался шестикилобайтной обёрткой libtool, настоящий бинарь лежал в .libs, а я минут двадцать делал выводы по скрипту-обёртке.

Третья: проверял линковку через nm -u, а санитайзер в этой сборке слинкован статически, и в неопределённых символах его, естественно, нет.

Четвёртая: мой аккуратно внесённый баг просто не скомпилировался.

Сработала пятая: функция с обращением по индексу, который вычисляется только во время выполнения, повешенная на constructor-атрибут, чтобы вызов гарантированно происходил при старте процесса. В каждой из двух сборок контроль сработал как положено. Только после этого зелёный прогон на настоящем коде стал что-то значить.

Тот же принцип в мелочах: go test с фильтром -run, не совпавшим ни с одним именем теста, честно пишет ok, и пустой прогон неотличим от успешного, пока не посмотришь, сколько тестов реально выполнилось. Проверка, которая не может упасть, не проверяет ничего. Звучит банально ровно до того момента, как насчитаешь у себя четыре фальстарта подряд.

Что писать автору, и что делать со своими ошибками

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

Тон отдельная тема. Ревью публичное, автор живой человек, и «у вас тут всё сломано» не работает даже когда правда. Работает «вот команда, вот вывод, вот что я ожидал увидеть». Блокер формулируется как факт плюс критерий выхода: что должно измениться, чтобы я снял возражение.

И самое сложное: свои ошибки я разбираю в том же публичном треде, где их сделал. У меня был скрипт, который считал покрытие документации и насчитал, что документирована только половина публичного API. Я успел написать автору, что это проблема. Потом заметил, что скрипт видел только определения в исходниках, а половина API этого проекта - макросы в заголовках. Пересчитал: покрытие оказалось около девяноста процентов. Пришлось так же публично отозвать собственную рекомендацию. Неприятно, но альтернатива хуже: рецензент, который не отзывает своих ошибок, ничем не лучше пакета, который молча не пишет выходной файл.

Что это даёт инженеру

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

Выглядит невыгодно ровно до тех пор, пока не посчитаешь, что получаешь взамен. Для меня это самый концентрированный тренажёр верификации из известных мне: за эти четыре ревью я собрал больше методических грабель, чем за год обычной работы, и все они один в один переносятся в профессию. Ещё вы читаете свежие работы по своей теме до публикации, и вопросы обсуждаются с автором напрямую, с ответами в публичном треде. И остаётся след: подписанное ревью нельзя приукрасить в резюме, оно либо есть, либо нет.

Как попасть, если захотелось. У JOSS открыт список входящих статей, ищется по языкам и темам: приходите в pre-review тред и предлагайте себя, коротко и с планом проверки. Отдельный путь - комитеты оценки артефактов при конференциях по системам и безопасности: POPL, NDSS, ASPLOS и другие набирают их через открытые формы самономинации, у каждой свои сроки и требования, работа по большей части письменная и асинхронная, опыт индустрии прямо приветствуется. Ни там, ни там не спрашивают степень. Спрашивают, по сути, одно: готовы ли вы собрать чужой код из чистого клона и проверить заявления запуском, а не прочтением.

Комментарии (0)