Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
04.12.2023
Дев-адвент 4: масштаб
Сьогодні працював з масштабом графіка. Це один з найважливіших моментів того, як зробити програму кращою за табличку в Екселі.
Якщо нічого не придумувати, то, напевно, графік буде сталої висоти (може, у весь екран?), а дані масштабуватимуться, щоб його заповнити. Математично це вірно, та за ціною ділення завжди можна чітко зрозуміти, що відбувається. Але наочно ми можемо побачити тільки характер змін, але ніяке не їх розмір, а відповідно, не значення.
Щоб дізнатись обсяг змін, доведеться долучити наше аналітичне мислення… Це погано, бо графік має бути зрозумілим на рівні відчуттів. Відкрив — побачив — зрадів (чи засмутився.)
Або ще більш типовий випадок — поки вага на місці, графік має бути прямим. Як відомо, наївний графік підсилюватиме навіть найменші зміни, щоб зайняти весь діапазон шкали.
Щоб уникнути цих проблем та зробити графік наочним, масштаб мусить бути сталим, а висота графіка змінюватись відповідно до діапазону даних.
Далі — справа техніки — обрати вірний масштаб, тобто коефіцієнт “пікселів на кілограм”. Тут спирався на вигляд, бо ніяких абсолютних значень, до яких можна було б привʼязатися, немає. За коефіцієнтом обчислюю висоту графіку.
PS. Найгірше у розробці програм для iPhone - це симулятор, який завжди витрачає 100% процесора — вже роками. Але я знайшов, як від цього врятуватись: замість симулятора запускати програми на справжньому телефоні — практично це нічим не відрізняється.
03.12.2023
Дев-адвент 3: віджет
Iнтервальне голодування - перевірений спосіб контролю ваги. Якщо одним реченням, то “їсти дозволено тільки протягом вікна у 8 годин на добу.” Давно хотів впровадити віджет для iOS, який показував би поточний інтервал. Що і зробив сьогодні.
Вже писав, як взагалі працюють віджети. Але у SwiftUI я ще віджетів не робив — тільки через React Native. Ну, точніше, сам віджет був все одно на SwiftUI. Але дані для віджета я будував в головній програмі на JavaScript (бо база та все інше було там), та передавав через UserDefaults - що взагалі було гаком, бо UserDefaults
призначений для налаштувань.
Коли вся програма на Swift, то так робити немає потреби. Віджет робиться зі спільного коду програми, та не має технічних обмежень на те, що дозволено робити для підготовки даних. Взагалі, чому я кажу “підготувати дані для віджета”? Тому що віджет на iOS складається з часової послідовності простих обʼєктів-конфігурацій та компоненти на SwiftUI, який робить з поточної конфігурації статичний вид.
Важливий момент 1: ми маємо заздалегідь запланувати, в який час та який вигляд буде приймати віджет. Немає такого, що можна сходити в базу чи в інтернет та оновити дані — таке оновлення відбувається рідко та за рішенням ОС (та повністю заміняє всю послідовність). В моєму випадку, це влаштовує, бо віджет показуватиме стан сталого розкладу.
Важливий момент 2: для кожної нової конфігурації віджет малюється тільки один раз, буквально. Після цього ОС бере з нього “знімок”, який вже не зміниться. Як мінімум це значить, що всі дані мають бути підготовані заздалегідь — ніякого “стану завантаження” нам не дано, як і ніяких ефектів після промальовки. В мене була модель, яка обчислювала свій стан в асинхронному блоці. Ось її довелось переробити на синхронне обчислення.
З приємного: архітектура віджетів однакова для iOS, watchOS, та macOS, а також для всіх різновидів віджетів, таких як віджети екрана блокування. Тому і навички, і код дійсно кросплатформні.
02.12.2023
Дев-адвент 2: значок
Програму iOS можна розробляти без значка аж до випуску в App Store. Але, звісно, це не дуже зручно, особливо якщо таких програм декілька, бо всі вони матимуть однаковий порожній значок. Отже, взявся й сьогодні зробив нормальний значок.
Загальні міркування: якщо навичок ілюстрації немає, допоможе мінімалізм. Взагалі, якщо роздивитися значки програм для айфона, більшість з них складаються лише з двох-пʼяти компонентів та стільки ж кольорів. Тож будь-який задум доведеться спрощувати.
Нагадаю, що мій додаток побудований навколо цікавого графіку ваги. Хотілося, щоб саме графік й був зображений на значку. А точніше, його особливість — зелені та червоні крапки, що позначають прогрес. Спочатку так і хотів — зелену та червону крапку. Але у червоних крапок значення негативне, тож краще тільки зелені.
Зелені крапки тягнуть графік вниз… така й була перша концепція, яку я довів до значка. Треба було щось поставити на тло. Для креслень є історично-стереотипна синька, тож саме її й поставив. v1
чомусь дуже нагадувала про ПриватБанк. А ще в маленькому розмірі деталей на ній було забагато, вони губилися.
Тоді пішов на спрощення. Головне — це зелені крапки — тож нехай вони тільки й залишаються. Також і “нитки”, які тягнуть графік донизу. Крапки прямують вниз — є прогрес! Щоб залишилась згадка про графік, додав координатне перехрестя. v2
реально виглядає, як позитивний шматочок графіка.
От тільки сіре тло графіка, яке прийнятно виглядало всередині додатка, на значку виглядає сумно та… сіро. Спробував повернутись до синьої теми. Трохи підправив кольори та виявилось, що графік на синьому тлі має чудовий вигляд! На v3
вирішив зупинитись. Можливо, до випуску ще додам текстуру або принаймні градієнт — якщо придивитись, то більшість значків мають ледве помітний градієнт.
Графік у додатку теж буде синій — це й візуально веселіше, і натякає на інженерну орієнтованість додатка.
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 надлишковий; чого варти тільки обовʼязкові лапки навколо ключів. Також немає стандартної підтримки коментарів — життєво необхідної для документів функції. Зате є інше певне рішення: YAML. (Чи знаєте ви, що стандарт YAML є надбудовою над JSON?)
-
Для двійкових даних. В JSON їх доведеться кодувати — скоріше за все в Base64. Це зайві витрати як по розміру (хоча Base64 чудово стискається в GZip), так і по часу (а якщо стискати — то ще більше.) На великому масштабі має сенс тримати двійкові дані окремо. З файлами досить поширено явище sidecar file. У HTTP, звісно, є
multipart
. В базах даних — поляblob
. -
Для “високонавантажених” вузлів варто переходити до форматів, де структура задається окремо - Protobuf або Cap’n’Proto. А для масивів даних - Parquet. Обовʼязково треба памʼятати, що відокремлення схеми робить дані менш прозорими та ускладнює підтримку. Тому “високу навантаженість” треба починати надзвичайно високо. Якщо сервіс всередині робить виклики AWS SDK по HTTP/JSON, то навряд чи йому потрібна gRPC. А для зберігання масиви JSON, стиснуті у GZip, практично не додають зайвого розміру, оскільки GZip чудово стискає повторювані рядки, якими є ключі та розмітка JSON.
Серед переваг JSON - легкість в розумінні людиною, висока архівна якість, наявність якісних, ретельно оптимізованих та перевірених (це дуже важливо!) бібліотек для будь-якого середовища. В роботі багато дилем, але для серіалізації можна спокійно брати JSON.
26.11.2023
Програмування типами в TypeScript
TypeScript - явище цікаве тим, що для компіляції та роботи додатка жодний тип не потрібний. Вони всі — для нас, для програмістів. Тому при утворенні типів потрібно думати не про те, щоб не було нічого “червоного”, а про те, як за допомогою типів висловити логіку програми. При цьому якщо код описує поведінку програми, то типи описують стани в які вона може потрапить.
В JavaScript широко поширена практика мати в обʼєкті поля на всі випадки життя, а про наявність того чи іншого поля дізнаватись по значенню поля-мітки. Можна реалізувати це в TypeScript буквально — оголосити всі поля опціональними. Або ще гірше — навіть опціональними не робити. Ми здобудемо компіляцію без помилок… але ці типи нічого не додають до правильності програми.
Краще взяти з моїх улюблених інструментів — мічене об’єднання (discriminated union
.) Як приклад — історія про різні форми внеску. Міченими обʼєднаннями можна розібрати тип на всі можливі стани, в яких він може знаходитись. З одного боку, це нас примусить додати перевірок в деяких місцях, де нам здається, стан і так зрозумілий. З іншого, надає “скелет”, на який можна натягнути код, котрий буде перетворювати стани, або обмежуватись одним з них.
25.11.2023
Нові стандарти UUID: v6, v7, v8
Дізнався, що з UUID, про які я кілька разів писав, є нові розробки, а саме, чернетка RFC, яка додає ще три стандарти, які виправляють, в тому числі й ті проблеми, про які я згадував.
-
UUIDv6 залишає розташування полів з UUIDv1, але виправляє дві головні проблеми: UUIDv6 можливо сортувати за датою та замість використання MAC адреси, ідентифікатор вузла обирається випадково.
-
UUIDv7 позбувається решти незручних та застарілих домовленостей. В якості таймстемпу береться звичайний час Unix (бо в UUIDv1 був свій, особливий стандарт кодування часу.) А решта бітів заповнюється випадковими бітами.
-
UUIDv8 - це абстракція, яка дозволяє нам вигадати будь-який метод побудови ідентифікатора — він тільки мусить мати правильні біти версії.
Хоч стандарт ще не затверджено, але генератори вже існують для Ruby, Golang, та й взагалі, побудувати та прочитати UUIDv7 набагато легше. Їх можна почати використовувати вже зараз, та якщо вже потрібно мати UUID, то я б радив саме цю версію.
Додатково: чому UUIDv1 не можна сортувати хронологічно, хоч вони й містять дату? А тому, що дата в UUIDv1 має порядок “менший біт на початку”.