Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
18.05.2024
OmniFocus
OmniFocus - це Jira у світі менеджерів задач для платформ Apple. В сенсі, не найкрасивіший (то буде Things), але найпотужніший. Так само як і Jira, OmniFocus є насамперед базою даних про задачі, з якої можна ліпити те, що влаштовує твої потреби.
Як і Jira, OmniFocus існує дуже довго (я перший раз його встановив у 2012) та як і Джіру, я його недолюблюю за надмірну складність. Проте коли в тебе сотні проєктів, великих та малих, то простими вони не виглядатимуть ніде.
Мої останні спроби зберігати проєкти були в власному плагіні для Obsidian; вимушений зізнатися, що не дочекаюся, поки я його завершу. Так, Ідея розробити систему та зробити під неї спеціалізований менеджер задач мені все ще подобається. В теорії зробити такий менеджер легше, ніж загальне рішення — бо набір функцій обмежений. Але річ у тім, що експериментувати з такою спеціалізованою системою стає надто дорого. Тому спробую експериментувати далі в готовому середовищі OmniFocus.
Нарешті, щодо систем та великої кількості задач. Зрозумів, що коли задач стає багато, то вони стають ніби дозволом нічого не робити (принаймні, проактивно) через відчуття марності. Тому критично важливо відокремити абсолютний мінімум поточних задач від решти. Причому їх кількість повинна залежати не від навантаження (яке за природою необмежене), а від можливостей.
17.05.2024
Перевірка унікальності в OpenSearch
…А немає її! В ті чи інші причини. Певно, головним чином тому, що індексація відбувається асинхронно, а до індексації перевірки не буде.
Проте є один метод, перевірений часом. Це зробити унікальний ключ ідентифікатором. Таке працює в багатьох NoSQL базах, наприклад, в DynamoDB практично лягає в основу. Тільки з міркувань рівномірного розподілення значень в OpenSearch краще брати не прямо ключ (скажімо, URL сторінки), а хеш від нього.
(Взагалі підхід з використанням справжніх значень як ідентифікаторів називається “натуральні ключі” та був популярним в часи “голих” SQL баз. А з приходом ORM як головного методу доступу — занепав.)
Ідентифікатори в OpenSearch, звісно, унікальні. Але й тут криється нюанс - звичайна операція індексації не заборонить повторюваний ідентифікатор, а просто перезапише документ (тобто зробить “upsert”). А нам потрібна операція create
.
Я вже натрапляв на проблему в тестах, що згенеровані фабрикою документи час від часу збігалися ідентифікаторами, вставлялися без create
, та призводили до випадкових помилок.
16.05.2024
Коли DRY не працює
Поділюся сьогоденним рефакторингом, яким дуже задоволений.
Історія така. Є розділ застосунку (насправді ActiveAdmin), де в багатьох місцях будуються адреси деякого ресурсу. Це в Rails робиться легко, так й код чистий.
Нововведення таке, що цей ресурс може існувати як самостійно, так і вкладений в інший ресурс. На кшталт такого: були просто “заклади”, а тепер зʼявилися у них власники; заклади власника можна переглядати окремим списком — проте загальний список теж залишився.
Задача: коли переходиш з власника на його заклади, а потім, наприклад редагуєш та зберігаєш — то маєш опинитись знову на списку закладів власника. (А якщо з загального списку — то повернутись туди.)
Ніби все просто та є навіть вбудована можливість зробити вкладеність ресурсів на рівні маршрутів. Але… тепер кожне місце, де будується адреса, повинно зʼясувати в якому ми режимі та побудувати або /owners/1/places/2
, або /place/2
.
Перша спроба розсипала по коду відповідні розвилки if
- що негайно засмутило Rubocop, та й вочевидь мало поганий вигляд. Замінити розвилки єдиною функцією (тобто класично “підсушити” код) немає можливості, бо вони відрізняються начинкою — маршрути всюди різні.
Тоді придумав взяти ті if
та витягнути всі в окремий модуль. Ну й нехай в ньому три майже однакових методи — зате тепер вони семантично сполучені. Читачам буде легко зрозуміти їхню мету, а може навіть пізніше порефакторити краще.
Отже, якщо вже доводиться задля нової логіки вносити побічні зміни в багато місць коду, нехай вони будуть стислими та легко розпізнаними.
15.05.2024
Масове видалення обʼєктів з AWS S3 та Google Cloud Storage
Сьогодні стикнувся з несподіванкою: якщо для видалення бакета AWS S3 Terraform примушує його спочатку спорожнити вручну, то з Google Cloud Storage робить те ж саме автоматично. Видалення може не тільки тривати днями, а й дуже дорого обійтись.
З першого погляду обидва сервіси не беруть плати за видалення об’єктів. Але є нюанс: спочатку треба дізнатись їх перелік. А це вже нормальна платна операція. Добре коли перелік є ззовні — може, в базі — але це не завжди так. Чим менш охайно та обережно пишуть в бакет — тим дорожче буде його почистити. Ось така пастка хмарного сховища.
Вихід, який я можу запропонувати, це правила життєвого циклу. Видалення обʼєктів за правилом дійсно безплатне. Цим можна скористатися при видаленні бакета: достатньо додати правило про видалення через 1 добу. Це застосується й до тих обʼєктів, що вже існують на момент створення правила. Щоправда, правила не виконуються миттєво та доведеться почекати — проте видалення вручну теж процес не швидкий.
Правила життєвого циклу — корисна штука. Єдине, що вони розглядають кожен обʼєкт окремо, тому не можна висловити, наприклад “залишити 1000 останніх файлів”. Зате можна розмежовувати правила за шляхом та за тегами, та навіть за розміром файлів — є де докласти фантазію.
14.05.2024
Відстеження кофеїну
🤎 Після перегляду ось цього відео вирішив задля забави встановити собі застосунок для стеження за кофеїном - HiCoffee. Треба сказати, що я й так відповідально ставився до кави принаймні не пив її після шостої. Бо під кофеїном я засинаю ще більш-менш, але прокидаюся декілька разів на ніч.
…Отже. Найбільшим відкриттям з застосунку було те, що період напіврозпаду для кофеїну - 5 годин. Цифру я цю знав, але не усвідомлював; точніше, в моїй уяві було так: за пʼять годин до сну кави не пити.
А тут завдяки графіку в застосунку я зрозумів, що насправді на час сну в організмі залишається 1/8 ранкової кави, 1/4 обідньої та 1/2 вечірньої. Якщо підсумувати, виходить майже ціла порція кави перед сном! Одним словом, за типовий день кофеїн накопичується швидше, ніж виводиться.
Все це, очевидно, дуже приблизно та абстрактно. Але дозволяє зрозуміти у відносних кількостях, як на тебе впливає кава, та коли пора зупинятись. (Звісно, не тільки кава, а й кола, чай, енергетики та так далі.) Для мене дуже корисний експеримент.
Також хочу відзначити, що HiCoffee здатний скорелювати споживання кофеїну з даними з Apple Health. Мені поки рано робити висновки, але вже якимсь чином є слабка кореляція високого споживання та коротшого сну. Отакої.
13.05.2024
OpenSearch в тестовому оточенні
Як забезпечити кожному тесту чистий аркуш в OpenSearch? В теорії — просто. Проте щоб це ще й не гальмувало збірку, довелося попрацювати.
-
Зазвичай з PostgreSQL ми вживаємо транзакції. В OpenSearch транзакцій немає. Значить, просто скасувати зміни після тесту не вийде. Другий підхід — це команда
TRUNCATE
. Такої в OpenSearch теж немає; щоправда, є API “delete by query”, яким можна видалити з індексу всі документи, але вона не ефективна. -
Отже, залишається тільки видаляти цілі індекси та створювати наново. На жаль, створення індексів не така швидка операція, щоб робити її без потреби. Тому я порефакторив код запитів, щоб він допускав відсутність індексів (тобто інтерпретував як порожній результат.) Таким чином, індекси ми створюємо тільки тоді, коли в них пишуться дані. (А значить, і видаляти потрібно не так багато, а коли тест не звертається до OpenSearch - то взагалі нічого.)
-
До речі, на відміну від PostgreSQL, схема даних зберігається в шаблонах, а не в самих індексах. Тому створювати індекси можна навіть не окремою операцією, а неявно, під час додавання до індексу першого документу.
-
Нарешті, спочатку я видаляв індекси так, як чищу таблиці в PostgreSQL, тобто кожний індекс окремим викликом. Виявилося, що це повільно (і навіть паралелізація викликів не допомагає.) Тому перейшов до очищення всіх індексів за префіксом
test
. -
Ідея, якою я ще не скористався: для кожного тесту генерувати унікальний префікс для ізоляції даних. Тоді можна видаляти всі тестові дані наприкінці.
12.05.2024
Anbernic RG353M: ностальгія в кишені
🕹️ Минулого року натрапив на кишенькові консолі-емулятори. В мене (та й мабуть, у цілого прошарку українців) такий пристрій моментально викликає бурхливу ностальгію. З одного боку, вони нагадують кишенькову гру “9999 in 1”, з якою можна було грати в Тетрис в трамваї. З іншого, емулятори пропонують ігри з всіх приставок, які можна було зустріти у друзів чи в компʼютерних клубах — від NES до PlayStation 1. Та, на диво, з карткою памʼяті можна дійсно зібрати “9999 в 1” ігор з усіх поколінь на пристрої, який влазить в кишеню.
Обрати консоль насправді дуже складно, бо їх є безліч. Мене спочатку привабила модель, яка взагалі кріпиться на дармовис та здатна емулювати PSX - бо це просто чорна магія! Але ж на практиці з таким розміром нереально грати. Треба знайти вірний баланс розмірів, ціни, та можливостей саме для тебе. Можу порадити канал RetroGameCorps на YouTube, там є огляди, порівняння та інструкції.
Я зупинився на Anbernic 353M. Розміром вона приблизно з iPhone 15. Якість збірки чудова — краща за Nintendo Switch, наприклад. Софт я відразу переставив на ArkOS, за рекомендацією. Взагалі на таки пристрої стає або Android, або збірка Linux (як-от ArkOS). Далі залишається знайти комплект ігор та насолоджуватись. Головне, що всі емулятори вже готові до використання.
Головним мінусом цього пристрою є те, що в мене немає часу на ньому грати! Я давно не їжджу годинами в транспорті, а коли є час пограти — то кишенькова консоль просто програє великому екрану та сучасним іграм. (Хоча, до речі, її можна підʼєднати до HDMI.) Все одно, в пристрої, що готовий запустити будь-яку ретро-гру є щось магічне та особливе.
11.05.2024
YAGNI
✂️ Трохи рідше ніж DRY або KISS можна почути мантру YAGNI - вам це не знадобиться. Мантра нагадує, що зайві функції ускладнюють проєкт, та на варто витрачати час на те, що не є підтвердженою потребою користувачів.
Проте зазвичай інженери будуть використовувати цей принцип для звуження фіч, а не архітектури. Про архітектуру зазвичай думають з запасом.
Наведу приклад з проєкту, на який я витратив пару років, але так і не запустив. Це був застосунок на React Native з власним ORM на основі PouchDB; на кожному пристрої — власна база, з синхронізацією через CouchDB. Тут поки все прийнятно. Але… база, як завжди, потребує оказійних змін схеми — міграцій.
Міграції в розподіленій базі — набагато цікавіше, ніж в традиційній; як бути, коли клієнт зі старою версією застосунку отримує нові дані? А коли продовжує писати стару схему в спільну базу? Як уникнути конфліктів?
Я витратив багато часу на розробку (теоретично) надійного механізму, а також убезпечення кожної міграції — хоча в мене був лише один користувач з одним пристроєм. Я міг би редагувати дані вручну в базі, якби так було зручно. Але ні, запас на майбутнє мене змушував робити абсолютно зайву роботу (бо проєкт не був опублікований.)
Думаю, тут має значення те, що програмістів приваблюють складні задачі. Часто розробка чергової “системи міграцій” нас захоплює, в той час, як важливі для продукту задачі потребують рутини.
Засновник екстремального програмування висловив цей принцип ще екстремальніше: Вам потрібний гетер для змінної. Добре, пишіть. Але не пишіть сетер “бо теж знадобиться”. Не пишіть гетери для інших змінних “бо теж знадобляться.” Кращий спосіб писати код швидко це писати його менше.
10.05.2024
OLTP проти OLAP простими словами
Коли ви обираєте базу, щоб напхати в неї багато даних — давайте зараз не будемо думати про те, що таке “багато” - та ганяти по цих даних аналітику — звіти, статистику, і таке інше — то недовго потрібно, щоб прийти до баз категорії OLAP та вирішити, що це саме те, що тут потрібно. Snowflake, Redshift, Clickhouse - всі вони говорять про великі дані та аналітику.
Проте є фундаментальна відмінність, про яку необхідно подумати. OLAP бази розраховані на невелику кількість користувачів та запитів. Не можна взяти базу OLAP та віддати її на використання клієнтам. Не тільки тому, що запити можуть тривати секунди, хвилини чи навіть години. Тут як раз можна погратись, запустити стрес-тест та зʼясувати, що ваші запити не такі вже й повільні.
Проблеми виникнуть, коли паралельних запитів буде багато. Бо типова OLAP база загнеться на 10-100 одночасних запитах. (Хмарна OLAP база на кшталт Snowflake буде просто зростати у витратах, тож там легше помітити заздалегідь, що багато запитів краще не робити.)
Бачите, OLAP бази були розроблені для бізнес-аналітики, тобто коли відділ аналітиків робить запити для узагальнення всіх даних в базі разом. Якщо “аналітика” це користувач бачить статистику по своїх постах, це зовсім інша задача. Та дуже важливо не помилитись, бо поки користувачів немає, це обмеження не помітити.
09.05.2024
Як влаштована кросплатформенність у Swift?
Apple багато ставить на пропозицію “напиши одноразово — запускай всюди”… принаймні на власних платформах. Втім, очевидно що платформи не еквівалентні та для кожної потрібний особливий код. Від корекції вигляду до використання унікальних можливостей платформи.
Наприклад, навігація побудована на однакових принципах, але очевидно суттєво розбігається. На iOS особливе розташування кнопок на панелях. На macOS є ціла панель меню. Діалоги та навіть процес збереження файлів абсолютно різний.
Все це у Swift розвʼязується просто та приємно — блоками умовної компіляції. Виглядають вони як директиви препроцесора в C та інших мовах: #if os(iOS) ... #endif
. (Але, на відміну від C, ці блоки є частиною мови.) В блоки умовної компіляції можна заточити майже будь-яку ділянку коду:
MyContentView()
#if os(iOS)
.navigationBarHidden(true)
#endif
Це чудово підходить для внесення точкових змін та не потребує багато думати про сумісність платформ: щоб скомпілювати застосунок SwiftUI з iOS на macOS, достатньо виключити всі специфічні інструкції. А головна маса стандартної бібліотеки спроєктована семантично універсальною.