Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
04.06.2023
Зберігайте гроші в цілочисельних значеннях
Ми в Сінтрі зберігаємо всі грошові суми у цілочисельній формі, тобто як значення в копійках. Це важливо не тільки для проведення арифметичних операцій, а й для збереження та відтворення значень. Дробові числа ніколи не можна використовувати для точних обчислень, спробую пояснити, чому.
На перший наївно-освічений погляд, яка там різниця — адже 1.23 гривні
це теж саме що й 123e-2
, в науковій нотації. Але це тільки абстракція про людські очі. Насправді дробові числа зберігаються у двійковому вигляді, з двійковою експонентою, а ніяк не десятковою. (З 2008 існує десятковий стандарт, але на практиці його ніде не побачиш.)
Чому це має значення? Для цілих чисел двійкове зображення нічого не змінює, та ми можемо практично про нього не знати. Але з дробами все не те що гірше, воно дуже погано. Десяткові дроби, як правило, не перекладаються в красиві та акуратні двійкові дроби (пояснення виходить за рамки цього короткого оповідання.) Навпаки, типовий десятковий дріб стає періодичним двійковим. Так що ті 1.23
гривні у двійковій системі мають вигляд 1.00111010111...
. При запису нескінченного дробу в скінченну памʼять ми обовʼязково втрачаємо точність.
При цьому при перетворенні округленого значення в десятковий вигляд ми можемо отримати вже інше значення: 1.23046875
. Принаймні все двійкові дроби стають скінченними десятковими (хоча це мало нам допомагає.) Також добре, що такий десятковий дріб вже нормально вкладається у двійковий… якщо його не округлити в десятковому вигляді. Також точність втрачається на будь-якій арифметичній операції, навіть на додаванні.
Так на кожній операції значення поступово псуються, або, як то кажуть, гниють. Це не якесь теоретичне занепокоєння — ми стикнулись з ним вже на перших місяцях внутрішнього тестування. Після цього всю математику перевели на цілі числа, які стають дробами тільки для відображення їх в інтерфейсі.
Звісно, ми не можемо зберігати дробові значення копійок. Проте, як виявляється, реальні дробові копійки не існують. Та, хоч теоретично можна, скажімо, рівно поділити 1000 гривень на 7 днів та забюджетувати 142.85714286… на день, практично це не має жодного сенсу; фактично ми округляємо до 142.86 та коректуємо останній день, щоб вписатися в початкову суму.
03.06.2023
Поєднання HomeAssistant, Firebase та SwiftBar: коли системи працюють на тебе
Приємне: віддалений геймінг з Parsec працює краще, ніж я міг собі уявити. В локальній мережі з дротовим підключенням взагалі непомітно, що граєш віддалено. Але не тільки — навіть за 200 км від дому та по вайфаю все ще можна грати із задоволенням! Так, час від часу підключення гальмує, але достатньо рідко щоб це не шкодило. Рекомендую.
Є тільки одна проблема: той самий компʼютер може використовуватись й напряму. Та Parsec не пропонує способу дізнатись, чи зайнятий він, чи ні. Щоб уникнути незручних ситуацій, вирішив зробити для себе індикатор.
-
Стан компʼютера, а точніше, стан медіацентру, до якого він під’єднаний, знає Home Assistant. Чого я точно не хочу робити, так це відкривати розумний дім в інтернет. Кількість вразливостей неможливо осягнути. Тому замість того обираю інший підхід — нехай Home Assistant сповіщає зовнішній сервіс про стан медіацентру. Для цього є інтеграція RESTful Command. Якщо дуже спрощено, то я створив пару автоматизацій, що спостерігають за джерелом зображення, та роблять відповідні HTTP виклики.
-
Куди підуть виклики? Для цього перевірене рішення - Firebase. Він надає відразу й функції, й сховище, та все це безплатно. В мене навіть проєкт вже готовий з минулого разу. Зробити функцію, яка записує в Cloud Firestore стан компʼютера — справа на пʼять хвилин. До речі, для такого проєкту на одного користувача для авторизації мої функції просто мають секретні назви з кодом всередині.
-
Нарешті, як перевірити поточний статус? Сам інтерфейс Firestore його показує — до речі, як я виявив, він навіть оновляється наживу, що допомогло в тестуванні, бо я міг перемикати налаштування медіацентру та миттєво спостерігати за результатами. Та заходити щоразу в вебконсоль Firebase - незручно. Тому зробив ще одну простеньку HTTP-функцію, та скрипт для Swiftbar, що її викликає. Знайшов, що якщо у Swiftbar вжити суфікс
|refresh=true
, то по клацу елемент просто оновлюватиметься. Дуже зручно.
Тепер я можу легко перевірити, чи не зайнятий компʼютер, перед тим як підключатись в Parsec. І на це пішло небагато часу, бо рішення складалося з частин, що в мене вже були, та треба було тільки поєднати їх між собою.
02.06.2023
Корисні оповіщення
Сьогодні читав статтю DevEx: What Actually Drives Productivity та мою увагу звернув один проблемний робочий момент. Читав я тому, що чекав завершення задачі (в Terraform.) А коли вона завершиться — не знав; треба було не забути поглядати в термінал. Так можна забутися та надовго полишити роботу. В контексті статті — це поламаний Feedback loop.
Ба — та це ж ідеальна ситуація для застосування оповіщень! Нехай коли команда в терміналі завершується, то система мені нагадає. Я не люблю оповіщення, бо вони відвертають від роботи, але в цьому випадку навпаки, допоможуть до неї повернутись.
На щастя, я не перший таке придумав. Для моєї улюбленої оболонки fish
є плагін done Для zsh є аналогічний zsh-notify, але я сам не перевіряв. Ще VSCode вміє видавати звуки на закінчення команди, та мені віконце оповіщення більш до вподоби.
До речі, самі оповіщення уможливлює утиліта terminal-notifier, яку можна легко застосувати у своїх скриптах. Єдине, що вона потроху старіє, а надійної заміни я не бачу.
Взагалі цікава стаття, на мою думку кожна команда може сформувати для себе набір KPI-індикаторів, щоб не ходити по колу нескінченних проблем на кшталт “доводиться довго чекати схвалення ПРів”, які виправляються, та через місяць-два підіймаються наново. Якби були показники, то можна стежити постійно та не доводити до критичного стану.
01.06.2023
Коротке розслідування в те, як Facebook збирає інформацію
Цікава подія трапилася: показав дружині крісло (до речі, безплатна реклама - home-club.com.ua - надійний спосіб замовити щось з Ікеї в Україні.) А ввечері то саме крісло зʼявилося у дружини в рекламі на Фейсбуці. При тому, що показував я на своєму компʼютері та ніякої очевидної передачі даних не було.
Що за екстрасенсорні здатності проявляє фейсбук в цьому випадку? Чи почув нашу розмову, наприклад? Мені здається, можна пояснити й простішими технологіями.
На сторінках Home-Club є скрипт фейсбуку. (Що логічно, раз вони запускають там рекламу.) Тобто фейсбук знає, що я дивлюся цю сторінку. Але до чого тут дружина? Тут є нюанс. Я взагалі фейсбуком не користуюся та активного сеансу не маю. Тому приєднати відвідування сторінки до мене не вийде. Але ж на той самій домашній IP адресі сидить дружина. На мою думку, тому подія записується на той обліковий запис, що можна привʼязати до IP адреси, та далі сприяє появі товару в рекламі.
Я б хотів звернути увагу на те, що збір аналітики йде далі, ніж просто “записати, які сторінки ти відвідував”. Вони збирають максимум даних та колупаються в них, щоб вичавити корисну інформацію. Щоб запобігти цьому, треба користуватись блокувальником трекерів та реклами (реклама та трекери йдуть рука об руку.)
До речі, в мене взагалі мають бути заблоковані всі трекери, аж два рази — або вбудованим захистом Safari, або 1Blocker. Але з усім тим вкладка “Мережа” інструментів розробника чітко вказувала на взаємодію з фейсбуком. Виявилось, що після оновлення 1Blocker необхідно відкрити один раз сам додаток, щоб він завантажив правила. Бо сучасні блокувальники реклами не втручаються в роботу браузера, а передають йому інструкції про те, які ресурси блокувати. Такий підхід практично не витрачає додаткових ресурсів.
31.05.2023
Оптимізація критичного шляху
Трохи міркувань з приводу дизайну архітектури навколо критичного шляху. Це така ділянка коду, який викликається найчастіше та містить головну бізнес-логіку вашого сервісу. Наприклад, в Телеграмі критичний шлях — це відправлення повідомлення. Чи є він у вас? Варто замислитись, якщо не знаєте.
-
Критичний шлях має проходить через якнайменшу кількість компонентів. Це звучить трохи тривіально. Проте насправді такий підхід йде всупереч класичному дизайну, де відповідальності розподілені по сервісах. Раджу критичний шлях зафіксувати як неподільне ціле, а все інше вже можна розкладати, як зручно. Також критичний шлях — гарний кандидат на винесення в стислий сервіс швидшою мовою програмування, такою, як Golang.
-
Все, звідки відбувається читання на критичному шляху, має бути закешоване. Або мати швидкодію та здатність до масштабування, порівняну з кешем. Кеш не обовʼязково має бути довготривалим — навіть кешування на 1 хвилину здатне перетворити високе навантаження в помірне. Потреба тут не стільки у швидкості, навіть, а в надійності.
-
Всі некритичні операції запису мають бути приховані за чергою. Взагалі черги — гарний механізм перенесення “тиску” між сервісами. Більш звично бачити чергу у випадку довгих задач. Але на критичному шляху навіть запис в базу може бути надто повільною операцією. Особливо коли потрібно спочатку прочитати старі дані, щоб їх оновити… якщо воно не потрібно для відповіді на запит, прибираємо в чергу.
На останок, не треба всього цього робити з MVP. Достатньо заздалегідь усвідомити, де той критичний шлях пролягатиме. Окрім витрат на передчасну оптимізацію, доробляти вже зоптимізований код завжди складніше.
30.05.2023
Пакування інформації в зашифрований пакет найменшої довжини
Турбує мене ця тема. Ну, наприклад, є невеличкий набір параметрів (на кшталт JWT). Потрібно зробити з них якнайкоротший зашифрований рядок. Може, для посилання, може, для QR-коду, і так далі. Що з цим можна зробити?
-
Кодування інформації. Логічно обрати формат, в якому структура даних не кодується разом з даними, а міститься в програмі, яка кодує та декодує. (Власне, так робить будь-який формат файлів.) Це значить, що точно не JSON та навіть не msgpack. Мені здається, що найкраще для такої задачі підходить формат Cap’n’Proto - він є логічною еволюцією формату protobuf. Тут структура пакетів задається схемою.
-
Насправді, можна обрати й msgpack, якщо кодувати не структуру, а масив. Масив — найбільш очевидна “компактна” структура даних. Проте тоді доведеться вручну збирати та розбирати ці масиви, а згодом ще й вручну версіонувати. Тож якщо є доступ до Cap’n’Proto або protobuf, то сенсу в цьому мало.
-
Компресія може згодитись й для маленьких рядків. Компресія GZIP будує словник з послідовностей, які повторюються. Тому якщо в рядках присутні повторення, то є можливість заощадити. Але є проблема — сам словник теж має бути включений у пакет. Тому важко досягти корисної компресії на маленьких рядках. Тут є корисний трюк. Замість того, щоб будувати словник з нуля для кожного пакету, можна підготувати спільний словник та тримати його окремо в коді. Це допоможе, якщо деякі фрази повторюються у більшості пакетів. Такий підхід, між іншим, використовує Cloudflare.
-
Шифрування. Тут не уникнути збільшення пакета, бо окрім даних, треба включити вектор ініціалізації та підпис. Підпис потрібний для того, щоб пакет не можна було підробити — а також щоб помітити, якщо він зіпсується Якщо я не помиляюся, то треба брати симетричне шифрування з аутентифікацією
AES-GCM
, та не вигадувати нічого свого. -
Кодування двійкового рядка в текст. Зашифрований рядок буде двійковим. Зазвичай його треба перетворити на текст. Тут можна вже обрати те, що підходить під потреби. Є Base64. Є Base58. Є Base85. Все залежить від того, який набір символів прийнятний. В ідеалі — просто залишити у двійковому вигляді.
29.05.2023
Як апгрейдиться React Native
Сьогодні апгрейдив React Native, бо стара версія (ну як — стара? минулого року) відмовилася компілюватися на XCode 14.3. До речі, гарна метрика зрілості технології — наскільки довго старі версії продовжують працювати без підтримки.
Кожному, хто працює з RN, обовʼязково треба знати про React Native Upgrade Helper. Це утиліта, яка порівнює код демонстраційного додатка з різних версій React Native. Вона незамінна для впровадження базових змін в коді, від нових версій бібліотек до коментарів з розʼясненнями. (Однак редагувати ваш код доведеться вручну.)
Цього разу після заміни версій пакетів yarn install
не зміг завершити роботу за браком памʼяті. Збільшення виділеної йому памʼяті (корисна команда: env NODE_OPTIONS="--max-old-space-size=8192" yarn
) тільки робило агонію довше, але так само не призводило до успіху.
Тому спробував оновлювати поступово, одна мінорна версія за іншою. Це допомогло. Для останньої версії також довелося оновлювати по одному пакету за раз. Але, в решті решт, все оновилось.
Потім ще цікаві ручні оновлення React-Native-Navigation та React-Native-Firebase; обидві бібліотеки мають глибоку інтеграцію в додаток та тому потребують редагування коду на Objective C.
Окрім банальної сумісності з поточною версією XCode, я також отримав так звану “нову архітектуру”, хоча поки не знаю, що це дає на практиці. Як бачите, React Native ще серйозно розвивається. Це з одного боку добре — бо проєкт є успішним, попри те що багато людей його зневажають. А з іншого боку — про стабільність можна тільки мріяти.
28.05.2023
Cyberpunk 2077 - гра наступного покоління, яку підвів її сюжет
Напевно, те, що більшість знає про Cyberpunk 2077 - останню гру від польської компанії CD Projekt Red, творців Відьмака та GOG - це те, що вона була сповнена багів та погано йшла на старих системах, в першу чергу, на Playstation 4. Що прикро, бо насправді гра вийшла варта уваги.
Я тільки-но пройшов цю гру, та хочу поділитися враженнями після 100+ годин з нею. Щодо швидкодії, то здається, вони багато виправили, хоча в мене на ПК проблем і з початку не було.
Що є видатного в Cyberpunk 2077:
-
Величезний, щільний світ. Він поділений на райони, які кожний мають свій “вайб”, тож валасатися вулицями від завдання до завдання приємно. На жаль, вони пішли з моделлю GTA, тобто місто заповнене випадковими людьми та машинами, та не має стійкого стану. Проте я був вражений, коли ближче до кінця гри натрапив на знайоме місце — колись давно я вичищав базу стервʼятників, а тепер там було мирне поселення. Дрібниця, але хоч щось зберігається. Також, як і в GTA, заходити можна тільки в окремі будинки.
-
Дійсно разюча графіка. При такому масштабі світу зробити ще й трасування променів — дуже круто. Освітлення створює мальовничі пейзажі, як вдень, так і вночі. Якщо не прискіпуватися до кожного багу, то загальне враження чудове. Треба розуміти, що це не просто три квартали якогось Deus Ex, а ціле місто.
-
Декілька можливих стилів гри, з якими цікаво експериментувати та знаходити комбінації. Я починав гру зі скритності та хакерства, потім спробував бути снайпером, а на кінець відкрив навичку “Cold blood”, яка робить можливим “run and gun”. До того, багато різновидів зброї та спорядження. Та, головне, гра збалансована так, що робити все “в лоб” неприємно складно, тому відточування власного стилю гри має сенс.
Що поганого:
-
Найгірше то сюжет та все що з ним повʼязано. Світ Cyberpunk 2077, як і весь жанр кіберпанк, несерйозний. Ніхто не аналізує, що значить для людини відпиляти всі кінцівки та замінити на залізні. Або як живе місто, де вбивство — це абсолютно нормальна, легітимізована річ. Це домовленості світу, та ми їх приймаємо, як є. Але ж сюжет Cyberpunk 2077 максимально мелодраматичний. Герої поводяться, як у Шекспіра. Головному герою в рота пхаються фрази, які несумісні з обраним стилем гри. Та якщо з Відьмаком це проходить, бо Геральт — вже зафіксована особистість, то тут повний людонаративний дисонанс. Моральних виборів у грі майже немає, а геймплей обертається навколо вбивства — звичайних, хоч, може, й морально сірих, людей. При цьому наш герой виставляється протилежністю анархіста, якого грає Кіану Рівз.
-
Решта завдань недостатньо повʼязані між собою. Окрім купки сюжетних ліній, решта це просто окремі “рівні гри”. Прийшов на локацію — виконав — отримав гроші. Тому немає відчуття місця. Я б хотів, щоб у світі були хаби, куди ти приходиш багато разів, та з якими можна поріднитися. Бо я помітив, що за 100 годин гри в мене майже не має місць, які я можу знайти на карті та повернутись туди. Світ яскравий та різноманітний — але в ньому немає за що зачепитись. `
-
Система нагород та предметів не цікава. Майже все, що ти знайдеш у світі — це зброя та одяг. Але не потрібне ані те, ані те. Зброя — випадкова варіація невеликої кількості моделей, так що рідко знайдеш щось новеньке для себе. Одяг — його безліч, але зовнішній вигляд мало що значить у грі від першого лиця. Ефектів одягу, окрім броні, майже немає. Тож виходить, що окрім унікальної зброї час від часу, грі немає чим нас нагородити.
Чи можу я порадити Cyberpunk 2077 сьогодні? Так, якщо ти будеш грати на ПК, та тебе цікавить механічна сторона. Тоді тут є з чим погратись. А от якщо хочеться “Відьмака з кіберімплантантами”, то такого не буде.
27.05.2023
Hugo як фреймворк для фронтенду
При створенні поста про похід довелося випробувати вбудовані в Hugo інструменти для компіляції JavaScript.
Висновок такий, що Hugo здатний замінити як сервер розробки, так і бандлер JavaScript. Це для мене дуже привабливо, бо використання Hugo дозволить поєднати статичний маркетинговий сайт та додаток в єдине ціле. Або міксувати блог та різні цікаві експерименти на JavaScript.
Для компіляції JavaScript Hugo містить бандлер ESBuild. Оскільки ESBuild написаний на Go та Hugo написаний на Go, то інтеграція відбувається на внутрішньому рівні. З поганого: конфігурація ESBuild прихована всередині, тому нічого з нею не зробиш. Наприклад, зараз вона не дає імпортувати SVG файли через технічно простий брак налаштувань. Але взагалі того, що є, цілком вистачає. Для Typescript можна редагувати tsconfig.json
як звичайно.
Ще з поганого: плагіни ESBuild не підтримуються, бо для них потрібний інтерпретатор JavaScript. Тому — ніякого Svelte - шкода. А JSX та відповідно React вбудовані, та з ними проблем немає. (Взагалі в мене дуже поганий досвід використання Svelte як з ESBuild, так і з Babel, тільки з Vite все задовільно працює.)
Зате вбудована підтримка Babel, а для стилів - Sass, та PostCSS. Перезавантаження наживу добре працює. Причому файли JS та CSS включаються в шаблони звичайних сторінок, тому можна, скажімо, деякий скрипт додавати тільки на окремих сторінках (як я зараз роблю зі скриптом карти.) Також, само собою, вбудована мініфікація та хешування. А ще Hugo вміє підготувати зображення.
Щоб зробити SPA з внутрішніми шляхами, його базовий шаблон треба помістити в layouts/404.html
; тоді всі сторінки, що не є статичними, будуть оброблені в SPA. Це працює як в локальному режимі, так і в продакшні, якщо правильно налаштувати (а саме, вказати вірну сторінку 404.)
26.05.2023
Capybara - найкращий інструмент для інтеграційних тестів
Люблю фреймворк інтеграційних тестів Capybara. Дванадцять років тому я робив презентацію про те, яка вона класна. Та, за цей час, вона стала тільки краще. Хоча що дійсно стало краще, то можливості емуляції браузера, через що “само-багів” в тестах значно поменшало.
В чому перевага Капібари? Я бачу дві.
-
Синтаксис Ruby найкраще підходить для створення DSL, а рубішний підхід до асинхронності не ускладнює код конструкціями на кшталт
then
,await
, оголошеннями функцій, типами та так далі. При цьому тести залишаються справжнім кодом, та його можна рефакторити, міксувати з кодом, що не стосується інтеграційних тестів, та інше. -
Останній пункт дуже важливий. Бо в тестах Capybara ми маємо повний доступ до контексту додатка. Наприклад, ми можемо наповнити базу тестовими даними, використовуючи ті самі фабрики, що і юніт-тести. (До речі, фабрики на Ruby теж незрівнянні, але зараз не про них.) Але ми також можемо безпосередньо впливати на код додатка — наприклад, замокати метод, щоб відтворити необхідну ситуацію.
Звісно, що найкраще Capybara працює з додатком на Ruby on Rails. (Проте це не є обовʼязковим, капібарою можна тестувати будь-що.) Можна сказати, що це обмежує її потенціал, але обмеження — це те, що очікує всіх, хто не обрав Rails. :)
Чого справді не вистачає в Capybara - це такого журналу виконання, який є в Cypress або Playwright. Дивно що хтось його ще не зробив.