Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
01.11.2023
Порівняння API вводу/виводу JavaScript та Go
Так вже вийшло, що останнім часом я спочатку працював з потоками в JavaScript (модуль stream), а потім — з модулем io в Golang. Обидва модулі реалізують ввід/вивід в загальному сенсі цього слова, але підхід зовсім різний та його цікаво порівняти.
Модуль stream побудований навколо асинхронних примітивів JavaScript - від подій до генераторів та async/await. Важливо, що ввід/вивід завжди вимагають блокування, тобто пасивного очікування. А в JavaScript немає можливості блокувати синхронний код. Тому при використанні модуля stream нам доведеться ускладнювати код — хоч сучасний підхід з async/await мені подобається та майже не напружує розуміння.
(Примітка: в JavaScript є декілька функцій, які здійснюють синхронний ввід/вивід, наприклад, fs.readFileSync. Вони реалізовані через вихід в C, тобто фактично “ламають” модель виконання. Ось детальне пояснення.)
Важлива функція абстракції вводу-виводу — це додавання логіки зі збереженням інтерфейсу. В JavaScript для цього є функція pipeline. Наприклад, нею можна додати до потоку запису в файл стискання. Або до читання з сокета — буферизацію.
Результат виконання pipeline - теж потік, тобто споживачам коду не потрібно знати, що в нього всередині. Також приємно, що pipeline приховує асинхронність всередині та залишає тільки зовнішню.
Про Go - завтра.
31.10.2023
Оптимізація Kafka для локального запуску
Звісно, зазвичай Kafka як і інші технології оптимізують під велике навантаження. Але, щоб інтеграційні тести швидко працювали локально або на CI, потрібні інші параметри оптимізації, та деколи варто витратити час, щоб їх знайти.
Як я до того дійшов? Вирішив зрозуміти, чому пакет тестів довго працює. На Go якщо запустити тести з ключем -cpuprofile, то буде записаний профіль виконання. Але профіль тільки показав, що більшість часу тести проводять в очікуванні даних, тобто в IO. В таких випадках профілювання по CPU дає зовсім некорисні результати, бо місце очікування не повʼязане з конкретним рядком програми, отже, незрозуміло, де саме вона гальмує.
Тоді є інший інструмент - -trace. Трасування запише деревоподібний журнал виконання програми, в якому в тому числі видні й очікування IO, проміж іншими видами блокування. Оскільки я вже знаю, що проблема в IO, то конвертую трасувальний журнал у профіль (go tool trace -pprof=net) та вивчаю його. Виявилось, що тести багато чекають на Кафку. Вдалося трохи це виправити налаштуваннями.
Чого не треба робити: зменшувати параметр fetch.max.wait. При мінімальних його значеннях цикл обробки буде безперестанно звертатися до Кафки. Це спричиняє зайві витрати CPU. Якщо ми очікуємо від Кафки хоч якісь дані, то fetch.max.wait спокійно можна ставити вище.
Натомість треба переконатись, що fetch.min.bytes встановлений в 1 (мінімальне значення, яке стоїть за замовчуванням.) Тоді ми дійсно отримуватимемо записи як тільки вони зʼявляться
Є ще linger.ms - це час буферизації продюсера. Його краще виставити більше ніж 0, що за замовчуванням, бо тоді відбудеться менше запитів до Кафки. Якщо середній тест триває 100 мс — то можна робити linger.ms=100.
Нарешті, за замовчуванням Кафка створює топіки на декілька розділів — а локально та з єдиним споживачем це тільки сповільнює отримання даних. Раджу перевірити, що топіки створюються лише з одним розділом.
Завтра ще є ідеї паралелізувати деякі кроки підготовки та попрацювати з викликами внутрішніх API.
30.10.2023
Спідран по саперу
Останнім часом сімʼя захоплюється “Сапером” в версії від Google (це одна їх з прихованих цяцьок). Мені відразу стає більш цікаво алгоритмізувати розвʼязок — цікава вправа. Щоб мати можливість робити інші експерименти, вирішив написати свою версію.
Сапер одна з найпростіших компʼютерних ігор, як для розуміння, так і у своїй моделі. Тому й добре підходить як вправа.
В стані маємо поле та міни. Міни розташовуються абсолютно випадково. Клітини навколо них оздоблюються лічильниками мін. Окремо відстежуємо стан відкриття клітин: закрито, відкрито, відмічено.
Гра закінчується тоді, коли відкриті всі клітини (а не за кількістю прапорців — бо ми маємо переконатися, що мін дійсно ніде більше немає.) Тож для стану гри обчислюємо: чи відкрили всі клітини (виграш), та чи відкрили міну (програш.) Поки гра не закінчилась, дозволяємо відкривати та відмічати клітини. От і весь ігролад.
На першу реалізацію в мене пішло година 20 хвилин, з яких останні 20 - на реалізацію двоетапного діалогу відкриття, бо плейтестінг показав, що на пристроях без миші він практично необхідний.
Це мій перший проєкт на SvelteKit, який тут, до речі, зовсім не потрібний, бо немає серверної складової. Чисто браузерний додаток можна було робити просто зі Svelte. Проте мені було ліньки налаштовувати проєкт з нуля, а SvelteKit все робить за мене. Далі проєкт можна безплатно розмістити на Vercel.
Ось: Сапер. А тут код. Далі можна робити над ним щось цікавеньке.
29.10.2023
Система коментарів для блогу
Коротенький огляд того, як в мене на блозі працюють коментарі. Сам по собі блог є статичним сайтом, тож коментарі є єдиним динамічним компонентом. До того ж реалізація такої системи була більше вправою для розваги, бо реально коментарів майже ніколи ніхто не залишає.
А раз вправа, то робив я її сам, замість того, щоб брати щось з систем, які вже існують. Зазначу, вони в більшості працюють не так, як я хотів: а саме, потребують як мінімум локального файлу бази даних, а зазвичай — просто сервер СУБД. (Є ще рішення-сервіси, наприклад, Disqus, але вони начиняють сторінку пачкою рекламних скриптів, та мені це дуже не подобається.)
Я хотів рішення з мінімальним використанням ресурсів. Тому обрав архітектуру яка витрачає гроші тільки на додавання коментарів, а не на відображення. Кеш коментарів до кожної статті зберігається як файл JSON на S3. Відображення здійснюється скриптом на Svelte. Спочатку я хотів зберігати на S3 готовий HTML, але JSON+додаток не тільки простіше в розробці, а й займають менше місця, тобто завантажуються швидше.
А при додаванні підключається додаток на Golang, який міг би бути розташований на безплатній AWS Lambda, але поки залишився на безплатному fly.io. Додаток працює з базою коментарів, яка сидить в безплатному DynamoDB. Від DynamoDB мені були потрібні два запити: пошук коментарів для сторінки та пошук конкретного коментаря за ідентифікатором. До речі, для ідентифікаторів обрав ksuid, бо вони є впорядкованими за часом.
В системі є неявна авторизація — а саме, після створення коментаря в сесії автора залишається ключ, який дозволить відредагувати коментар. По закінченню сесії можливість редагування втрачається. Для перевірки на спам використовую Akismet - з цим сервісом я вже багато років.
28.10.2023
Два тижні з робочим компʼютером — враження
Минуло два робочих тижні з того часу, як я почав працювати за окремим компʼютером. Ділюся враженням від цього незнайомого (та дивацького, на 2023 рік, хех) образу життя.
Щодо технічної реалізації. Я працюю за робочою станцією з монітором, клавіатурою та мишею. Після того, як я переніс периферію на розʼєми USB монітора, перемикання між робочою та неробочою машиною виконується одним кабелем. Проте насправді така зручність не дуже потрібна. Не тільки тому, що в ноутбук можна зазирнути й без монітора — а тому, що одна з моїх цілей це фізично розділити середовища — в тому числі й в часі. Швидке перемикання тут тільки шкодить.
Щодо режиму використання. Я намагаюсь емулювати фактично роботу в офісі, тобто перемикаю на робочу машину після сніданку та повертаюсь “додому” перед вечерею. Такий режим виявився моєю улюбленою стороною експерименту. Мені подобається, що є час, коли я фізично сфокусований на роботі. Більшість відволікань зникла сама собою. Так само подобається, що наприкінці дня можна її відкласти. Сам момент відключення робочої машини наповнює день сенсом та завершеністю, яку було важко досягти раніше.
Щодо програмної частини. Я вже писав, як налаштовував середовище. З витрат на програми — довелося сплатити додаткову підписку Setapp. Зʼявилася проблема обміну інформацією між робочим та домашнім компʼютером; для цього скористався месенджером, який вже є - Apple Messages - щоб листуватися зі своїм альтер его. Це трохи простіше, ніж класичний спосіб — електронна пошта. До речі, так, для робочої системи я створив новий обліковий запис Apple, щоб не мати доступ до документів та світлин.
В цілому, залишаюсь задоволеним цим підходом — навіть набагато більше, ніж уявляв. Всім раджу, за можливістю.
27.10.2023
Реалізація пейджинатора на SwiftUI
Взагалі для прокручування по сторінках не потрібна прокрутка.
Інтерфейс ділиться на три етапи. Перший та головний — перегляд сторінки. Оскільки ми вирішили зупинятись на кожній сторінці, тут все просто — це буквально просто компонент з поточною сторінкою.
Другий етап — жест прокручування. Для того у нас є DragGesture. Поки жест триває (тобто поки користувач утримує палець — а з ним і зміст), ми застосовуємо до сторінки зсув (offset()), який відповідає довжині жесту. Але, звісно, під час перетягування ми хочемо бачити й наступну сторінку — або попередню. Тому малюємо не одну поточну сторінку, а смужку з трьох сторінок. (При цьому вони, звісно, кадруються до розміру однієї.)
Нарешті, останній етап — реакція на жест, який закінчився. Жест може бути успішним або неуспішним. Успішність визначається, принаймні у мене, або коли протягнуто більше ніж 70% сторінки, або коли жест відбувся ривком, тобто з достатньою швидкістю. Після неуспішного жесту анімуємо зсув назад до нуля.
Після успішного жесту відбувається найцікавіше. Маємо перейти до наступної сторінки — не тільки візуально, але й у внутрішньому стані. На щастя, SwiftUI дає можливість поєднати те та інше. Для переходу змінюємо індекс поточної сторінки — це ясно — а також переводимо зсув у систему координат нової сторінки. Та… також анімуємо зсув до нуля. Тепер наш пейджинатор повністю перейшов до нової сторінки та готовий почати все спочатку.
Звісно, є багато чого тут можна покращити. Наприклад, відразу створювати не 3, а більше сторінок, щоб вони встигли підвантажитись. Тоді можна, наприклад, прокручувати більше однієї сторінки особливо різким жестом.
Схожий підхід, думаю, спрацював би й в JavaScript чи інших фреймворках.
26.10.2023
Ozymandias: Цивілізація на мінімалках
Ні, сьогодні буде зненацька про гру, яка захопила мене останній тиждень. Це Ozymandias - мінімалістична стратегія в жанрі 4X, дещо схожа на Civilization, спрощену до рівня трохи складнішого ніж настільна гра. Я завжди губився в подробицях та мікроменеджменті справжньої Цивілізації, а Ozymandias гра саме на мої здібності.
Це не значить, що вона легка. Замість випадкових мап, Ozymandias має набір ручних мап з різних історично-географічних контекстів — як настільна гра. Стартові позиції на них свідомо несправедливі, та є такі, на які я поки неспроможний.
Для виграшу треба виконати ряд цілей або просто захопити світ (що займає в рази довше.) У грі відсутня дипломатія, тож, хоч є “невійськові” цілі, але практично завжди доведеться принаймні захищати кордони. Типовий шлях такий: зайняти стільки землі, скільки встигнеш; розвинути технології; прокачати армію та перемогти сусідів. Гра “одночасно-покрокова”, тобто кожний гравець готує крок одночасно в секреті, після чого розраховується результат.
Єдина точка дотику між гравцями — битви, тож гра набуває найбільшої інтриги саме на останньому, військовому етапі. Для захоплення комірки потрібно два кроки: спочатку відбувається “виклик”. Відповідно, на другому кроці невідомо, чи посуне опонент армію, щоб захистити комірку, чи ні. При цьому та армія в попередньому положенні могла ставити виклик на іншу нашу комірку. Щоб досягти успіху, треба навчитися ставити ворогу дилеми.
Замість юнітів в Ozymandias у тебе є одна чи декілька армій та абстракція “сили”, яка застосовується до всіх армій. Є компроміс: чим більше армій — тим вони слабкіші (за однакового рівня доходів). Тому найвигідніше мати тільки одну армію, так її потенціал найвищий. Але це поки тобі не загрожують з двох сторін.
Системи гри математично прості та не містять випадкового компонента. Відповідно кожний крок можна продумувати з математичною детальністю. Головний важіль — на що витрачати гроші: зростання, розвиток чи силу. У складних сценаріях потрібно детально розкладати дії на кроки та стратегічно обмежувати витрати — якщо розкидати гроші широко, противники залишать тебе позаду.
Ще тут є мультіплеєр та він має бути досить приємним — зокрема тому, що вся гра триває не більше пари годин, а також завдяки одночасному прийняттю рішень. Але я ще його не пробував.
25.10.2023
Реалізація посторінкової прокрутки на SwiftUI
Проєкт, який я роблю, потребує посторінкової прокрутки на кшталт TikTok. Як виявилось, це не щось легко доступне, та найкраще поки що рішення я зробив сам.
Почав взагалі з найбільш очевидного — є ScrollView, а в нього цього року зʼявився scrollTargetBehavior(.paging). Він робить дещо схоже на CSS scroll-snap-type, але прокрутка зупинятиметься на кожному екрані. Ніби це те, що потрібно. Але проблем купа:
-
Використання памʼяті. Ми точно не хочемо створювати всі елементи списку відразу. Взагалі, як виявилось зі статей про такі інтерфейси, керування памʼяттю це як не найголовніше; наприклад, забагато відео чи навіть світлин в памʼять не влізе. На то є LazyVStack. Він утворюватиме дочірні елементи тільки тоді, коли до них дійде черга. На перший погляд — те, що треба. Проте як виявилось,
LazyVStackніколи не звільняє елементи. Відповідно, з довгим списком все одно буде та сама проблема, тільки пізніше. -
Поведінка при повороті екрана. Оскільки кожний елемент має розміри екрана, то при повороті висота елементу змінюється, а значить, позиція в списку (яка відстежується за координатою) втрачається. Можна це розвʼязати через те, щоб після повороту перемотувати список знову на поточний елемент. На жаль, список перемотується тільки з анімацією — це не тільки некрасиво, а й відстрілює промальовку всіх проміжних елементів.
-
Загальна нестабільність. Посторінкова прокрутка тут це тільки додаткове обмеження, та час від часу вона збивається. Особливо якщо прокручувати за допомогою смуги прокрутки — тоді режим сторінок практично виключається.
Є ще компонент List — він гарно впорається з довгим списком, оскільки реально використовує елементи повторно — взагалі цей компонент краще брати для схожих задач. От тільки List не вміє робити посторінкову прокрутку.
Найскладніша частина свого рішення — це обробка жестів. Але про це завтра.
24.10.2023
SwiftData - нарешті у SwiftUI є кіллер-фіча?
Як кумедно, останній раз про Swift я писав буквально рік тому. Це абсолютно незапланований збіг. Але маю версію, що інтерес до розробки під iOS підігрівається виходом чергової версії цієї операційної системи.
Цього року зʼявився новий фреймворк для зберігання даних - SwiftData. Його видатна особливість — наскільки мало треба зробити, щоб звичайний клас з даними перетворився на модель зі сталими даними. А саме, дописати до класу анотацію @Model і все. Після цього зміни до обʼєктів класу будуть автоматично збережені в базу даних. (Ну так, треба ще оперувати з контекстом — це місцевий аналог репозиторію, який відповідає за створення, видалення та пошук обʼєктів.) На відміну від вебтехнологій, тут база обʼєктна - тому ніякого SQL або ORM немає.
SwiftData очевидно побудований на попередніх технологіях - CoreData та CloudKit. Проте раніше моделлю міг бути тільки спеціальний клас, схему до якого потрібно було будувати окремо. А зберігати зміни доводилось вручну. Та до того, шаблонного коду в CoreData було багацько. Всі ці недоліки тепер виправлені, та я здивований тим, як легко зберігати дані з SwiftData.
Нарешті у Swift є щось таке, що хочеться використовувати та будувати на ньому цікаві речі. Як щодо решти… SwiftUI поступово зріє (хоча модель інтерфейсу суттєво відрізняється від вебу та сходу її не зрозумієш). Легко робити інтерфейс, що підтримуватиме і macOS - особливо завдяки макросам, яки вмикають та вимикають функції для кожної платформи. Для одночасного програмування є async/await. Трохи заважає, що в Інтернеті забагато матеріалів, що стосуються застарілих версій Swift, та треба розуміти, які з них вже втратили актуальність.
Ось більш детальний приклад по SwiftData, якщо цікаво.
23.10.2023
Як жити з Jira
Походивши з тиждень з мрією приховати Jira в Obsidian, дійшов висновку, що це одна з таких ідей, які в голові виглядають простими, але як почнеш втілювати, то зовсім не зрозуміло, як. (Тому, взагалі, краще ідеї проєктів перевіряти практикою, хоч мінімально.)
Скористався цим імпульсом, щоб подолати неприязнь та знайти спосіб користуватися Джірою так, щоб це було зручно.
Зокрема, класичний канбан мене не влаштовує, оскільки а) ця форма винайдена та зручна для команди, а не для окремої особи та б) щоб працював канбан, треба, щоб задачі вже були впорядковані, а моя головна проблема як раз з тим, що вони розповзаються. Наприклад: добре що в канбані є фільтр “Мої задачі”. Але що якщо я забув призначити задачу на себе? Або не вказав правильно епік, коли створював? І так далі.
Що мені подобається в Jira, так це мова запитів JQL. З нею можна знайти ті задачі, які потрібні. А потім з пошуку можна легко робити масове редагування. От тільки робити кожен раз новий пошук не дуже зручно.
Улюблені запити можна зберегти у вигляді фільтрів. Але ще зручніше потім зробити з фільтрів дашборд. Для дашбордів є два гаджети, які мене зацікавили: це “Filter Results” та “Filter Counts”. Перший виводить таблицю результатів фільтра, а другий — тільки кількість. Кількість дуже зручно використати як раз для виявлення проблемних задач (а з гаджета можна легко перейти до результатів пошуку.)
От з таким дашбордом я спробую користуватись Джірою як постійним інструментом планування особистої роботи.
Для масового імпорту задач є вбудований імпорт з CSV. Та якщо в тебе є просто список пунктів задач, то це технічно є CSV з одним стовпчиком, та його можна імпортувати вбудованими засобами. Єдине, чого не вистачає — це, щоб так само легко імпортувати нотатки. Може, тут дійсно варто зробити більш обмежений інструмент для імпорту з Markdown.
Останнє: Jira краще працює в Google Chrome. Я великий фанат Safari, проте обʼєктивно в Хромі інтерфейс реагує швидше. Це теж важливо для безболісного користування.

