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

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

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

01.12.2023

Дев-адвент, день 1: відновлюю роботу над додатком

🎄 Грудень — місяць відліків. Хочу використати цю оказію, щоб спонукати себе закінчити ще один проєкт: програму для відстежування дієти. Я користуюсь ним вже цілий рік, проте для публічного випуску не вистачає базового набору функцій. Спробую тридцятьма маленькими кроками його здобути. (Маленькими, бо доведеться це вмістити проміж іншими обовʼязками.)

Сьогодні зробив фічу, яка, в залежності від точки зору, може бути абсолютно зайвою або необхідною: додавання зважування на сьогодні. Нагадаю, що програма моя використовує як головне сховище базу Apple Health, тому вона цілком могла б бути тільки засобом малювання красивих графіків, без можливості редагування.

Проте, по-перше, це все одно трохи дивно, а по-друге, інтерфейс додавання ваги в Aple Health нагадує роботу з базою даних та його можна й покращити. Наприклад, вага за сьогодні мало відрізняється від ваги за вчора, та не обовʼязково вводити її наново вручну — можна зробити спінер.

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

Окреме питання — автоматичне оновлення даних — запорука сучасного реактивного інтерфейсу. Для того є механізм HKObserverQuery. То добре, та він в мене навіть працював, от тільки нові дані там не зʼявлялись. Сьогодні знайшов дуже просту причину: для запиту потрібно вказати діапазон дат, та як дату кінця я вказував поточну дату… яка, звісно, виключала будь-які нові дані. Коли дату кінця замінив на Date.distantFuture, то оновлення виправилось.


30.11.2023

Типи та інтерфейси в TypeScript - в чому різниця?

Деякий час мене турбує питання — як це так, що в TypeScript можна створювати однакові типи як словом type, так і interface? Згадав про це з довідника по TS, в якому відповіді немає, зате багато чого цікавого є.

// виглядає ніби однаково
type T = { foo: string };
interface I {
  bar: number;
}
// і ніби використовується однаково:
interface E extends T, I {
  baz: boolean;
}

Так в чому різниця? В інтернеті можна знайти функціональне пояснення, тобто яка різниця у використанні, але мені більше цікавий сенс існування обох синтаксисів. Розібрався.

Типи в TypeScript, взагалі, не мають назв. Будь-яке значення має тип; він або задається явно, або виводиться з оточення. Якщо ми хочемо привʼязати до типу назву, то вживаємо ключове слово type, і оголошуємо псевдонім типу. Псевдонімом можна назвати будь-який тип, незалежно від складності.

Інтерфейс — це один з різновидів типів. А саме, це тип, який описує обʼєкт. Для перевірки типів що безіменний тип, що названий, що інтерфейс — абсолютно еквівалентні. Для успадкування теж практично байдуже, хіба що безіменний тип не можна успадкувати.

Є лише одна функція, доступна тільки через інтерфейс, оголошений словом interface. Це розширення інтерфейсу. Якщо оголосити інтерфейс з однаковим імʼям два рази, то типи будуть обʼєднані в один. Такий трюк особливо важливий для бібліотек, бо традиційно в JavaScript заведено підсипати функціонал до обʼєктів, які вже існують — на кшталт window.jQuery. Про це я вже писав. Причому, типи так само можна розширювати, але не під тою самою назвою. Але в межах одного проєкту це не тільки прийнятно, а навпаки — бажано.

Виходить, що оголошення interface утворює точку розширення, в цьому його сенс. Оскільки іншої різниці реально немає, то я б використовував interface тільки саме з цим наголосом.


29.11.2023

Кодогенерація для доступу в базу на Go з sqlc

Ті проєкти, де я працюю з Go, мають певний мікросервісний характер. В тому сенсі, що від бази їм потрібний вузький набір даних — ніяких там адмінок, редакторів і так далі немає.

Отже, для доступу в базу використовуються стислі, вручну написані запити SQL, а також функції Go, які їх виконують та повертають результати. Це відносно непогано працює, та тягнути в проєкт цілий ORM точно поки не хотілося.

Але як тільки в такому проєкті зʼявляється трохи складніший сценарій взаємодії з базою — ну такий, що в Ruby on Rails став би запитом з декількома розгалуженими includes() - то рентабельність ручного написання стрімко зникає.

…Втім, ORM заради пʼятьох запитів все одно буде зайвим. Знайшов натомість чудове, ідіоматичне рішення для мілкої роботи з базою — генератор коду sqlc. Тут з запитів SQL генерується Go - причому й структури записів, й код взаємодії з базою. Результат генерації близький до того, що я пишу руками. Це просто ідеальний розвиток підходу.

Мені подобається, що sqlc залишає за мною можливість написати складний SQL, та не витрачати час на моделювання бази засобами Go. Бо на то є Ruby on Rails - там міграції повноцінні та взагалі з базою зручно працювати.


28.11.2023

Легко та просто — субʼєктивні явища

В списку читання натрапив на інтервʼю про те, як Дерек Сіверс використовує Ruby. Він згадав цікаву доповідь Річа Хікі десятилітньої давності - Simplicity Matters. І це наштовхнуло мене на цілу низку спогадів та думок, бо десять років тому я був в захваті від ідеї “простого коду”, але зараз вона здається мені дуже… недалекою?

Особливо зачепило, коли Дерек як приклад простого рішення навів переніс логіки додатка в функції PostgreSQL. За моїм досвідом, це ніяк не спрощує розробку або підтримку. Він і сам, напевно, здогадується підсвідомо, бо наступним чином каже, що зробив би з рішення опен-сорс, але на жаль, всі його додатки завʼязані на єдину базу даних, що навіть за означеннями Річа ніяк не “просто”.

Я не хочу сказати, що він не правий. Якщо його влаштовує власне рішення, з ним зручно працювати — то все чудово. Яка йому різниця до моєї оцінки? В компʼютерних технологіях немає нічого “простого”, тобто “односкладного” за природою. Абстракції над абстракціями над абстракціями. Простота — це лише влучний вибір того, які аспекти ми хочемо виділити. Це наш, субʼєктивний вибір.

Що значить субʼєктивність, практично? Те, що до простоти можна йти з двох боків. Або наближати предмет (код, архітектуру) до розуміння споживача, або ж споживача наближати до предмета. Придумати кращу абстракцію. Обрати вірну перспективу. Підібрати слова. І тоді люди скажуть — так, це просте рішення!

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


27.11.2023

JSON - універсальний формат даних для всіх випадків

Для серіалізації структур даних у рядок є одне правильне рішення: JSON. Є кілька випадків, коли це не найкращий вибір, а у всіх інших можна два рази не думати. (Раніше писав про HTTP/JSON).

Серед переваг JSON - легкість в розумінні людиною, висока архівна якість, наявність якісних, ретельно оптимізованих та перевірених (це дуже важливо!) бібліотек для будь-якого середовища. В роботі багато дилем, але для серіалізації можна спокійно брати JSON.


26.11.2023

Програмування типами в TypeScript

TypeScript - явище цікаве тим, що для компіляції та роботи додатка жодний тип не потрібний. Вони всі — для нас, для програмістів. Тому при утворенні типів потрібно думати не про те, щоб не було нічого “червоного”, а про те, як за допомогою типів висловити логіку програми. При цьому якщо код описує поведінку програми, то типи описують стани в які вона може потрапить.

В JavaScript широко поширена практика мати в обʼєкті поля на всі випадки життя, а про наявність того чи іншого поля дізнаватись по значенню поля-мітки. Можна реалізувати це в TypeScript буквально — оголосити всі поля опціональними. Або ще гірше — навіть опціональними не робити. Ми здобудемо компіляцію без помилок… але ці типи нічого не додають до правильності програми.

Краще взяти з моїх улюблених інструментів — мічене об’єднання (discriminated union.) Як приклад — історія про різні форми внеску. Міченими обʼєднаннями можна розібрати тип на всі можливі стани, в яких він може знаходитись. З одного боку, це нас примусить додати перевірок в деяких місцях, де нам здається, стан і так зрозумілий. З іншого, надає “скелет”, на який можна натягнути код, котрий буде перетворювати стани, або обмежуватись одним з них.


25.11.2023

Нові стандарти UUID: v6, v7, v8

Дізнався, що з UUID, про які я кілька разів писав, є нові розробки, а саме, чернетка RFC, яка додає ще три стандарти, які виправляють, в тому числі й ті проблеми, про які я згадував.

Хоч стандарт ще не затверджено, але генератори вже існують для Ruby, Golang, та й взагалі, побудувати та прочитати UUIDv7 набагато легше. Їх можна почати використовувати вже зараз, та якщо вже потрібно мати UUID, то я б радив саме цю версію.

Додатково: чому UUIDv1 не можна сортувати хронологічно, хоч вони й містять дату? А тому, що дата в UUIDv1 має порядок “менший біт на початку”.


24.11.2023

Зробив інструмент для аналізу графу ресурсів Terraform

Сьогодні ставала задача — знайти, де саме використовуються деякі ресурси в Терраформі. Задача ніби нескладна, та з нею порається Terraform Language Server в доповненні для VS Code. Проте є ситуації, коли цей пошук нетривіальний та доповнення його недовиконує. Наприклад, коли залежність проходить через модулі, та не просто модулі, а й ресурси output та var. Зʼявилася ідея оптимізувати, тож написав маленький додаток.

Пошук інструментів, що існують, гарних результатів теж не дав. Зате знайшов корисну команду terraform graph, яка генерує граф ресурсів на мові DOT. Сам по собі граф теж не підходить, бо коли ресурсів тисяча, то нічого наочного в графі немає. Зате граф — хороша основа для машинного аналізу.

…В контексті Terraform цей граф це також добре тому, що він переносний, тобто не привʼязаний до конфігурації, доступів, та іншого, а також тому, що він не містить жодних секретів (хіба що саму структуру ресурсів.)

Оскільки вивчення графу — це операція не з одного кроку, вирішив зробити не утиліту командного рядка, а графічну, а саме, звісно, клієнтський вебдодаток. Для розбору файлів DOT знайшов пакет ts-graphviz. А решта - Svelte (який вже точно став моїм вибором №1 для простих додатків.)

Зокрема, я винайшов рішення для var та інших проміжних ресурсів. В додатку є особливий режим, який робить їх “прозорими” та відображає успадковані залежності. Також, оскільки граф не містить інформацію про розташування ресурсів в коді, то додав можливість скопіювати рядок оголошення ресурсу, щоб можна було легко знайти його в редакторі.

Сам додаток TFDig та його код. Якість поки на рівні “пару годин писав”, ви мої альфа-тестери. :)


23.11.2023

Історія однієї фічі: коректування накопичень

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

GoalDeposit = { isCorrection: true } | { expenseID: string }; // та інші поля


22.11.2023

Правила безпеки для Firebase Firestore

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

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

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

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

В цілому, правила це зручний інструмент, та мені подобається їхня завершеність. Що не подобається: правила абсолютно непрозорі для клієнтів — клієнт просто отримує повідомлення “бракує дозволу” як на помилки валідації, так і взагалі на логічні помилки в правилах (наприклад, звертання до атрибута, якого не існує.)

Щоб знайти справжню причину, потрібно дивитися в логи Firestore та зіставляти їх з діями клієнтів. Це робити незручно. Тому я й наполягав на написанні вичерпних тестів на правила безпеки — тести, оскільки працюють з емулятором, мають доступ до справжніх помилок.