Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
19.06.2023
Чим парсити XML в JavaScript?
Головна логіка в OPML Doctor складається з обробки документів XML - спочатку OPML, а потім RSS. Думаю, може, цікаво, чим воно робиться.
Версія з 2015 року робила розбір окремо на ClojureScript (OPML) та на Clojure (RSS). На той час для Clojure була бібліотека data.xml, яка разом з data.zip надавала функціональну абстракцію для документа XML. Це було прикольно, але на той час ці бібліотеки не підтримували ClojureScript, тому розбір OPML на фронтенді я робив за допомогою… jQuery. Бо так, jQuery це вміє. А ще є бібліотека jayq для ClojureScript, яка спрощує роботу з jQuery.
(Взагалі багато бібліотек для Clojure/Script загортають якісь бібліотеки на Java/Script в функціональні абстракції. Іноді вдало, іноді зовсім ні. Наприклад, бібліотека clj-time мені зовсім не подобається, та ще й документації нормальної немає. Тому на цей раз обрав використати luxon - це бібліотека на JavaScript з функціональним стилем. Викликати функції та класи JS з CLJS цілком можливо та тільки трохи незручно.)
У 2023 Clojure в мене більше немає, залишився тільки ClojureScript, а бібліотека data.xml
, хоч його й підтримує, виглядає напівживою. Тому я знайшов рішення на JavaScript, а саме — стандартний та вбудований в браузер DOMParser, який підтримує як HTML, так й XML документи. Він будує стандартне дерево документа. Якщо давно не бачили, то зараз з ним зручно працювати через наявність, наприклад, метода querySelector.
Якщо парсити на бекенді (в Node.js), то вбудованої реалізації DOMParser
там немає. Її можна знайти в пакеті jsdom. Є ще пакет xmldom
, але він не реалізує функцію querySelector
, тож шукати елементи доведеться вручну. А jsdom
вміє практично все, що браузер.
18.06.2023
OPML Doctor - глибока перевірка RSS стрічок
Доробив за вихідні додаток OPML Doctor та тепер публікую тут бета-версію. Поки є тільки версія для macOS. Причина в тому, що Tauri не вміє робити кроскомпіляцію, та для збірки під Windows та Linux необхідна машина або СІ.
Додаток буде корисним всім, хто читає або випускає RSS. Зараз він дозволяє перевірити колекцію стрічок у форматі OPML на проблеми з адресою, наповненням та інші. От, з моїх 200 стрічок майже половина має проблеми; в основному це старі закинуті блоги, але є й сайти, що переїхали, наприклад.
Tauri порадував своїми можливостями. Наприклад, обробку перетягнутого на додаток файлу зробити дуже легко. Також легко зробити перемикання зі світлої на темну тему. До речі, для оформлення я використав Bootstrap, в якому тільки-но зʼявилася підтримка тем. Тож від мене потрібно було тільки встановлювати атрибут html.data-bs-theme
.
Збірка додатка в Tauri відбувається майже без налаштувань. Трошки повозився з нотаризацією. Річ у тім, що сучасні додатки в macOS мало підписати сертифікатом, їх також потрібно завірити “у хмарі”. Це надає додатковий захист від крадіжки сертифіката. Коли розберешся, що як налаштувати, то все працює автоматично. Зокрема потрібно в XCode згенерувати сертифікат “Developer ID Application”. (Підписка розробника в мене вже була.) Та ще довелося явно сказати Tauri, що я хочу універсальну збірку, а не тільки для Apple Silicon.
Сподіваюся, що додаток запрацює й у вас. Значок теж встиг сьогодні намалювати, до речі. А сайт з поясненнями не встиг. Отак з продуктами завжди: думаєш, що залишилось тільки код дописати, і справа зроблена. А насправді навпаки, з цього все тільки починається.
17.06.2023
Загортання вебдодатка у звичайний за допомогою Tauri
Сьогодні продовжив гратись зі своїм OPML Doctor. Як можна здогадатись, головний функціонал такого додатка обертається навколо завантаження RSS-стрічок, десятками або навіть сотнями. Минула версія додатка робила це з бекенду. Але мені трохи боязно запускати такий додаток в публічний доступ, бо треба буде піклуватися про захист від визискування.
Завантажувати стрічки безпосередньо на фронтенді неможливо через захист CORS. Тому придумав цікаву альтернативу: загорнути вебдодаток в повноцінний додаток (на кшталт Electron). Тоді завантаження стрічок відбуватиметься з компʼютера користувача. Це прибирає ризики визискування; практично програма не робитиме нічого такого, чого не може зробити сам користувач з браузером.
Electron - найбільш відомий інструмент для створення додатків на вебтехнологіях. Але в нього погана репутація. Оскільки кожний додаток на Electon містить в собі Node.js та браузер Chromium, то вони й великі за розміром, й жадібні до оперативної памʼяті.
Тому є низка альтернатив, з яких мені подобається Wails та Tauri. В обох “бекенд” пишеться ефективною мовою - Wails на Go, Tauri на Rust. Обрав поки Tauri, бо вона, схоже, більш зріла. Також мені подобається, що Tauri використовує системний браузер, що додатково економить ресурси.
Перенести SPA додаток, який вже існує, в Tauri виявилось дуже просто (для цього в них є інструкція.) Навіть при тому, що фронтенд в мене на ClojureScript! Фактично все, що потрібно — це вказати Tauri, якою командою запускати сервер розробки, та на якому порті він розташований. Далі розробка ведеться так само як в браузері.
Але, спитаєте ви, як мій бекенд на Clojure перенести на Rust? Переписувати не хочеться. Тому поміркував та визначив, що все, що мені потрібно від Rust, це виконання запитів HTTP. На мою радість, така функціональність вже доступна; Tauri надає вебдодатку декілька API, в тому числі http для HTTP-запитів через Rust.
Всю іншу логіку перевірки стрічок я можу перенести в фронтенд, бо Clojure та ClojureScript - одна й та сама мова. Тільки з залежностями не все так просто.
16.06.2023
Чому у Twitter рядкові ID
Хоча ID твітів чисельні, але у їх API вони повертаються як рядки. Чому? На перетині 64-бітних ID та чисел з рухомою комою знаходиться, мабуть, найбільший Wat Джаваскрипта. А саме, те, що великі значення цілих чисел будуть округлені без жодних повідомлень та попереджень.
Причина в тому, що в Javascript є тільки один тип “число”, та це число зберігається як тип double
. Оскільки тип double
теж займає 64 біти, то цілочисельне значення в 64 біти в нього технічно неможливо вмістити.
З цим майже неможливо стикнутись при обчисленнях, бо ніде не потрібні такі великі числа з такою великою точністю. Але схеми генерації унікальних ID, про які я писав, за визначенням мають заповнювати всі 64 біти. Тому з ID округлення практично гарантоване, а наслідки округлених ID будуть від неприємних до катастрофічних.
Насправді така проблема є не тільки в JavaScript, але й в будь-якому середовищі, де числа представлені у вигляді з рухомою комою. Власне, ми знайшли цей нюанс в пропрієтарній мові однієї з наших залежностей. Причому там ще дивніше було, бо числа нібито працювали правильно, доки їх не друкуєш або не конвертуєш в JSON. доки не розумію, як це взагалі може бути. Хіба що число насправді зберігається як int64
.
Не всі мови себе так поводять; наприклад, Ruby в залежності від значення вживатиме цілі числа або рухому кому. А якщо число не влазить в int64
,ʼ то перейде до довгої арифметики, тобто до представлення числа у вигляді необмеженого рядка з байтів. Довга арифметика вже 5 років є й в JavaScript - це тип BigInt, але його треба залучити явно, та сподіватись, що всі це зроблять, не можна. Ну, а з системними мовами проблем точно не буде.
Щоб такого з вами не трапилось, 64-бітні ID треба передавати у вигляді рядка. Якщо почитати документацію API Twitter, то вони саме так і пояснюють. Я б пішов ще далі та замість числа в десятковій системі зробив більш компактний формат, ну принаймні hex
. Це також сприятиме використанню ID у вигляді рядка, без ризикованої конвертації в число. (Але там, де швидкодія має значення, треба перетворювати назад в int64
, безумовно.)
15.06.2023
Сучасний стан розробки на ClojureScript
На Реддіті побачив запитання про засоби перевірки OPML файлів для RSS. Ба, думаю, так я ж такий писав. От тільки давно. Та є два нюанси. По-перше, він був розміщений на моєму виділеному сервері, який я давно припинив підтримувати (бо складно). По-друге, написаний він був на Clojure/ClojureScript, бо в ту епоху (у 2015) мені було цікаво кудись його застосувати.
Куди розмістити зараз — в мене вибір один - Firebase. Бо дешево та просто. Але Firebase на бекенді підтримує тільки JavaScript. Не проблема, в теорії, бо бекенд можна портанути з Clojure на ClojureScript. Знайшов гарну інструкцію, як запустити ClojureScript на Firebase Functions, та це дійсно швидко спрацювало.
До переписування бекенду я поки не дійшов, бо ще треба оновити фронтенд. Майже все, що я використовував 8 років тому, померло. Зʼявилися нові інструменти. Замість Figwheel для збірки проєктів тепер є Shadow CLJS. Дуже приємно працює, з простими налаштуваннями робить дві збірки — бекенд та фронтенд, сам завантажує залежності (зі світу Clojure; ті, що на Javascript, завантажує yarn, як звичайно), в браузері наживо оновлює. По цьому питань взагалі нема.
Фреймворк для фронтенду Om теж більше не підтримується. Вони самі рекомендують переїхати на Fulcro. Це дуже складна система зі власними абстракціями та з привʼязкою до свого фреймворку на бекенді. В мене найбільше питань по тому, що для вибірки даних зі стану використовується EQL. Для простого додатка це зайві ускладнення.
Зате re-frame все ще живий. Re-frame це як React+Redux+Reselect в красивій функціональній абстракції. Його я б і рекомендував брати, якщо захочеш спробувати Clojure. Це, напевно, найкрутіше що є з практичних міркувань.
Для форматування коду мені дуже подобався parinfer, але й він більше не підтримується. Для розробки в VSCode є доповнення Calva з власним форматувальником — до нього треба звикнути, але задачу балансування дужок, в цілому, він виконує.
14.06.2023
WaitGroup в Go
Для закінчення питання, поставленого вчора, не вистачає ще однієї частини. Ну, добре, зробили ми архітектуру на контекстах, контекст сплинув, горутіни отримали сигнал зупинки. Що далі? Необхідно дочекатись, доки всі горутіни завершать свою роботу. Особливо якщо зупиняємо всю програму — якщо не чекати, то немає сенсу навіть ввічливо попереджати горутіни про зупинку.
Можна знову придумати свою систему з каналами, і так далі. А можна взяти стандартний тип sync.WaitGroup, який створений саме для того, щоб чекати зупинки декількох горутін. Від банального лічильника його відрізняє наявність методу Wait()
, який буде чекати, поки всі горутіни не викликають метод Done()
.
Тобто алгоритм таких: на початку підраховуємо кількість горутін, які запускаємо, та заряджаємо викликом wg.Add()
. Далі, кожна горутіна по завершенню викликає wg.Done()
. Нарешті, головна горутіна спочатку сигналізує про завершення контексту, а потім викликає wg.Wait()
.
Чи не надто витратно створювати WaitGroup для довготривалих процесів? Ні, цей тип містить лише 2 цілих числа, та не має “живого” коду. Взагалі це просто лічильник + семафор, нічого магічного. Це добре, бо нам потрібно передати WaitGroup в кожну горутіну, яку ми запускаємо.
Як чекати з обмеженням по часу? Це типова задача — наприклад, ми знаємо, що через 60 секунд після отримання сигналу наш процес буде вбитий оркестрантом. Відповідь така: обмеження треба обробляти ще всередині горутін. Що це значить на практиці — залежить від конкретного випадку. Але ми точно повинні не кидати горутіни у невизначеному стані, як воно буде, якщо тайм-аут спрацює назовні.
Такий підхід набагато простіше, ніж спостерігати за спустошенням каналів з чергами або вигадувати ще якийсь механізм очікування.
13.06.2023
Контексти в Golang
Як зупинити код у відповідь на зовнішній фактор? Один з механізмів відомий всім — то команда kill
, якою можна зупинити цілий процес. Але, як зробити це всередині програми? Що робити, коли блок коду займає більше часу, ніж ми можемо чекати? Це реальні питання, які часто не мають гарної відповіді. Ось в Ruby, наприклад, є модуль Timeout
, який наполегливо рекомендують не використовувати.
В Go питання зупинки процесів стає ще актуальнішим, оскільки створення одночасних процедур — типова повсякденна робота. Як зупинити всі ті горутіни, коли вони більше не потрібні? Можна накрутити (та я й крутив) додаткові канали для сигналізації. Зупиняємо сервіс — сигналізуємо горутінам.
…Паралельно з цим доводиться постійно передавати в різні бібліотечні функції якийсь “контекст”. Нащо він потрібен той контекст — не знаю. Добре що завжди можна створити новий на місці, наприклад функцією context.Background()
.
Та тільки нещодавно зрозумів, що контексти в Go і є той самий механізм зупинки коду у відповідь на зовнішній фактор. У контексту є метод ctx.Done()
, який повертає канал, що буде закритий, якщо пора зупиняти код. Для перевірки використовується команда select
. Це й треба використати замість самописних механізмів. Більше в документації.
Щоб створити власний корисний контекст, є відповідно функції context.WithCancel
та context.WithTimeout
. Особливо зручно те, що такі функції загортають контекст батьківського рівня, та успадковують закриття ще й від нього.
Для сигналів, про які я вже згадав, є signal.NotifyContext. Сервіс, який себе гарно поводить, має спиратись саме на цей контекст, щоб контрольовано зупинити всі свої компоненти.
Тепер ніяких більше context.Background()
.
12.06.2023
Сумний стан української локалізації
Коли я сьогодні дізнався, що Cyberpunk 2077 невдовзі отримає українські субтитри, зрадів. А потім, захотів зрозуміти, який взагалі на 2023 рік стан локалізації. Вирішив зробити маленьке дослідження самотужки.
Звідки взяв дані. Відразу обрав як джерело Steam, щоб якось обмежити вибірку. Теоретично, каталог Steam відкритий для всіх, та є сервіси на кшталт SteamDB, які його викачують. Але саму базу для аналізу довелося ще пошукати, бо стягнути її не дають, а потрібного мені функціоналу пошуку не вистачало. Нарешті знайшов на Гітхабі версію на грудень 2022 року. Далі написав скрипт на Ruby, який генерує CSV з результатами. Далі — робота редактора таблиць.
Результати, на перший погляд, прикрі. Тільки 1746 ігор загалом перекладені українською. У відсотках це лише 8%. Менше, ніж російською або польською. Але більше ніж, наприклад, тайською — при тому, що в Таїланді живе 70 мільйонів людей.
(Цю цифру складніше отримати, ніж можна уявити. З ігор треба викреслити всі, що не мають локалізації зовсім — зазвичай це ігри англійською або китайською. До того, як це зробив, спостерігався різкий спад локалізації у 2014 році. Це пояснюється запуском програми Greenlight, за якою випустити гру в Steam стало набагато простіше.)
Якщо ж дивитися на значення в динаміці, то картина краща. З року в рік кількість ігор українською зростає. З ігор, що вийшли у 2022 році, перекладено було 10%. Взагалі бачу дві групи мов: є “класика” локалізації — французька, німецька, італійська, іспанська — ці мови втрачають долю локалізації. А менш представлені мови — як корейська, норвезька або українська — поступово її збільшують. Все це можу пояснити тенденцією до мовної інклюзивності.
А з українською озвучкою все зовсім погано. Хоч в базі значаться близько 500 ігор, насправді більшість з них практично не має озвучки як такої. Реально можна порекомендувати хіба що серію Metro 2033 та всі частини Сталкера. Та, як не дивно, першу частину Baldur… ее, Брами Балдура. Оце справжня, міцна RPG. Та вся озвучка перекладена. Я спочатку й не повірив, довелося встановити та перевірити. Звісно, голосу в Брамі мало, зате тексту як в добрій книзі. Одним словом, рекомендую.
11.06.2023
Які RSS я читаю?
Поступив запит на рекомендації по RSS. Раніше я писав про те, як я користуюсь RSS взагалі. Складно давати рекомендації по конкретних стрічках, бо фактично через RSS можна споживати майже все, що вам цікаво в інтернеті, але все ж таки висвітлю дещо з того, на що я підписаний.
-
Моя власна стрічка з Hacker News. Тут завжди багато цікавого. Зокрема, на HN можна знайти багато маленьких авторських блогів, на які теж варто підписуватись.
-
З великих IT-видань - The Verge, MacRumors. Ще Lifehacker та Wirecutter. Ще є гарний Ruby Weekly. Про ігри давно читаю Rock, Paper, Shotgun.
-
Купка стрічок додатків та сервісів, якими я користуюсь. Якщо в когось є живий блог, додаю. Все так просто. Наприклад, цікаві блоги у GitHub та Cloudflare.
-
Окрема категорія - RSS стрічки з GitHub Releases; так можна відстежити оновлення, які треба робити вручну. От, наприклад, Hugo: https://github.com/gohugoio/hugo/releases.atom.
-
Декілька вебкоміксів: Saturday Morning Breakfast Cereal, Penny Arcade, Oglaf. Ну XKCD ще.
-
Купа персональних блогів. Нещодавно знайшов блог творця Agile Programming. Або є Павло Авдокушин — чудовий оглядач української інфраструктури. Але загалом персональні блоги нечасто пишуть та їх більше сотні, тому не можу тут зробити чесний огляд.
-
Блог Railsware. :) В нас зараз серйозна команда блогерів, яка висвітлює внутрішні підходи, тож виходять цікаві статті, особливо з галузі керування продуктами.
10.06.2023
Рімейк System Shock
В мене з System Shock смішна історія. Перший раз я про цю гру почув десь у 2001 році, та витратив може місяць, щоб викачати її через модем зі швидкістю 56.6К. На жаль, мене чекало тільки розчарування, бо користуватися архаїчним (вже на початок сторіччя) інтерфейсом не зміг. Після години чи двох спроб гра була закинута.
За минулі 22 роки я встиг пограти у всі нащадки System Shock - від System Shock 2 та Deus Ex до Dishonored та Prey. Тому, коли у 2016 році відкрився Kickstarter для створення рімейку, то я підтримав його без роздумів. На розробку пішло 7 років, але нарешті рімейк вийшов.
Та вийшов пречудовим! По суті це абсолютно нова гра, яка близько відтворює рівні та сюжет оригіналу. Вочевидь багато чого змінено; насамперед темп гри змінився абсолютно, та, я так розумію, довелося переосмислити озброєння та противників. Тепер це повноцінна бродилко-стрілялка з інвентарем, за формулою дуже нагадує Prey.
Основний геймплей залишився — поступово та методично досліджувати просякнуті атмосферою та подібні до лабіринтів рівні, поки не відкриєш всі двері, не знешкодиш всі камери, та не збереш все що варте збору. Ніяких елементів RPG тут немає, успіх визначається тим, скільки спорядження встигнеш зібрати. Цікаво, що можна обрати, більше чи менше інструкцій про сюжет ти будеш отримувати. На складності за замовчуванням вони високорівневі та необхідно уважно слухати аудіозаписи та роздивлятись оточення.
Чи рекомендую я System Shock Remake? На всі 100! Чи хороша це гра сама по собі? Так, чудова.
Тепер з ігор, в які я б дуже хотів пограти, але не можу через архаїчність, залишається Fallout 1 та 2. Може, і їх дочекаюсь. А ще все ж таки хочеться тепер спробувати оригінал System Shock, або принаймні Enhanced Edition.