Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

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, але прокрутка зупинятиметься на кожному екрані. Ніби це те, що потрібно. Але проблем купа:

Є ще компонент 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, проте обʼєктивно в Хромі інтерфейс реагує швидше. Це теж важливо для безболісного користування.


22.10.2023

Як НЕ користуватись Докером для розробки

Як писав, розробка в Docker мене все ще не влаштовує. Втім, я перейшов на macOS саме для того, щоб працювала більшість потрібних для роботи програм (бо до того колись дуже давно був Windows+VirtualBox.) Та й дійсно, в більшості випадків потреби в Docker в мене немає. Ізолювати оточення проєктів та керувати флотом процесів допомагають наступні інструменти:


21.10.2023

Як працює механізм прихованої копії BCC

Механізм “прихованої копії” є найбільш неочевидним з усіх стандартних поштових функцій. Та заголовок Bcc: дійсно є стандартним, бо він визначений ще в стандарті поштових повідомлень RFC 822. Ба більше, він був згаданий ще у RFC 680 1975 року. Всі їм користувались, але як воно працює? Як лист опиняється у прихованого отримувача, коли в листі немає про нього ніяких згадок?

Тут важливо розуміти, що лист доставляється за протоколом SMTP, а протокол SMTP взагалі не цікавиться змістом листа, зокрема його заголовками. Для доставлення пошти є адреси конверта, про які я вже писав. Технічно, вони можуть і не сходитися з адресами в заголовках, та це дійсно трапляється на практиці (наприклад, якщо сервер будує зворотну адресу для сервісної комунікації.)

Коли ти відправляєш листа по SMTP, то ніякого Bcc: взагалі не потрібно, бо можна просто вказати потрібну адресу (чи декілька) в команді SMTP RCPT TO. (До речі, протокол SMTP був вперше стандартизований у RFC 788 у 1981, тобто вже після того, як придумали заголовки та Bcc:.)

Тому Bcc: обробляється ще до надсилання повідомлення, на рівні поштового клієнта. Фактично, так само як і заголовки To: та Cc:, він містить інструкції для поштового клієнта, а саме, ті адреси, які потраплять на початковий “конверт”, тобто в команди RCPT TO, які клієнт надішле на SMTP сервер.

Відповідно, коли лист надходить на будь-який сервер SMTP, наприклад, у Мейлтрап, або будь-який інший сервер, навіть якщо це локальний Mailcatcher, то ніякого заголовка Bcc: в листі вже не має бути, а якщо є — це помилка клієнта. Натомість в Mailtrap можна буде побачити адресу Bcc в розділі “SMTP Transaction Info”. Так можна перевірити, що доставлення прихованої копії відбулося.


20.10.2023

Формат поштових відправлень (message/rfc822)

Останнім часом доводиться складати чимало поштових повідомлень вручну. Чому вручну? Бо так можна досягти більш компактного, мінімального змісту, порівняно з автоматичними засобами (наприклад, чудовим пакетом для Ruby mail.)

Цікаво, що цей формат, так само як HTTP, був очевидно розроблений для можливості ручного авторства, але згодом нашарування абстракцій витіснило його з колективної свідомості. Хоча, формат повідомлень SMTP та HTTP суттєво простіше, ніж, наприклад, HTML. Навіть коли доходить до повідомлень з декількома частинами (multipart).

Перше, що треба знати: повідомлення складається з заголовків та тіла. Між ними завжди стоїть порожній рядок. Заголовки в форматі Header-Name: value, мабуть, найбільш відома частина повідомлення. Менш відомо, що заголовок може містити декілька рядків; додаткові рядки значення починаються просто з табуляції.

Тіло повідомлення складається просто з тексту. На цей час текст може містити будь-які символи, в тому числі UTF, з цим проблем немає. Але якщо тіло двійкове — воно обовʼязково кодується, зазвичай в Base64. Це на відміну від HTTP, де в тілі дозволено відправляти двійкові дані.

Зокрема це потрібно тому, що двійкові дані зазвичай не будуть всім тілом повідомлення, а лише його частиною. Текст тіла в такому випадку можна прочитати як декілька частин — отже, ніяких двійкових даних всередині бути не може. Частини розділяються спеціальними рядками та також мають закінчуватись трохи іншим рядком. Кожна частина має свої заголовки та своє тіло… яке в деяких випадках може теж складатись з частин.

Ось мінімальний, але повний приклад повідомлення з двома частинами:

Content-Type: multipart/mixed;
    boundary=demo

--demo
Content-Type: text/plain

Hello, Text!
--demo
Content-Type: text/html

<p>Hello, HTML!</p>
--demo--

Як бачите, ніякої магії, якщо розібратись.


19.10.2023

One Sec - програма для припинення відволікань

Як я колись писав, особливо підступними є рефлекторні відволікання. Тобто такі, що виникають несвідомо в той час, коли ти маєш займатись чимось іншим.

Знайшов по рекомендації програму One Sec. Вона блокує відволікання особливо винахідливим чином. А саме, запрошує почекати декілька секунд перед відкриттям тих ресурсів, які ти відзначиш як відволікання.

Для мене це ефективніше ніж повне блокування тому, що багато ресурсів не є однозначно поганими - Reddit, YouTube, Twitter є корисними джерелами інформації, але в той час є й засобом відволікань. Наявність паузи дозволяє зупинитись та усвідомити свою дію.

Працює One Sec на iOS та на macOS, для додатків та для сайтів. Як можна здогадатись, механізм переривання не завжди реагує ідеально. На iOS він використовує Shortcuts та деколи має затримку на пару секунд. На macOS зʼявляється окреме вікно блокувальника, яке я декілька разів ненавмисно закривав; тоді One Sec просто припиняв роботу. (Лайфхак: через BetterTouchTool можна призначити комбінації Cmd+Q порожню дію в межах цього додатка, тоді його не можна буде закрити.)

Проте насправді задачу свою One Sec виконує повністю, попри ці перешкоди. Смішно помічати за собою несвідомі реакції; в перший день використання я навіть встиг зазирнути в телефон та побачити екран паузи, поки чекав, щоб минули ті пʼять секунд на компʼютері. І сміх, і гріх. Приємно звільнятись від таких рефлексів.

One Sec наразі ще й безплатна “бо настав час думскролінга”, як пише автор.


18.10.2023

Профілювання проблемної ситуації з Go

Поступила скарга: наш сервіс на Go, локально запущений в Докері, призводить до постійного споживання CPU, навіть коли нічого не робить. Що сталося? Чи це Докер винний? Така задача розвʼязується профайлером.

По-перше, сам Docker командою docker top вкаже, що CPU споживає нібито сам процес сервісу. Та й пояснення від найпростішого вказує туди ж.

Щоб зрозуміти, що робить сервіс, підключаємо профайлер. В такій ситуації — на всю програму разом. Для цього в Go достатньо додати три рядки на початок функції main():

f, _ := os.Create("/mnt/tmp/service.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()

Це автоматично покриє весь код, включаючи горутіни. Компілюємо, запускаємо контейнер з сервісом, чекаємо пару хвилин та зупиняємо. Це згенерує профіль та збереже його в файл (в Докері зручно буде, якщо файл помістити в директорію, що примонтована — бо тоді відразу можна з тим файлом працювати.)

Далі можна слідувати офіційному посібнику. Я раджу не блукати в консольних командах профайлера, а відкривати вебверсію (команда web). Відразу бачу, що більшість часу програма витрачає на компонент, який оновлює деякий кеш. Ну трохи дивно, але має сенс, бо він це робить за графіком, а не “ліниво”.

Дивлюся на період оновлення… він береться з конфігураційного файлу та локально дорівнює 10000000. З першого погляду взагалі схоже на налаштування “ніколи не оновлювати”. Але то зважаючи на те, в яких це одиницях. По коду виходить, що це значення читається як time.Duration, а Duration вимірюється не в мілі- та не в мікро-, а в наносекундах. Тож цей астрономічний на вигляд період насправді дорівнює лише одній сотій секунди! Виходить, що цей компонент практично не припиняв працювати — звідси й витрати CPU.

От заради таких ситуацій треба бути готовим взяти в руки профайлер.