Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
11.02.2024
Go 1.22
Вийшов тиждень тому Go 1.22. Для мене це чудова новина, бо цей реліз чекаю ще десь з початку осені. Не тому, що вона щось таке нове приносить, а через комбінацію обставин.
По-перше, все ж дещо нове зʼявилось у версії 1.21 - це структурне логування. Я б хотів уніфікувати все логування в проєкті навколо стандартної бібліотеки, включаючи надсилання помилок в Sentry. (Про все це напишу, як відбудеться.)
По-друге, в тій же ж 1.21 виправили цікаву помилку в розборі поштових повідомлень — а саме, повідомлень без змісту, а тільки з заголовками, що цілком дозволено стандартом. Стандарту тридцять років, а баги все виправляють. Причому оскільки бібліотека розповсюджується з Go, то я не знаю способу оновити її без переходу на нову версію компілятора та всього іншого.
Чому ж я досі не на 1.21? А там вилізла інша помилка, ще страшніша, бо сервіс геть вилітав з повідомленням fatal: morestack on g0. Довелося відкотитися на 1.20. Помилку виправили, але тільки для 1.22, а бекпорт для 1.21 так і не встиг вийти. Тож той код, якому потрібно було розбирати повідомлення без змісту, чекав на 1.22 - і нарешті дочекався.
10.02.2024
Сповіщення в iOS: не все так просто
Для проєкту стохастичного таймтрекера сповіщення видались не такою простою задачею, як я собі уявляв.
Парафразую: сповіщення — це привілей, а не право застосунку. В мене за звичкою з більш прямолінійних систем було уявлення, що скільки я сповіщень створю, стільки й зʼявиться. Але на практиці виходить не так. Є купа незрозумілих обставин, за якими сповіщення ти побачиш тільки в журналі, постфактум. Так що доведеться багато експериментувати з налаштуваннями. Наприклад, є таке як interruptionLevel = .timeSensitive
. Емпірично, воно не рятує, але допомагає.
Є ще критичні сповіщення — це такі, які приходять від “Тривоги”, наприклад. Критичні сповіщення обходять всі обмеження системи (якщо користувач їх не вимкне.) На них потрібний спеціальний дозвіл, який видає підтримка Apple. Оце рівень ексклюзивності! Я для MVP спробую туди не лізти.
Ще виявилося, що зміст сповіщення повинний вказувати звук — інакше сповіщення зʼявиться абсолютно непомітно. В мене взагалі завжди звук вимкнений, тому і в цьому застосунку я планував спиратись на вібрацію Apple Watch - але щоб отримати цю вібрацію, маємо зазначити звук. Така не дуже очевидна конфігурація.
09.02.2024
Імпорт з CSV в Taxer
Сьогодні — останній день подачі податкової декларації. А значить… я дізнався про новий функціонал Таксера — імпорт операцій з CSV. (Ну, можливо, вона там існує з минулого року, але я тільки помітив.)
В сукупності з експортом CSV з кабінету ПриватБанку чи Монобанку маємо просте напівавтоматичне рішення для підготовки звітів. Та, коли мова йде про фінансові сервіси, мені спокійніше так, ніж інтеграцією через API.
Тепер, про технічну реалізацію. Таксер просить CSV в суворо фіксованому форматі. Це хоч трохи незручно, зате спрощує логіку на їхньому боці — а це значить, знижує шанс непередбачуваної ситуації. Що знов-таки вигідно під час роботи з фінансовими даними.
Щоб збудувати CSV вірного формату, можна скористатися або редактором таблиць, або написати скрипт. Я зробив невеличкий скрипт на Ruby, ділитися яким немає сенсу — він надто спеціалізований. Ну, хіба що ось розбір CSV з Монобанку.
Під час імпорту є не тільки валідація, а й “транзакційність” в тому сенсі, що якщо бодай одна операція буде хибною (уводить рахунок в мінус, наприклад), то весь CSV не буде імпортований. Думаю, всім сервісам так варто робити, бо після виправлення помилок не доведеться ще й відфільтровувати вже імпортовані рядки.
08.02.2024
Асинхронні функції з синхронними назвами
Досить багато часу провів в пошуках помилки в програмі на SwiftUI, яка була спричинена функцією removeAllPendingNotificationRequests. Ця функція видаляє всі заплановані локальні сповіщення.
В мене був дуже простий алгоритм для оновлення плану сповіщень: почистити всі, що були, та створити нові. Операція не така вже й важка, тож можна було дозволити собі таку неефективність заради спрощення логіки.
Але чомусь мої сповіщення ніяк не зʼявлялись. Я вже все перевірив: зміст, розклад, дозволи, запуск на симуляторі та на справжньому телефоні. (До речі, багато систем в iOS погано працюють на симуляторі — але сповіщення не одна з них.)
Нарешті, знайшов відповідь у документації. Виявилось, що функція removeAllPendingNotificationRequests виконується асинхронно. Тож поки я створював свої нові сповіщення, вона їх тихесенько видаляла! Ба більше, у цієї функції навіть немає ані колбека, ані іншого способу дізнатись, що вона завершилась. Напевно, задум такий, що ти її будеш викликати для остаточної зачистки, коли користувач вимкнув сповіщення — або щось таке.
На мою думку, асинхронна функція має бути очевидною. В TypeScript, наприклад, гарно повертати Promise. Або, у Swift в тому ж класі UNUserNotificationCenter
є інші функції, що приймають колбек. А про цю чомусь забули.
…Щоб виправити помилку, можна отримати перелік запланованих сповіщень функцією getPendingNotificationRequests
(до речі, яка приймає колбек!) та видалити конкретно їх. Або, в такому разі, можна вже й не видаляти, а пропускати при створенні.
07.02.2024
Людський фактор інтеграції
Те, що не можна забувати при виборі бази даних (або іншої підсистеми) - це те, стільки піде витрат на її інтеграцію. Особливо якщо база приходить на заміну рішенню, яке не тільки вже працює (може, з недоліками), але й вже інтегроване.
Інтеграція — це не тільки розгортування сервісу та забезпечення стабільної роботи. Інфраструктура навпаки, мабуть, найпростіша частина роботи, бо тут можна спиратись на зовнішню допомогу — зараз взагалі багато пропозицій з повним циклом обслуговування: заплатив і користуєшся.
Ні, я насамперед маю на увазі, що команда муситиме навчитися працювати з новою технологією. Від базових навичок до різних нюансів, підводних каменів і такого іншого. На це піде час. А поки отримаємо нові, небачені баги (які комусь доведеться зрозуміти та виправити.)
А друге, це переписування коду — деколи дуже суттєве. Навіть продукти, які мають ніби однаковий інтерфейс (наприклад, PostgreSQL та Redshift), при ближчому розгляді виявляють розбіжності — особливо в підходах. Там можна було робити запити в циклі — тут треба впровадити пакетний підхід. А ще десь зовсім транспонувати своє уявлення про дані.
Просто, часто чую як порівнюють технології за технічними властивостями (здатність до масштабування, ціну, можливості тощо), а про людський фактор забувають.
06.02.2024
Pkl - нова мова конфігураційних файлів від Apple
Нещодавно Apple випустила Pkl - мову програмування для генерації конфігураційних файлів. Взагалі ця тема мене займає, бо найкращі з моїх рішень — це шаблони jq
для JSON та YAML з шаблонами Go - я б щасливий був замінити. Тим паче побачив [відгук] про те, що Pkl - один з найкращих внутрішніх інструментів Apple.
Що ж воно таке? Сама мова чимсь нагадує за базовим синтаксисом Terraform (тобто HCL). В ній є як повноцінні засоби програмування (навіть класи імпорти), так і типізація. Єдине, що через семантику конфігурації трохи заплутано зрозуміти, де що (наприклад, обʼєкт може успадкувати інший обʼєкт.)
Все це потрібно, щоб на виході згенерувати або JSON, або YAML, або ще декілька форматів. Тобто ідея в тому, що наш скрипт Pkl буде створювати конфігурацію для іншої системи. А система можна навіть нічого про Pkl не знати, а спокійно споживати традиційні конфігураційні формати.
Звідки скрипт бере дані? Раджу роздивитись документацію про ресурси. Наприклад, зі змінних оточення. Або навіть з запитів HTTP (наприклад, з метаданих ECS). Додати до цього повну по Тюрінгу мову, та можна реально замінити мій генератор YAML на Go. (Або генератор JSON з jq на Баші, який ще складніше підтримувати.)
До речі, про інтеграцію з Go (одну з офіційних інтеграцій.) Інтеграція дозволяє завантажувати дані прямо з файлів Pkl у структури. Тобто використати Pkl як конфігураційну частину свого сервісу. Пропонується кодогенерація, проте вона генерує тільки структурні типи. Решта коду інтерпретується — тож інтеграція Pkl тягне за собою цілий інтерпретатор. Тут я поки не бачу сенсу (хоча може з досвідом зʼявиться.)
05.02.2024
Особливості ORM CoreData/SwiftData
Робота з ORM на платформах Apple дуже відрізняється від ActiveRecord, до якого я звик. Йдеться про CoreData - офіційну ORM від Apple, яка може синхронізуватися через iCloud, проміж інших переваг рішення “від першої сторони”.
-
CoreData - це справжня обʼєктна база даних. Це попри тому, що як сховище вона використовує SQLite. Але шар SQL повністю схований під абстракцією — та, до речі, має незручну в прямому доступі структуру, якщо вже знайти базу на диску та відкриті в клієнті SQLite.
-
А так, все, що ми робимо в CoreData, робиться через обʼєкти. Створення, видалення, асоціації. Атрибути обʼєктів не обмежені примітивними типами.
-
Для взаємодії з базою використовується контекст моделі. Це клас, який вміє як діставати з бази обʼєкти, так і зберігати. Але більше цікаво те, що контекст є ніби вікном в базу. Зберігання обʼєктів відбувається не по одному, в ActiveRecord, а всім контекстом. Аналогія така, що база — як шафа, а контекст — як робочий стіл: дістали зі шафи все потрібне, обробили, та повернули назад.
-
Через це CoreData виглядає більш… цілісно; код більше займається бізнес-логікою, ніж механікою роботи з базою. Це мені подобається. Втім на початку трохи лячно, бо будь-яка ненавмисна зміна до об’єкта буде збережена. Інтуїція працює зворотно від ActiveRecord: там треба не забути зберегти, а тут — не проґавити зайву зміну.
-
Якщо без оцінювальних суджень, то просто цікаво мати досвід з різними системами, щоб уявляти що взагалі можливо. Наприклад, думаю, на фронтенді підхід з контекстом був би досить зручним.
04.02.2024
Стохастичний тайм-трекінг на SwiftUI
Сьогодні наполовину для розваги, наполовину для діла спробував зробити на SwiftUI реалізацію одного особливого тайм-трекера. Особливий він тим, що, замість ручного ведення журналу чи автоматичного збору даних, трекер питає тебе, чим зараз займаєшся. А щоб це не було передбачуваним — інтервал запитів буде стохастичним, тобто випадковим. Інколи через пʼять хвилин, інколи через дві години. Випадковість моменту робить такий трекер неупередженим (якщо завжди заносити саме те, що робиш зараз.) На довгому проміжку часу статистика дає правдивий розподіл часу.
Це все не я придумав — йдеться про TagTime - зроблений чимало років тому розробниками сервісу Beeminder. TagTime має вигляд купки скриптів на Perl та, відповідно, здатний працювати тільки на десктопі.
А в мене ідея зробити застосунок для Apple Watch та скористатись повсюдністю годинника. У WatchOS є така цікава можливість. як long look для повідомлень — фактично цілий інтерактивний екран, доступний безпосередньо при перегляді сповіщення.
До того ж використання бази даних CloudKit та універсальності SwiftUI дозволяє легко зробити застосунок, в який можна заносити з десктопу, з телефону чи з годинника — де зручніше. А інтеграція Apple Health може автоматично логувати сон та тренування. Причому виглядає так, ніби все це не потребує багато зусиль, динамічного програмування і так далі. Ну, подивимось.
03.02.2024
JIT, чому Ruby повільний, та як він може стати швидшим
Читав нещодавно статтю про те, як реалізацію парсера на Ruby зробили швидше за С. Хотілося прокоментувати, що це в принципі значить. (Все це також стосується майже будь-якої динамічно типізованої мови, будь то JavaScript, Python, і так далі.)
Робота будь-якої програми більш-менш зводиться до операцій над памʼяттю. Саме тут й криється головна різниця між статичними та динамічними мовами: якщо статична мова, як С, під час компіляції знає форму даних в памʼяті, то динамічна, як Ruby, нічого про неї не знає.
Наприклад, в С структурований тип має зафіксований перелік атрибутів. А в Ruby ми можемо додати чи видалити атрибут в будь-який момент. Так само можемо взагалі передавати обʼєкти різних класів у функцію та користуватись duck typing.
А значить, при кожному зверненні до змінної чи атрибута Ruby мусить спочатку знайти його в памʼяті. Це і є повільний етап. (А не інтерпретація коду, бо код давно ніхто не інтерпретує, а компілює наперед в інструкції віртуальної машини.)
Втім, в реальних програмах структури даних не змінюються часто. Тож нормальний код на Ruby теж можна було б привʼязати до фіксованої форми даних та компілювати так само як і C. Саме це й робить JIT - виявляє фрагменти коду, що працюють зі стабільними структурами даних, та компілює їх.
Однак JIT пришвидшує тільки ті програми, де структури даних сталі, що залежить від стилю коду. Якщо динамічно утворювати нові атрибути в різних місцях логіки, то структура класу не буде сталою, а значить, JIT залучений не буде. Більше можна почитати в статті зверху.
02.02.2024
Масові операції в OpenSearch - практика
-
Масове оновлення скриптом — може, не така вже й хороша ідея. Працює це повільно. Що таке повільно — поясню: припустимо при типовій роботі база отримує 1000 нових документів на хвилину. Логічно, що в такому разі ресурси нашої бази розраховані на таку швидкість індексації (плюс стільки, за скільки ми переплачуємо для резерву). З цього випливає, що масове оновлення буде відбуватися десь з такою самою швидкістю — теж 1000 документів на хвилину. На мільйон документів піде 17 годин. І це оптимістична оцінка, бо оновлення старих документів повільніше, ніж утворення або оновлення свіжих. Я б радив придумати, як уникнути оновлення та спиратися на дані, які вже є.
-
Трансформації — теж не дуже швидка операція, бо під час трансформації OpenSearch виконує по одному пошуку на кожну комірку агрегації. На мільйонах документів та сотнях тисяч комірок виходить теж довго. Можливо, дні стовідсоткової завантаженості процесора.
-
З прозорістю в OpenSearch все складно. Зрозуміти, скільки залишилось трансформації, можна хіба за поточними результатами. А ще вона може зупинитись з помилкою. А дізнатись, що то була за помилка, можна тільки з журналу, бо ззовні видно тільки помилку “верхнього рівня”, типу “помилка трансформації” а в неї схована причина - “не вдалося виконати пошук”, а в неї першопричина - “запит має надто багато пунктів”. Все це можна побачити в журналі. А яке це має відношення до моєї трансформації (бо в ній запит не такий вже й складний!), та як виправити — то вже без форумів не розберешся.
-
Одним словом, за зручністю та… “міцністю” до PostgreSQL тут далеко. І це такий момент БД, який я не часто чую в обговореннях — якщо до PostgreSQL майже завжди можна ставитись до бастіону надійності, де нічого непередбачуваного не відбудеться, то OpenSearch все ж тільки ще один сервіс, зі своїми багами та сюрпризами.