Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
17.04.2024
Отримання GPS-треку з відеореєстратора
Давно в мене є маленька мрія взяти відео з реєстратора та накласти на них UI перегонової гри. Ну, тобто в першу чергу мінікарту, а також бажано спідометр та трек прогресу. Проте такий проєкт потребує багато розрізнених навичок. Вирішив потроху розплутувати.
Для початку, мені потрібний GPS-трек. Якщо придивитись, то мій реєстратор додавав координати GPS прямо на відео. А значить, за допомогою OCR можна забрати їх звідти. Так я собі й уявляв початок роботи — розтягати відео на кадри, залучити якийсь OCR, та витягувати координати.
Однак… цього робити виявилось не потрібно. В мене була надія, що відео містить трек як метадані — так само як світлини містять координати в блоці EXIF. Так воно і є — та метадані можна витягнути епічною утилітою ExifTool. Називаються дані QuickTime Tags та, в моєму випадку, містять не тільки координати, а й швидкість та навіть азимут. Якщо хочеш перевірити своє відео, то ось команда:
exiftool -g3 -ExtractEmbedded video.mp4
Кожний пакет даних вже привʼязаний до кадру, тобто залишається (якщо це слово доцільне) згенерувати відповідний кадр UI та накласти його. Далі буде.
16.04.2024
URL в повідомленнях про помилку
Просте та геніальне рішення: щоб покращити досвід від ваших повідомлень про помилки, можна зробити на сайті підтримки сторінку з поясненням та… вставити посилання на цю сторінку прямо в текст помилки. (Йдеться про повідомлення в API чи SDK, не в інтерфейсі — хоча там посилання на довідку теж доцільні.)
Дійсно, програмним засобам абсолютно байдуже, чи є зміст повідомлення текстом, чи посиланням. А людям не потрібно пояснювати, що є посиланням, або що з ним робити. Таким чином ми можемо розширити зміст пояснення практично без обмежень.
Мені подобається, що почати можна буквально зараз (якщо у вас є сайт підтримки, але якщо його немає, то краще почати з нього.) Та це не повинно бути глобальною зміною — можна взяти перше повідомлення, про яке частіше доводиться пояснювати клієнтам.
Також подобається, що таку документацію легше підтримувати, бо в коді є на неї посилання. Якщо ж робити окремо, на кшталт сторінки з переліком кодів помилок — то є всі шанси забути оновити її, коли зʼявляється ще один.
Такий підхід я бачив в деяких API Google. Також його регулярно використовують сервери SMTP. Не дивно, бо по SMTP ми постійно стикаємося з незнайомими серверами та несподіваними ситуаціями. Посилання — часто єдиний спосіб пояснити, що відбувається, та головне — що клієнт мусить зробити.
15.04.2024
Три різновиди імпорту/експорту в продукті
-
Імпорт/експорт для звʼязку з іншими продуктами. Ми робимо його, щоб користувачам легше було переїхати до нас. Або, щоб вони не відчували себе запертими — це теж важливо. В багатьох галузях є стандартний формат. А в інших — наприклад, в менеджерах задач — кожна програма має власний. В будь-якому разі, такий імпорт/експорт не зобовʼязаний включати всі можливості нашої програми, а тільки те, що можливо та доцільно переносити в іншу програму.
-
Архівний імпорт/експорт призначений зберегти, а потім повністю відновити весь користувацький зміст. Тут не важливо, щоб формат був відкритий або зрозумілий — головне, щоб наша програма його вміла читати. Краще, коли архів переживе оновлення програми.
-
Експорт для користувача призначений, щоб його було зручно прочитати. В ідеалі також відредагувати та імпортувати назад. Але головна мета — щоб користувач міг забрати власні дані. Формат обовʼязково повинен бути текстовим - JSON, CSV, YAML, або навіть просто текст.
Деколи ці режими можуть бути поєднані в один. Проте частіше їхні потреби знаходяться в протиборстві: для користувача зручніше текст та дублювання інформації, для архіву — двійковий формат та нормальна форма. Або: ніби у “стандартний” формат можна запхати всі дані програми, але не в такому представленні, щоб прочитати їх назад.
Тому я б радив спочатку зʼясувати, який саме імпорт/експорт потрібно зробити, та не намагатись покрити всі вимоги відразу.
14.04.2024
Розклад-автопілот
Читаю нову книгу від автора “Глибока робота” - “Повільна продуктивність”. (Насправді, не читаю, а слухаю аудіокнигу — ось вам вже порада по продуктивності.)
Багато чого в книзі мені апріорі близько — наприклад, ідея того, що якщо звести кількість обовʼязків до мінімуму, можна досягти великого успіху в тому, що залишилось. (Я, нарешті, навчився відповідати — чесно — що “до вашого питання зможу дістатись тижня через два”. Замість того, щоб накопичувати обіцянки, а потім не встигати їх зробити.)
Про розклад-автопілот. Це свідома організація власних обставин так, щоб обовʼязки виконувались “на автопілоті”, тобто випливали з обставин. Наприклад: “після обіду я беру чашку кави, сиджу на кухні та перевіряю всі пул-реквести.” Або: “поки я роблю каву зранку, також розкладаю посудомийну машину”. Або: “Середами зранку ми зустрічаємось в кавʼярні та працюємо над проєктом.”
Цікаво, що так чи інакше у всіх є звички-“автопілоти” - прокидатись та залізати в телефон на 15 хвилин це теж “автопілот”. Тобто він дійсно працює. Залишається докласти усвідомленості та змусити автопілот працювати на тебе.
13.04.2024
Гра
Сьогодні завершив роботу та гра дійсно готова до того, щоб нею поділилися. Пограти можна тут: monsterland.leonid.codes.
Найскладніше було зробити екранну клавіатуру. Спочатку просто зрозуміти, як же ж воно може працювати: клавіатура повинна створювати події KeyboardEvent
. Рухомих частин забагато, тому довго шукав, що не так з прикладами з Гітхабу, поки не зрозумів, що створюю події за старим стилем (тут в стандартах багато змін, як не дивно). Поміняв на актуальний та все запрацювало. (А якби код був на TypeScript, я б це швидше знайшов. Тільки розробка тут велася взагалі без тулчейну, напряму.)
Потім — налаштувати CSS, та ще й так, щоб на різних екранах було нормально. Сконцентрував зусилля на пейзажному режимі та ніби вийшло непогано. Навіть повноекранний режим працює (тільки не на iPhone - він його, як виявилось, не підтримує.)
Нарешті, довелося все ж трохи доробити гру, на рівні Community Patch. В першу чергу, додати візуальні запити, коли гра очікує на ввід — наприклад, після команди “кинути” треба вказати номер предмета — бо без того взагалі незрозуміло, чому гра зависла. Але також виправити пару очевидних багів та скоректувати колір тексту, бо не читалося зовсім.
О, до речі, щоб зробити картинку для фону, долучив ML-генератор Diffusion Bee. Якщо її ще й розмити, то вади стають непомітні — а для атмосфери фон досить важливий.
12.04.2024
З Borland Pascal в браузер
Пʼятничний проєкт: захотілось оживити гру, яку я писав 22 роки тому. Так, щоб можна було поділитися, звісно. Гра була написана на Borland Pascal для MS DOS. (Колись я вже це згадував.
По-перше, як його було запустити або скомпілювати. Переносити код на новішу платформу я точно не хотів — занадто багато зусиль та занадто далеко від оригіналу. Тоді мушу запустити BP, щоб скомпілювати. Як людина, яка багато грає в старі ігри з GOG, знаю, що ДОСівські програми запускають в DOSBox. Але додатково дізнався, що є свіжіша версія - DOSBox-X. З нею я прямо з macOS можу змонтувати в DOS всі потрібні теки, запустити Паскаль та отримати бінарний файл.
Далі можна було б загорнути свою гру в той самий DOSBox, як це робить той же ж GOG, та на цьому зупинитись. Проте на “поділитися” для мене це не витягує. Треба, щоб було в браузері. (От нещодавно син попросив знайти Donkey Kong для Atari 2600, та я швидко знайшов варіант з вебемулятором. Як зараз все просто стало.)
Щоб запустити програму для DOS в браузері, знайшов… той же ж DOSBox, тільки скомпільований у WASM за допомогою Emscripten. Як зараз все просто стало! Майже з першої спроби гра запустилася в браузері.
От тільки Emscripten додає купу зайвого обрамлення. А ще без екранної клавіатури важко грати — в мене давно немає діагональних стрілочок, наприклад. А на мобільному зовсім ніяк. Тож залишилось трохи доробити напилком та можна ділитись.
11.04.2024
Експорт даних з OpenSearch
В OpenSearch немає очевидного способу відвантажити зміст бази — такого, як pg_dump
, mysqldump
або команди \copy
. А потреба в такій операції, звісно, є — утворення резервної копії, синхронізація. Нещодавно просто потрібний був експорт даних, щоб проаналізувати їх разом.
Якщо поритися в документації, знайдемо механізм scroll
. Він саме і надає можливість забрати з бази не одну сторінку пошуку, а абсолютно всі результати.
Scroll
- це особливий режим пошуку; він вмикається опцією scroll
в пошуковому запиті. Пошук все одно поверне одну сторінку результатів, але також ми отримаємо вказівник scroll ID
, та за наступними сторінками підемо вже в окремий Scroll API.
На відміну від звичайного пошуку, Scroll зберігає контекст пошуку на стороні сервера. Тобто на відміну від пейджинації, яка може включати дублікати або пропущені результати, Scroll гарантовано поверне все, що є. Але, зберігання контексту коштує ресурсів, тому для звичайного пошуку цю опцію не рекомендують, та до того ж вона потребує особливого дозволу.
Так зі Scroll можливо відвантажити не тільки весь індекс, але й результати будь-якого пошуку в OpenSearch. Ось такий цікавий механізм. Порівняно з PostgreSQL, то це навіть зручніше, бо працює по HTTP та не потребує особливих команд або доступу до локального диска.
10.04.2024
BigQuery Write API як приклад гарного API
Довелося заганяти дані в BigQuery через тамошній Write API. В мене з Google Cloud досвід мінімальний, тому очікувань гарних не було. Був приємно здивований.
-
Запис відбувається тільки по gRPC. Щоб надсилати записи, ми мусимо спочатку оголосити для них схему Protobuf. Це розвʼязує відразу декілька задач. Protobuf це компактний формат даних — бо містить тільки зміст, а не структуру. Він обовʼязково повинен збігатися зі схемою таблиці — тобто ми впевнені, що не пишемо сміття.
-
Також завдяки кодогенерації легко з Protobuf отримати структури даних — наприклад, для Go. Та ще й схеми таблиць. В такому разі можна навіть взяти Protobuf за джерело істини та “мігрувати” таблиці у згенеровану схему.
-
В сам механізм запису вбудований захист від обриву. Точніше, є два різновиди потоків запису. Всі потоки завжди надсилають дані відразу в BigQuery, проте різниця в тому, коли ці дані зʼявляться в таблиці. Звичайний потік робить дані видимими відразу. А відкладений — тільки по явному закінченню запису. Якщо програма обірвалася та не закрила потік — то як і з транзакціями, дані будуть відкинуті. Єдине, що не подобається — неможливо атомарно закрити потоки в різні таблиці — тож у випадку паралельного запису можна створити розбіжності.
-
Одним словом, можна швидко зробити надійний імпортер, стійкий до помилок та змін схеми. Нарешті, запис через потоки відносно дешевий - 2.5 центи за гігабайт. Я завжди ставився до BigQuery як до дорогого сховища, але, виходить, дорого буде звертатись до тих даних — а не записувати їх.
09.04.2024
Контексти в Golang, осмислення
Контексти, як і тип error
, висвітлюють одну з головних ідіом Go: робити перетік програми очевидним. Навіть в збиток до стислості та легкості написання.
Там, де в інших мовах помилки самостійно “виринають” по стеку механізмом винятків — хтозна-куди — в Go ми примушені перевірити кожну помилку в кожному місці виникнення. Та, в 99% випадків, вручну реалізувати повернення вгору по стеку. (В 1% ми перевіряємо, чи це не io.EOF
або ще якась “прийнятна” помилка.) Це найбільше й дратує — що хоч ми робимо перевірки вручну, але практично з єдиним передбачуваним результатом.
Якщо error
- це найбільш прозорий спосіб повернути стан з середини назовні, то context.Context
- найбільш прозорий спосіб передати зовнішній… контекст всередину коду. Так, це значить, що ми передаватимемо параметр ctx
в кожну функцію нашого проєкту, де є бодай одна операція з мережею. Це включає не тільки багато очевидних функцій, але й деякі такі, де, здавалось би, ніякий контекст не потрібен. Наприклад: функція-конфігуратор, яка проміж іншим створює клієнт AWS, повинна передавати контекст. (Ця проблема нагадує поширення async
по коду в JavaScript.)
Виникає питання — навіщо взагалі це зроблено? Як я писав, контексти існують в першу чергу заради можливості скасування. Як і обробка помилок, це механізм для виняткових ситуацій, тому його цінність не очевидна. Також, контексти нічого не варті, якщо їх не скасовувати — буквально. На щастя, причини скасовувати завжди є. Сервера повинні обмежувати час на відповідь. Програми повинні зупинятись за командою.
Ми всі знаємо, що проблема зависання існує, так само як і проблема несподіваних помилок. Go, на відміну від інших мов, примушує нас не закривати на них очі.
08.04.2024
Бібліотека conc в Golang
Вже другий пост задоволення бібліотекою github.com/sourcegraph/conc. Такі в ній гарні обгортки для типових задач рівночасності.
Цього разу — конкретно про модуль conc/pool
. Він реалізує абстракцію “запустити декілька задач рівночасно та почекати на результат”. Ніби для того достатньо стандартного sync.WaitGroup
, але то буде примітивне рішення. Доведеться вручну лічити задачі, збирати результати, обробляти помилки…
З conc/pool
достатньо запустити необхідні задачі викликом pool.Go()
, а потім зачекати — викликом pool.Wait()
. Є декілька модифікацій пула залежно від змісту задач: чи повертають вони результат, або помилку; чи потребують вони контекст (з особливою можливістю скасувати контекст після першої помилки.)
conc/pool
підходить для ситуацій, коли ми запускаємо різнорідні задачі — приблизно як Promise.all
в JavaScript. Та головне, що він робить використання рівночасності легше та простіше. Наприклад, якщо нам потрібно завантажити дві неповʼязані сторінки. Зазвичай така задача надто маленька, щоб розкладати її на горутіни, канали, і все інше. Але з conc/pool
майже нічого зайвого писати не доведеться. Просто чудова маленька абстракція.