
Чего на самом деле стоит версия 2: 206 коммитов честных состояний
GeekBye v2 не был релизом новых функций. Это были 206 коммитов, нацеленных на одну идею: приложение никогда не должно врать о собственном состоянии. Вот чего это стоит — включая однострочную ошибку в lockfile, которая едва не помешала нам выпустить хоть что-то из этого.
Большинство релизов «версии 2» — это груда новых функций с числом побольше на обложке. GeekBye v2 был обратным. Он почти не принёс новых пользовательских возможностей. Его 206 коммитов были нацелены на одну неброскую идею:
Приложение никогда не должно показывать вам состояние, которого нет.
Это звучит очевидно, пока вы не посчитаете, сколькими способами десктопное приложение тихо врёт. Оно ставит галочку на загрузке, которая ещё в пути. Говорит «подключено» поверх сокета, умершего минуту назад. Молчит, пока ваш транскрипт теряется по дороге. Ни одно из этого не является сбоем. Это хуже сбоев, потому что приложение выглядит нормально, будучи неправым — а вы узнаёте об этом только позже, когда записи, которая вам была нужна, нет на месте.
v2 стал релизом, в котором мы пошли на охоту за каждой из этих мелких лжей.
Ложь, которую мы ненавидели больше всего: загрузка, которая «прошла»
GeekBye умеет делать резервные копии ваших записей в Google Drive. В v1 соединение с Drive считалось фоновой деталью — если работало, отлично; если нет, сбой был по большей части невидим. Приложение продолжало выглядеть подключённым. Вы полагали, что ваши записи в безопасности. Иногда это было не так.
v2 перестроил это вокруг честных состояний соединения. Drive теперь всегда находится в одном из нескольких явных, правдивых состояний — подключено, переподключается, отключено — и приложение транслирует каждое изменение этого состояния всему интерфейсу в тот же миг, когда оно происходит. Когда соединение разорвано, глобальный баннер переподключения говорит вам об этом, везде, прямо. Приложение больше не делает вид, что загрузка прошла, когда это не так. Если ваша резервная копия не может произойти прямо сейчас, вы знаете это прямо сейчас — а не через неделю, когда пойдёте искать файл.
Под капотом это небольшая архитектура, а не слоган: один источник истины для соединения, событие, разлетающееся к каждой части интерфейса при изменении, и баннер, читающий прямо из этого состояния. Нет пути, при котором соединение разорвано, а интерфейс выглядит исправным, потому что они читают из одного и того же факта.
Ложь потерянной записи
Второй крупной починкой честности было восстановление записей. Транскрипция в реальном времени требует живого соединения. В v1, если это соединение икало посреди сессии — мигание Wi-Fi, тоннель, переподключение VPN — аудио за время разрыва могло просто пропасть. В транскрипте была дыра, и ничто вам об этом не говорило.
v2 изменил контракт: аудио сохраняется локально во время разрыва соединения и сверяется потом. Когда канал падает, GeekBye держит аудио в безопасности на вашей машине; когда он возвращается, это буферизованное аудио отправляется и вшивается в транскрипт в нужное место. Плохие тридцать секунд сети больше не стоят вам тридцати секунд встречи. Это фундамент, на котором последующие релизы надёжности строились напрямую — механика переподключения-и-буферизации в статье почему ваш AI-заметочник останавливается при плохом Wi-Fi — та же идея, только закалённая.
Ложь шторма всплывающих окон
Есть более тонкая нечестность в том, как приложения сообщают о проблемах: они сообщают слишком много. Один нестабильный момент сети может выстрелить одной и той же ошибкой пять раз, и внезапно у вас стопка одинаковых красных тостов, хоронящих единственное важное сообщение. Это тоже нечестно — это шум, притворяющийся информацией.
Поэтому v2 добавил дросселирование тостов по ключу категории и маршрутизацию ошибок. Ошибки группируются по тому, чем они на самом деле являются, и каждая категория ограничена по частоте, так что одна лежащая в основе проблема даёт одно ясное сообщение, а не шквал. Проблема с лимитом частоты направляется к сообщению о лимите частоты; проблема с соединением направляется к сообщению о соединении. Приложение говорит вам что правда, один раз — та же дисциплина, что позже уберегла 429 от того, чтобы выдавать себя за выход из аккаунта в статье день, когда наше приложение заДДоСило само себя.
Число 206 — это и есть суть
Честные состояния, баннер переподключения, восстановление записей, маршрутизация тостов — это четыре идеи. Релиз был из 206 коммитов. Куда ушло остальное?
В неброский длинный хвост, которого «никогда не показывай ложное состояние» на самом деле требует. Каждое место, где старый интерфейс мог рассинхронизироваться с реальностью, нужно было найти и перепаять так, чтобы он читал из истины, а не из устаревшей копии. Каждый путь переподключения нужно было заставить обновлять общее состояние, а не гадать локально. Десятки мелких починок надёжности в записи, транскрипции и загрузках — каждая закрывает конкретную щель, где приложение могло выглядеть правильно, будучи неправым.
Вот чего стоит настоящая «версия 2», когда цель — доверие, а не функции. Нет единого заголовочного коммита. Есть 206 маленьких, и релиз лишь ощущается одной вещью — спокойствием — потому что все 206 тянут в одном направлении.
Релиз чуть не сорвался
Вот военная история, и хорошая, потому что она о том, как наше собственное приложение соврало нам.
Когда вы режете релиз, скрипт подъёма версии штампует новый номер по всему проекту. На v2.0.0 он обновил версию приложения — но package-lock.json, точный реестр зависимостей npm, остался указывающим на старую версию, 1.9.0. Локально всё было в порядке; никто не переустанавливает зависимости просто чтобы собрать. Но CI запускает npm ci, а вся работа npm ci — отказаться продолжать, если lockfile расходится с манифестом. Это фича строгости — и она сделала ровно то, что должна, завалив сборку на самом крупном релизе, что мы когда-либо резали.
Затем под ней всплыла вторая, коварнее. Более новый npm вырезал опциональную транзитивную зависимость — пакет encoding, который тянет node-fetch — из lockfile как ненужную. Вот только чистой установке нашего CI она была нужна, так что установка сломалась таким образом, который не имел ничего общего с нашим кодом и всё общее с тем, что реестр был тонко неправ.
Оба были однострочными починками: пересинхронизировать lockfile с реальной версией, восстановить вырезанную запись. Оба также идеальные, отрезвляющие примеры ровно того, о чём был v2 — состояния, которое утверждало, что оно правда, а это было не так. Lockfile должен быть честной записью того, от чего зависит приложение. Когда он отъехал от реальности, сборка сделала ровно то, что теперь делает наше приложение для пользователей: отказалась притворяться, что всё в порядке. Выпуск релиза надёжности оказывается проблемой надёжности до самого дна.
Три вещи, которым нас научил v2
- Опасные сбои — тихие. Сбой объявляет о себе. Ложное «подключено», фантомная удачная загрузка, транскрипт с невидимой дырой — это стоит вам доверия именно потому, что ничто не выглядит неправильным. Охотьтесь за тихой ложью.
- Честность — это архитектура, а не сообщение. Нельзя прикрутить «говори правду» к интерфейсу в виде баннера. Баннер должен читать из того же единственного источника истины, что и всё остальное, иначе он становится ещё одной вещью, которая может быть неправа. Один факт, разлетающийся наружу — никогда две копии, которые могут расходиться.
- Версия 2, достойная своего числа, в основном невидима. Если ваш v2 — это список функций, это v1.5 с маркетингом. Настоящий v2 — это 206 коммитов, на которые никто не может указать по отдельности, складывающихся в продукт, который просто перестаёт вам врать.
GeekBye v2.0.0 — фундамент, на котором строился каждый релиз с тех пор. О том, что несёт этот фундамент, читайте почему ваш AI-заметочник останавливается при плохом Wi-Fi, день, когда наше приложение заДДоСило само себя (v2.0.1) и живая транскрипция, когда файрвол блокирует WebSocket (v2.0.8). О спокойствии, ради которого всё это делалось, что нового в GeekBye v2.