Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
26.07.2023
На Cloud Ops не заощадиш
Коментар до вчорашнього посту нагадав мені про мою профдеформацію — а саме, останні роки я працюю в оточенні, де AWS є даним. Тому коли я порівнював Lambda, то в першу чергу порівнював з ECS.
Проте якщо починати з вільного вибору, то брати AWS для розгортування вебдодатка це безглуздя. Заощадження у коштах вийдуть неадекватними витратами часу та зусиль. Навіть якщо мати досвід AWS - все одно має сенс зупинитись на простішому хостингу - Fly.io, Heroku і так далі. “Сантехнікою” варто займатись, тільки коли є потреба.
На жаль, я не знаю середньої альтернативи між контейнеризованим хостингом та повним хмарним. Допомагає принаймні вести облік в Terraform та за можливістю рефакторити. Насправді є багато різних модулів для Terraform - для створення того ж сервісу ECS, наприклад. Але за моїм досвідом між сервісами залишається достатньо різниці, щоб такий підхід був непрактичним — абстракція “тече” (як сантехніка). Можливо, також Kubernetes в чомусь допомагає, але мені він не зайшов через власну складність.
До речі, ми як раз шукаємо DevOps інженера — чесно, не планував рекламувати вакансію, але був би дуже радий мати поруч досвідченого спеціаліста.
25.07.2023
Cloud ops - сантехніка інтернету
Коли я порівнював запуск Rails на AWS Lambda та AWS ECS, я нічого не сказав про складність розгортування. Хоча, було бажання написати, що ECS це просто, а Lambda потребує більше налаштувань. Проте от в чому справа — мені здавалося, що різниця обʼєктивна, а субʼєктивна, бо з ECS в мене набагато більше досвіду.
Сьогодні довелося створювати новий сервіс на ECS, та ця підозра підтвердилась. Так, сам сервіс являє собою скупчення контейнерів Docker, це ніби зрозуміло, та нагадує Heroku або мій улюблений Fly.io. Але скільки всього потрібно створити навколо сервісу, щоб він запрацював! Роль IAM - зрозуміло. Група безпеки — для цього сервісу та ще не забути оновити всі його залежності. Балансувальник навантаження — без нього сервіси ECS не працюють. Ресурси журналу (та дозволи на них.) А далі, ще налаштувати CodeDeploy. Підготувати параметри Parameter Store. І далі, і далі.
Та, якщо в чомусь припустити помилку, то, найчастіше, не отримаєш навіть базового повідомлення. Типова ситуація — коли просто “не працює”. Може, в групі безпеки бракує потрібних дозволів. Може, сервіс в підмережі, де немає зовнішнього підключення. З досвідом стає простіше написати правильно з першого разу та знати, куди дивитися, якщо не працює.
Але природа задачі залишається такою самою. Та понад усе вона мені нагадує роботу з сантехнікою. Купа складних частин, жодна з яких сама по собі нічого не робить. Кожна погано влазить та стикується. Та як остаточний результат, отримуємо не якийсь шедевр мистецтва, а просто дещо, що робить свою роботу.
24.07.2023
Remark та Unified
Дізнався про екосистему обробки синтаксичних дерев, від якої я абсолютно в захваті. Це екосистема Unified для TypeScript (та JavaScript.) Я натрапив на неї при пошуку бібліотеки для розбору Markdown - та виявив, що бібліотека Remark побудована саме на основі Unified.
З чого взагалі складається Unified? В першу чергу, це стандартна обʼєктна модель синтаксичного дерева (ну, може, не стільки обʼєктна, стільки структурна — бо не використовує класів.) Навколо цієї моделі побудоване скупчення функцій, що її обробляють. Як базовий приклад — пакет unist-util-visit. Цією функцією можна знайти в дереві потрібні вузли — наприклад, задачі в документі Markdown.
На такій базовій системі побудовані інструменти для різних мов та задач. Кожна мова має свою специфікацію дерева — наприклад, mdast для Markdown. Але, як я був здивований дізнатись, Prettier теж використовує Unified - для форматування JavaScript!
Втім, мені нічого, окрім Markdown, не потрібно. Навіть в такому випадку наявність стандартизованого дерева спрощує створення доповнень, яких для Remark є чимало.
23.07.2023
Створення доповнення для Obsidian
…Виявилося простою та приємною справою. Підготовка оточення потрібна мінімальна — доповнення це звичайний проєкт на TypeScript. Документація чудова. Декларації типів теж. Так званий зразок доповнення насправді містить всі потрібні налаштування - yarn, tsc, esbuild - та також приклади по більшості можливостей розширення. Оновлення наживу можливе.
Робота з інтерфейсом відбувається через HTML/CSS. Тобто ніякої інтерфейсної бібліотеки вивчати не треба. Навпаки, можна використати React або іншу бібліотеку.
Де цей інтерфейс буде? Авжеж, можна створити модальне вікно. Але, окрім того, можна додавати власний зміст прямо в документи. Для цього є два незручних способи та один обмежений, але дуже простий.
Оскільки в Obsidian є два режими документа — редагування та читання — то способи теж окремі. Для читання простіше — це, по суті, сторінка HTML, її можна змінювати як хочеш. Для редагування складніше — треба писати компонент редактора, хоча схоже що за інструкцією і це можливо зробити.
Але простіше за все, то є точка розширення registerMarkdownCodeBlockProcessor(). Вона дозволяє замінити блок коду (три лапки) з визначеною “мовою” на результат виклику функції, тобто довільний HTML. Це працює в обох режимах. Так, фактично, можна вкраплювати в документи справжні додатки — що я й спробую зробити.
22.07.2023
Markdown як структурна мова
…Роздивився вихідний код Obsidian Tasks. Зрозумів, чому вони не підтримують вкладені задачі. Виявляється, що вони обрали найпростіший підхід до розбору документів — рядки з задачами шукають регулярними виразами. Ніякого звʼязку між рядками немає, тому немає й вкладеності. За словами самих розробників, без серйозного рефакторингу її не зробити.
При цьому, документ Markdown наділений цілком реальною структурою. Попри відсутність розмітки, а точніше, навіть завдяки їй — бо на відміну від HTML, Markdown дійсно є мовою “what you see is what you get”.
Колись я вже робив менеджер задач на основі Markdown, тільки на React Native. Ідея була в тому, що редагувати текст на телефоні незручно, тому можна перетворити текст на список (в сенсі UI) - наприклад, елементом списку могла бути задача, яку можна було легко закрити, або ж відредагувати текст, пересунути на інше місце, і так далі. Всі ці зміни перетворювались назад у Markdown.
Як це працювало: я брав дерево AST, створене у markdown-it. Далі перетворював це дерево на семантичне, тобто таке, де елементи мають сенс в моєму контексті: параграфи, списки, задачі, і таке інше. Потім це дерево розрізав на елементи списку. Елементи списку вирушали в React Native.
При змінах зі списку наново будується весь документ. Відтворити оригінальний документ теж простіше, ніж з HTML, бо у розмітці Markdown менше варіації. Наприклад, тег HTML може містити довільні пробіли, а у Markdown таких нюансів майже немає — хіба що список можна створити з *
або -
та інші дрібниці.
21.07.2023
Доповнення Obsidian Tasks
Виявилось, що в Obsidian вже є чарівне доповнення Tasks, яке вирішує 90% моїх потреб. (Більш за те, решту можна доробити, про що пізніше.)
Сам по собі синтаксис задач - [ ]
в Obsidian є стандартно. Доповнення Tasks робить дві речі. Додає розширений синтаксис для оздоблення задачі метаданими — це різні дати та пріоритет. Для цього синтаксису є навіть окремий модальний редактор, але ніхто не заважає написати його руками (з автодоповненням!), бо метадані зберігаються у звичайному тексті: - [ ] надіслати звіт 🔺 📅 2023-08-10
. Мою ідею про автоматичне занотовування дати початку Tasks вже реалізував.
Та друга функція Tasks - це можливість побудувати список задач з результатів пошуку. Пошук можна робити як по всій колекції, так і по одному документу, а також фільтрувати по статусах, датах, і так далі. Це відразу вирішило мою потребу бачити список поточних задач: path includes next projects / status.type is in_progress / group by filename
.
Чого Tasks не вміє категорично, це розрізняти вкладені задачі. Тобто будь-який елемент, розмічений як задача, має рівний порядок. Хотілося б все ж таки щоб в переліку задач не зʼявлялися ті, що мають вкладені. Або — ті, що заблоковані попередніми задачами. Можливо, як раз тут я можу додати функціоналу власним плагіном, якщо буду назначати відповідні статуси. (До речі, окрім класичних “зроблено”/“не зроблено” Tasks дозволяє створювати довільні статуси та багато з них вже мають підтримку тем.)
Стає ясною концептуальна різниця між Obsidian та іншими системами ведення нотаток. Зазвичай системи діляться на “чисто текстові” та “розширені та розумні” - вибір між простотою та користю. Натомість підхід Obsidian - витягати з простого тексту більше структурованої інформації. Це залишає можливість обробляти цей текст потужними засобами — ручними та автоматичними. Наприклад, щоб перетворити свій список задач в правильні анотації Tasks, я просто скопіював його в VSCode та швидко це зробив мультикурсорами.
20.07.2023
Доповнення для Obsidian - як воно все працює
Задався ідеєю зробити доповнення для Obsidian, яке б покращувало роботу з задачами. В цілому, хочеться такий інтерфейс: команди можна писати безпосередньо в файлі, а доповнення при збереженні файлу буде читати команди та заміняти на результати. Поки, щоправда, це дуже абстрактно, але для початку хотілося б щоб до кожної нової задачі приписувалася дата створення.
Зробив маленьке дослідження того, яка модель редактора в Obisidan та як працювало б таке доповнення. Для цього є документація. Також як приклад, є плагін з Prettier та лінтер.
Цікаво, що вони по-різному підходять до заміни тексту. Обидва плагіни спочатку генерують змінений текст. Але Prettier просто заміняє весь зміст редактору, а Linter будує стислий пакет змін бібліотекою diff-match-patch, а потім вже застосовує кожну зміну окремо. Треба ще перевірити, чому, але можливо, що це краще зберігає історію змін.
Також треба знати, що редактор в Obsidian насправді не один, а багато, бо область редагування можна ділити. Там ціла деревоподібна структура, листям якої є конкретні редактори. Але при цьому можна знайти поточний… якщо він є та містить Markdown.
Ще цікава можливість доповнень Obsidian - це додавати в документ власні анотації та й взагалі будь-яку розмітку. Наприклад, задачі можна розфарбовувати за віком.
До речі, для роботи з Markdown як зі структурним документом можу порадити бібліотеку markdown-it. Я раніше використав її, щоб створити структурний редактор Markdown для React Native.
19.07.2023
Приховані проблеми з 64-бітними ідентифікаторами
Коли я рекомендував використати 64-бітні числа як ідентифікатори, я упустив важливий нюанс, а саме те, що не всі мови та середовища їх підтримують. Навіть в наш час, коли майже кожний компʼютер має 64-бітний процесор — від смартфона до сервера.
Ви маєте ретельно перевірити, що ID не проходять в числовій формі там, де мова не підтримує 64-бітні цілі числа. Я впевнений, що такі місця у вас є, принаймні тому, що JavaScript їх не підтримує. Але перевіряти треба все, бо як я писав, наші проблеми почалися не з JavaScript, а з закритої цільової мови. Щоб не створювати додаткових ризиків, краще не тільки перетворювати ці ID на рядки, а й робити їх очевидно не чисельного вигляду — хоча б додати префікс.
При чому, в типовій ситуації надто велике значення ID не призведе до винятку. ID залишиться числом — цілим числом — він тільки округлиться до іншого значення. Абсолютно непомітно.
Поламані ID - це не так погано на фронтенді, бо там вони призведуть хіба що до тимчасової втрати доступу користувача до своїх ресурсів. Але всередині системи це вкрай ризикова помилка, бо ID зазвичай нема чим перевірити, та якщо вони потрапляють в базу, відновити дійсну картину буде складно.
Та ще загальна рекомендація — якщо аналітики знайшли вам баг в даних за допомогою деякого SQL запиту, треба перетворити цей запит на постійний моніторинг. Добре мати набір SQL-інваріантів, які помітять розбіжності до того, як це зроблять аналітики (або гірше, користувачі.)
18.07.2023
Сучасна сцена модів на Doom та GZdoom
Якщо хочеться класної ретро-стрілялки (жанр, також відомий як boomer shooter), варто знати, що моди для Doom роблять й по цей час та вони під час вражають якістю, яку не очікуєш від безплатного проєкту. Треба зазначити, що сучасні рушії для Doom - з яких перший GZDoom - вийшли далеко за межі оригінального рушія — але все ж таки загальне відчуття залишається.
Що відрізняє покоління Doom від інших ігор? Рівні Doom були побудовані з кімнат, кожна з яких є призмою — в основі її лежить довільний багатокутник, а підлога та стеля мають фіксовану висоту. У 1993 році це уможливлювало алгоритм, що сканував екран за стовпчиками та визначав, на яку кімнату та стінку припадає кожний стовпчик. Оскільки вся підлога є пласкою, а стіни — вертикальними, то це потребувало тільки обчислень в площині, бо для тривимірних обчислень потрібна матрична математика, а тогочасні компʼютери були не настільки розумні. (Чи знаєте ви, що Doom вийшов навіть на приставку Super Nintendo?)
А у 2023 цей архаїчний формат рівнів створює цікаве творче обмеження. Та й до того ж моделювати рівні у 2D простіше. Окрім звичайних кімнат, Doom дозволяє створювати всілякі обʼєкти за допомогою відʼємного простору. Наприклад, “кімнатою” може стати ліжко. Або подушка на ліжку. Або навіть телефон на подушці. Головне, щоб це був багатокутник, та над ним нічого, крім стелі, не було. Тож сучасні рівні для Doom це особливий жанр 3D мистецтва.
Для ознайомлення можу запропонувати два різних проєкти. Мод Ashes 2063 являє собою цілий епізод захопливих пригод в постапокаліптичному місті. Деталізація рівнів неймовірна, та особливо цікаво, що вони відтворюють справжні локації — вулицю, заправку, ресторан. Мод MyHouse - це експеримент, який робить з рушія неможливе… сказати більше буде спойлером.
До речі, GZDoom чудово почуває себе на macOS. Втім, багато модів розповсюджуються як “standalone executable” - звісно, тільки для Windows. Тут можна не зволікати, знайти в архіві мода найбільший файл .pk3
та запускати його в власному GZDoom.
17.07.2023
Контексти в Golang - простота
Минулого раз я писав, що контексти в Go існують для зупинки процесу ззовні. С тих пір я намагався вписати їх у свій код, та набув додаткового розуміння.
Розуміння таке: так само як і винятки, контексти не потребують багато уваги, насправді. Не потрібно їх постійно створювати або перевіряти.
Новий контекст потрібний тільки там, де може виникнути потреба скасувати операцію окремо від решти програми. Наприклад, на початку обробки запита HTTP можна створити контекст з обмеженням по часу. Якщо час сплинув, то запит пора зупиняти, а решту сервера — звісно, ні. Окремо цікаво, що контекст запита може успадкувати контекст сервера, та тоді запит буде зупинений не тільки за власною тривалістю, але й при зупинці сервера — якщо така поведінка потрібна.
В інших випадках контекст просто проходить по коду зверху вниз. Що, може, не так й зручно, але тут ще нюанс: контексти потрібні здебільшого там, де відбувається зовнішня комунікація — оскільки саме вона створює неочікувані затримки. В простій бізнес-логіці контексти не потрібні. Хіба що можу уявити, якщо є код, що робить довге обчислення — наприклад, відеокодек — то в ньому має сенс перевіряти стан контексту. (Що, до речі, робиться через конструкцію if c.Err() != nil { return c.Err() }
.)
Нарешті, чи потрібно перевіряти, чи повернула функц
ія помилку context.Canceled
? Насправді ні — таку помилку можна передавати нагору та само, як і будь-яку іншу. Це виконує задачу контексту: код буде зупинений. Тільки на найвищому рівні — там, де контекст був створений — треба перевірити, що саме трапилось — скасування контексту або інша помилка.
]