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

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

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

29.05.2024

Сповіщення на iOS: по-простому та по правильному

Повернувся до роботи над таймтрекером. Сьогодні вдосконалив, як працюють сповіщення-нагадування (чи не найважливіший компонент всієї системи.)

Взагалі, щоб відкрити застосунок за сповіщенням, робити нічого не потрібно. Окрім запланувати (або отримати) це сповіщення. Цим я й користувався до цього часу. Нагадування про пінг просто відкривали застосунок, а він на момент запуску зʼясовував, чи є актуальний на дану хвилину пінг, та показував в цьому разі форму.

Плюси — рішення тривіальне. Мінуси — якщо відкрити старе сповіщення, то застосунок форму не відкривав. Також нічого не відбувалося, якщо сповіщення приходить, а застосунок вже відкритий. (Хоча за три місяці такого не трапилось жодного разу. Статистично неймовірна подія!) Тобто, можна сказати, що це халтура, але для MVP мене цілком влаштовувало.

А правильне рішення це задекларувати делегат та обробляти відповідні події.

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

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


28.05.2024

Планування: з простору проблем у простір рішень

Хотілося закінчити про процес планування, бо це досить важлива тема та я їй сам приділяю недостатньо уваги.

Коли простір проблем досліджений до достатньої міри (попросту кажучи, ідеї закінчились), то він перекладається на простір рішень. В моєму випадку рішення — апріорі застосунок, залишається перебрати кожну проблему, ризик, та користь та покрити їх функціями застосунку. При цьому можна відразу прибрати аспекти з низьким пріоритетом.

Отримуємо закінчену мапу продукту, який виконує всі ті потреби, які ми змогли уявити. У власній розробці помічаю антипатерн: я знаю, що “чогось” продукту не вистачає, але намагаюсь закривати це навмання — функцію тут, функцію там. Наявність мапи допомагає не тільки сфокусувати зусилля, а й мати осяжну ціль, поза якою продукт можна назвати “готовим”.

Наприклад: я окреслив ризик (та високий!), що людина не довірятиме запропонованій системі статистичного обліку. Такий ризик мені був відомий давно, але я нічого з ним не робив. Після формального прийняття ризику я додав епік “Підручник”. Бо дійсно, в цій програмі прихована математична модель, яка не стане зрозумілою тільки з використання та неявних ознак.

Або інший приклад: я з самого початку задумав, що теги повинні мати вкладеність (наприклад: “робота / програмування / проєкт X”). Але для вкладеності не знайшлося місця на мапі: вона не розвʼязує жодної потреби користувача. При цьому складності додає — як у розробці, так і у використанні. Тепер можна впевнено прибрати її з проєкту та розблокуватися.


27.05.2024

Планування проєктів схемою BRIDGeS

В інді-розробці часто проблема навіть не в тому, що немає часу робити, а незрозуміло куди рухатись. Особливо коли звик працювати в команді, де структура роботи задана згори. Може стати сюрпризом, що просто робити історію за історією недостатньо для успіху.

А взагалі ця задача стосується будь-якого продукту, не тільки інді, та не кожна команда вміє їх розвʼязувати. У нас в Railsware вміють, та навіть витягнули своє ноу-хау в фреймворк, який назвали BRIDGeS. Він чудово підходить для того, щоб зібратись гуртом навколо білої дошки, але нічого не заважає робити планування наодинці в Фігмі.

Ключовою особливістю Bridges є мозковий штурм. Ми починаємо зі збору уявлень про майбутній продукт, його користувачів, та їхні проблеми та ризики. Це найбільш плідний етап роботи — вилазять неочікувані, невимовлені аспекти. (Наприклад, в схемі зверху спочатку була тільки одна персона “Користувач”, але пізніше я помітив, що деякі ризики мають на увазі більш обмежені персони, та виділив їх окремо.)

Різні аспекти записуються на картки різного кольору та упорядковуються за сенсом. Це сильна сторона дошки — візуальний компонент вміщує купу інформації.

Коли всі уявлення записані на дошку, ми їх пріоритизуємо та розшукуємо розвʼязки, тобто компоненти системи, які розвʼяжуть ті чи інші потреби. На цьому етапі менше сюрпризів, проте доводиться шукати баланс між зазначеними ризиками та користю. Стає до нагоди як довідка та мапа уявлень, яку ми попередньо зробили.

Нарешті після того, як зрозумілі розвʼязки, ми перетворюємо їх на знайомий план епіків та задач. Ось так можна від абстрактного уявлення про продукт перейти до дій.


26.05.2024

Автопостинг з CI: зовнішнє сховище

Я зʼясував, що без зовнішнього сховища з CI постити не вийде. Займався пошуком такого сховища.

В мене тут є дві потреби: запобігання конфліктам та власне зберігання стану. Краще першу відразу відокремити: запобігання конфліктам вимагає синхронності, що відразу звужує можливий список. Думаю, краще для того покладатись на вбудований функціонал CI - щоб просто не запускав дві збірки одночасно. Принаймні, для першої версії цього достатньо. (Наприклад: Terraform з S3 може використовувати DynamoDB для замикання стану.)

Як щодо зберігання, то в мене спочатку думки пішли в бік баз даних, а саме Firebase та вище згаданої DynamoDB. Цих двох, бо їх достатньо легко розгорнути. Проте будь-яка база зробить рішення практично непереносним, а підтримка різних баз вимагатиме відносно складних адаптерів.

Тому вирішив залишитись в парадигмі “єдиний великий JSON”: всі дані зберігаються в один файл JSON, а далі ми цей файл зберігаємо… на S3. Це очевидний вибір, оскільки API S3 став стандартом де-факто для хмарного сховища. Плюс, користувачі Hugo вже можуть мати налаштований S3. Плюс, JSON-“базу” можна взагалі нікуди не відвантажувати, а тримати локально, якщо CI вам не потрібний.


25.05.2024

Труднощі з автопостингом з CI

Отже, як я писав, хотів би загорнути своє рішення для автопостингу в гарний пакет та опублікувати. Частиною того є дійсно автоматичний запуск: наразі це скрипти, які я мушу згадати запустити сам, до того ж на власному компʼютері.

Модель така: є статичний сайт на Hugo; коли в вхідному коді зʼявляється новий пост, публікуємо його в Telegram, Mastodon та ще кудись. Логічним було б поєднати публікацію з розгортуванням самого сайту: для цього на CI вже запускається hugo. Втім, просто “взяти та запустити” мої скрипти на CI не вийде. Чому?

У скриптів є стан. Наприклад, це відображення постів на ID повідомлень в Telegram. З нього скрипт знає, які пости залишилось опублікувати. Поки стан зберігається в JSON прямо в репозиторії. Це просто в реалізації та уможливлює виправлення його вручну.

Чи можна зберігати JSON в репозиторії з CI? Технічно, можна; GitHub Actions дозволяють робити git push. Але з CI ми потрапляємо на територію рівночасності та мусимо виконувати всі її вимоги. Що буде, коли я зроблю git push два рази поспіль — може, щоб виправити помилку? По-перше, git push з CI може вже не спрацювати, бо репозиторій “поїхав далі”. По-друге, два різних запуски можуть так само налізти один на одного (хоча цього легше уникнути.) А нам життєво необхідно, щоб кожна публікація була занотована в стані!

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

PS: для простіших випадків знайшов цікаву дію github/lock - тут замки вбудовані у GitHub.


24.05.2024

Домашній DNS у відключенні

Знаходжусь в пошуках рішення для живлення роутера від павербанка постійного струму. Про це буде окремий пост пізніше, а поки що зачеплю дотичну тему: що робити з Pi-Hole?

Pi-Hole - то домашній DNS з блокуванням реклами. В мене він встановлений на ODroid XU4. Нарікань жодних не маю, але з живленням виходить проблема. ODroid XU4 живиться власним блоком з параметрами 5 В на 4 А. Чотири ампери — це поза будь-яким стандартом USB, тож просто “взяти та вткнути” не вийде, який би потужний павербанк не був. Дев’ять вольтів ніби йому теж протипоказано. Також немає надії, що вистачить менш ніж 4 А, принаймні тому, що в мене система завантажується з зовнішнього диска.

Бачив ідеї, що можна взяти USB-C кабель з вибором режиму 20 В та конвертор напруги до 5 В та заживити через такий ланцюг. Поки залишимо це на план “Г”, бо немає ніякої впевненості.

Далі, альтернативи: перемістити блокувальник DNS на роутер. Сам Pi-Hole не вийде — він досить важкий по залежностях. Є аналог для AsusWRT-Merlin: Diversion. Нюанс: в нього немає вебінтерфейсу. Вебінтерфейс в DNS-блокувальнику вкрай потрібний, щоб його можна було вимкнути. (Хибні позитиви це нормальна справа — наприклад, Pi-Hole не дозволяє мені відписатися від деяких розсилок через заблокований домен.) Тут теж можна розвʼязати технологіями — може, навіть, встановити для того розумну кнопку-вимикач.

Та ще пропонують купити малесенький Pi Zero, що живиться від USB, та увіткнути його в сам роутер. Теж цікаве рішення з очевидними мінусами: теж потрібно щось купувати, налаштовувати, та й підтримувати.

…Що тут сказати. Певно, найрозумніший поки вихід — це вимкнути до біса то блокування.


23.05.2024

Публікація в Mastodon: світлини та архітектура

🖼️ По вчорашньому питанню з форматом ілюстрацій вийшло ось що: по-перше, хоч пости пишуться в Markdown, але розмітка для зображень заборонена. Що, взагалі, абсолютно логічно. Тож залишається два підходи: або світлину окремим постом як в Telegram, або включати світлину в головний пост. В головному пості вона зʼявиться наприкінці, але все одно так мені більше подобається.

🚧 Ще для повноти функціонала мені потрібно редагувати пости, тобто синхронізувати пост в Mastodon з тим, що в блозі. Як це вже працює для Telegram: я зберігаю для кожного поста (з блогу) його ID в Telegram, а також контрольну суму змісту. Якщо збережена контрольна сума розбігається з актуальною, то викликаю API редагування посту. (Насправді там навіть не одна, а дві контрольні суми. Одна — від змісту, що безпосередньо надсилається в Telegram. Друга — від вхідного файлу. Вона використовується, щоб не обробляти файли зайвий раз.)

🚢 Все це рішення хотілося б опублікувати, але зупиняє те що немає красивої історії розгортування. Скрипти я запускаю вручну, а потім комічу результати. Хотілося б щоб публікація відбувалася автоматично. Ймовірно, під час генерації статичного сайту. Якби оце розвʼязати, то можна було б перетворити купку скриптів на продукт.


22.05.2024

Mastodon: публікація через API

Хотілося б ретранслювати цей канал в Mastodon, але ж в концепції WOPE (Write once - publish everywhere.) В Телеграм я давно публікую ботом, також весь зміст є на сайті, доведеться й для Mastodon зробити автоматичну публікацію.

В цілому це набагато простіше, ніж з Telegram або з Twitter. У GoToSocial є API, з ним все зрозуміло, працює без сюрпризів. На власному сервері жодних обмежень на довжину поста немає. Навіть в Markdown можна писати.

Але ж мої пости пишуться не просто в Markdown, а містять метадані. Також я використовую особливу розмітку для посилань на пости з каналу. Можна або, як то кажуть, обробити регулярками по-простому, або все ж доведеться розібрати Markdown, перетворити та зібрати назад. Знайшов рендерер саме для цього.

Та друге питання — що робити з ілюстраціями. Наразі вони публікуються в Телеграм окремим постом через особливості відображення постів зі світлиною, які мені не подобаються. В Mastodon можна або робити так само, або обʼєднати в один пост; треба ще поекспериментувати та визначитись. Якщо окремим, можна робити мінітред з двох постів.


21.05.2024

AWS Lambda для запуску скриптів

Зазвичай про Lambda згадують в контексті всяких конфігурацій Serverless, тобто хочуть заміняти ними справжні сервери. Ну або для інтеграцій з іншими сервісами, наприклад, як обробник черги.

Але, за що я люблю Lambda: це найпростіший спосіб запустити код в AWS. Так, може я виходжу з позиції, коли базова конфігурація вже є в Terraform: ролі, мережа, і таке інше. Але нічого простішого я не знаю та лямбди чудово виконують ролі разових чи запланованих задач.

Ключем є можливість задеплоїти Lambda відвантаженням ZIP-файлу. Навіть з вебінтерфейсу. Особливо в поєднанні з Go це зводить деплой до секунд: скомпілювати, створити архів, відвантажити. Бо програми на Go легко скомпілювати локально та результат не буде відрізнятися від “чистої збірки” на CI. На відміну, наприклад, від Ruby. Хотфікси для Lambda можна розгортувати вручну (хоч не заохочую таку поведінку.)

Також Lambda можна запустити з вебінтерфейсу. Функція ця називається “Test”, але нехай то не збиває з пантелику: такий тест не відрізняється від іншого запуску. Тут навіть можна передати параметри; в моїх лямбдах часто будуть особливі ключі запуску: без ключа вона виконує головний намір — наприклад — за планувальником, а з ключем переходить в ручний режим та робить щось особливе — наприклад, перевірку або повне оновлення, або ще щось.


20.05.2024

Пара відкриттів з керування задачами та GTD

Поки налаштовував задачі в OmniFocus, зʼясував дві важливі речі. Як і з усім, корисно було порозглядати відео від справжніх користувачів.

🎯 Обовʼязково формулювати кожний проєкт як закінчуване твердження. Для цього потрібно додумати цей проєкт та зрозуміти — в цьому моменті — що ти від нього хочеш. Якщо не додумати зараз, доведеться думати щоразу як бачиш проєкт. Коли недодуманих проєктів пʼятдесят, їх сенс втрачається та система розвалюється.

Наприклад: висів в мене декілька років проєкт “Прибрати гараж”. (Зазвичай навесні зʼявляється, а потім поки доберуся, ба — вже знову холодно та не на часі.) Змусив себе подумати та переписав як “Бути задоволеним порядком у гаражі”. (Ну, бо дійсно — немає обʼєктивного критерію тут, ідеальним гараж ніколи не стане.) Та відразу стало зрозуміло, як його просунути: продати шини, замовити вивіз сміття, і так далі.

📝 Нарешті зрозумів силу контекстів. “Контекст” по GTD - це категорія обставин, в яких можливо зробити задачу. Я до них ставився як до звичайних тегів для фільтрації. Але в чому сила: кожний контекст утворює окремий список задач. Ключовий момент, що в нього йдуть тільки ті задачі, які готові до виконання.

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