
Чого насправді варта версія 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.