Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
- ActiveRecord
- AmazonRedshift
- API
- AWS
- AWSLambda
- CGO
- Chezmoi
- CI
- Cloudflare
- CloudflarePages
- CssParser
- CтохастичнийТаймтрекер
- DNS
- Docker
- Dotfiles
- Fly.io
- GCP
- Git
- GitHub
- Go
- Golang
- GTD
- HomeAssistant
- Hugo
- JavaScript
- JSON
- Kafka
- MacOS
- Markdown
- Mastodon
- Obsidian
- ObsidianCanvas
- OmniWOPE
- OpenSearch
- Oura
- Ping
- Plausible
- ReactNative
- Redis
- RSS
- Ruby
- RubyOnRails
- Sintra
- SMTP
- SQL
- SQLite
- Svelte
- Swift
- SwiftData
- SwiftUI
- Telegram
- Terraform
- TLS
- TypeScript
- Vercel
- VPN
- WeightPlot
- WordPress
- XCode
- Адвент2024
- Бази Даних
- Безпека
- Вебтехнології
- ВолюІнформації
- Гаджети
- ДизайнМовПрограмування
- Ігри
- Інструменти
- ІнтеграційніТести
- Кава
- КеруваннняЗадачами
- Кодогенерація
- Криптографія
- Локалізація
- Маркетинг
- МетаПост
- МоїПроєкти
- Оптимізація
- ОсновиІнтернетБезпеки
- ПомічникШІ
- Програмування
- Продуктивність
- Проєкти
- Проза
- РДУГ
- Рівночасність
- РобочийКомп
- Розробка
- РозумнийБудинок
- СинПопросивПриготувати
- Сон
- СтохастичнийТаймтрекер
- ХмарніТехнології
25.05.2025
Оновлення дати у SwiftUI
В моєму стохастичному таймтрекері Ping чимало логіки залежить від поточної дати. Почати хоча б з часограми на головному екрані. Це ставить цікаву задачу: як підтримувати дату актуальною? Календарну дату.
Як я дізнався, на платформах Apple є відразу декілька способів стежити за датою — залежно від потреб. Але я був здивований, що один з них — це NSCalendarDayChangedNotification - зроблений саме для мене. Це сповіщення покриває всі випадки зміни дати, яких насправді більше, ніж очікуєш — наприклад, зміна часового поясу. А головне, що не треба вигадувати власної логіки, як колись довелося для React.
Отже, отримати сповіщення від системи — дуже легко. Поширити ці дані в застосунку в сучасному SwiftUI теж легко — завдяки фреймворку Observation. Це проста, але дієва магія. Я створив одинак — джерело дати (тобто просто клас із властивостями currentDate
та ще currentStartOfWeek
), помітив його @Observable
, та тепер кожного разу, як я використовую його у SwiftUI, відбувається підписка на зміни. Байдуже, чи роблю я виклик прямо з компоненти, або десь в глибині коду. Observation лише зберігає відносини між компонентою та всіма атрибутами @Observable
, які були використані.
Оце й усе. Пройшовся по коду, замінив явне отримання дати на звернення до нового DateSource
, та застосунок став помітно уважнішим до часу.
В продовження теми дат, також випускаю мікрооновлення для Reminders2JSON - тепер файл експорту містить в назві поточну дату — бо так зручніше для резервного копіювання. Та іконка теж змінилася.
24.05.2025
Google Workload Identity Federation... на ECS
Понад рік тому я писав про авторизацію з AWS до GCP. ЇЇ не так важко налаштувати та працює вона відмінно. Без жодного секрету, тільки з правильним файлом credentials.json
. Але нещодавно дізнався, що є нюанс: не у всіх середовищах.
Всередині клієнту Google потрібні ключі доступу до AWS. (Щоб побудувати підпис — далі вони не йдуть.) В простому випадку код авторизації (ось на Go) шукає ключі в змінних оточення.
Якщо мовити про виконання в AWS, то ключі в оточенні знайдеш тільки в AWS Lambda. Тому що ключі ці завжди тимчасові. Та тільки Lambda теж настільки ефемерна, що її час існування не перебільшує терміну дії ключів.
А в інших місцях — на EC2 та на Fargate - є спеціальні HTTP API для отримання тимчасових ключів. Різні. Та клієнт Google вміє читати ключі тільки з EC2 Metadata API. Буквально — в їхній реалізації немає згадки про Fargate.
(До речі: намагався знайти офіційне посилання на документацію по цьому Fargate API, та знайшов тільки згадку в документації по SDK. Тобто технічно цей API… незадокументований? Таємний? Це багато пояснює.)
(Взагалі, а чому вони не використовують для цього AWS SDK, в якому вже реалізовані універсальні методи отримання ключів? Включаючи, звісно, читання з цього самого API? Політика?)
Отже, як вийти з цих обставин? Ну я знайшов дикий, але дієвий та універсальний спосіб: підробити EC2 Metadata API, а точніше, його частину, до якої звертається клієнт Google. На щастя, в credentials.json
є посилання на той API, отже, його можна підмінити на наш власний. А власному залишається просто читати тимчасові ключі з Fargate API та віддавати за EC2 API - суть авторизації абсолютно не змінюється. Класичний патерн адаптера. (Але це не точно).
23.05.2025
Залежності — це проблема інтернету
Знаєте, всі ці труднощі із величезними наборами залежностей, а також з розвʼязування їхніх версій — це дуже сучасна проблема. Може тут здаватися, що JS чи інші інтерпретовані мови “завдяки дилетантам отримали таку погану систему”, але насправді це останнє слово прогресу.
Без інтернету проблеми немає тому, що тобі нема звідки взяти нові версії. Люди жили із тим, що є у них в системі, та оновлювали разом із нею, раз на декілька років. Додаткові бібліотеки встановлювалися вручну, з диска, ну або з інтернету, тільки як звичайний архів — і так само вручну оновлювалися.
З одного боку, це люто обмежує — нема в тебе заголовків OpenGL, то й сиди без своєї графіки! З іншого, ті бібліотеки, що були, готувалися із найширшою сумісністю. (А головне, їх було мало, то й проблем було ще менше.)
Я памʼятаю як тільки пробував Ruby on Rails десь у 2006. Було потрібно встановити його з усіма залежностями з RubyGems. Проте з моїм діалапом gem install
, який ходить в інтернет та сам щось стягує, постійно відвалювався. Довелося знайти, як завантажити файли вручну, а точніше, менеджером завантажувань, здатним відновити роботу після відмови (ще й таке було.)
Але головне, що на той час RubyGems це було надсучасно. З PHP в мене просто в проєкті сиділи копії тих кількох бібліотек, які не входили в системний набір. Яка версія була, така й була. Чи була вона сумісною? Якщо працює — то так. (До речі, як збирати щось на C, досі бачиш checking for foo... yes
. ) Іронія в тому, що й зараз версії не гарантують сумісності, а тільки обіцяють її.
Так що яка тут мораль… Що мати багато бібліотек — це надбання прогресу! Та надає безмежний потенціал. Для проблем, але також для творчості. А з версіями ми щось придумаємо.
22.05.2025
Чим система залежностей Go гірша за node_modules?
Тека з модулями Node.js - node_modules
- є на цей час біблейським прикладом надмірної купи залежностей. Але я б не сказав, що ця проблема обмежується тільки Node.js чи тільки JS. Поки подивімося на Go.
В Go пакети завжди імпортуються за URL: import "github.com/leonid-shevtsov/omniwope"
. Що сидить за цим URL? Який формат файлу мають пакети Go? Ооо… Пакет Go завжди є репозиторієм системи контролю версій. (Звісно, найчастіше Git, але не обовʼязково.) Таким чином, мова Go не має ніякого “пакетного формату файлів”, а просто завантажує зміст, як це робить git clone
. Та ще й перекладає питання версіювання на репозиторій.
Це все з одного боку спрощує життя. В Go немає проблеми сквотингу (хоча дехто відхопив собі смачне імʼя github.com/lib/pq, наприклад.) Немає централізованого сервісу — можна хоч на власному домені публікувати. Питання авторизації та приватних пакетів теж легко розвʼязується через репозиторій. Через все це я люблю Go modules
.
(Ще цікавий момент: мажорна версія змінює URL пакету. Хоча це тільки домовленість, та я багато разів зустрічав несумісні зміни з тією ж мажорною версією.)
А тепер, величезний недолік. Змістом пакета є зміст репозиторію. Немає ніякої можливості сказати, що деякі файли не потрібні. Коли ти встановлюєш пакет Go, то тягнеш всю-всю документацію, ілюстрації, тестові файли — просто все. Наприклад: є github.com/pierrec/lz4/v4 із 40 Мб тестових архівів. І всі вони будуть сидіти в тебе на диску, в кеші CI тощо.
Те ж саме стосується й транзитивних залежностей. Та що мене особливо дратує — навіть якщо транзитивна залежність потрібна тільки для оголошення типу, все одно всі її файли будуть завантажені в Go modules. Ну а як інакше, коли тип є частиною коду? Цього можна частково уникнути, якщо оголошувати інтерфейси на боці споживача — але все одно вони часто спираються на конкретні типи. Або, пакети, потрібні для тестів транзитивних залежностей, теж завантажуються. Наприклад, пакет testfixtures підтримує БД Clickhouse. Мені вона зовсім не потрібна — та сам пакет не має на неї залежність. Але його тести — мають! Та оскільки тест сидить в тому ж самому репозиторії, то пакет Clickhouse стає транзитивною залежністю вже мого застосунку. І це тільки один приклад з безлічі.
Заради справедливості: цього напливу залежностей можна уникнути, якщо робити go mod vendor
- тоді копіюється тільки код, потрібний для головного пакета. Але насправді це допомагає тільки якщо у вас цей vendor
ще й доданий до Git. Бо інакше будете тримати локально як vendor
, так і окремо всю купу — щоб його побудувати.
21.05.2025
Тести, дебаг та обережність
Промучився кілька днів із випадковими падіннями тестів. Проєкт великий, незнайомий, а я почав додавати в нього інтеграційні тести з базою. Ну й пішло… один тест — проходить, всі тести — десь плутають стан. Підозр було напрочуд багато.
Спочатку я рухався дуже обережно — рядочок тут, перевірочка там. Щоб нічого не поламати. Згодом збагнув, що поки я запускаю тести локально, я нічого ніколи не поламаю, та можна робити будь-які зухвалі зміни, аби швидше знайти причину.
Підключення до бази зберігалося у внутрішньому пулі. Намагався якось підчищати його, щоб виключити можливе забруднення. Але щоб дійсно перевірити, чи це воно, можна вирізати весь пул з коду та повертати завжди нове підключення.
В коді було багато горутін, які не повідомляли про своє завершення. (Це не такий вже й поганий патерн, коли на кінці виклику ти залишаєш частину роботи на рівночасне виконання.) Чи можуть вони псувати стан для інших тестів? Та додай між тестами очікування у 5 секунд, щоб всі горутіни точно завершились.
Незрозуміло було, де саме відбувається забруднення “чистого середовища”. То й розстав логів хоч після кожного рядка.
А наприкінці виявилось, що я забув, що go test ./...
запускає пакети тестів паралельно. І тільки з опцією -p 1
- ні. Так що на цей раз моя проблема мала дуже просте рішення — яке я не бачив через власні переконання. (І ще через купу інших можливих причин, які довелося відкинути.) 😅
20.05.2025
Асемблер та машинний код
В продовження поста про мови, якими пишуться інші мови, все ж залишається питання: а що в самому низу? Які інструкції розуміє компʼютер — що таке машинний код? Та чим він відрізняється від асемблера (мови програмування)?
Це взагалі цікаво, бо машинний код не складається з “найпростіших” інструкцій, тобто він не є “в самому низу” (хоча що це взагалі означає?) Зокрема, інструкції Intel x86 взагалі можуть містити в себе цілу підпрограму, як-от мені колись допомогла інструкція POPCNT для підрахунку бітів. І вона не найскладніша. Але — машинний код це найнижчий рівень, на який може залізти програміст, тому не більше й не менше, він розмежовує програмне та “апаратне”.
Втім, головною різницею між машинним кодом та мовами програмування є те, що машинний код є мовою інструкцій, а не рішень. Та його зручності спрямовані на керування процесором, а не на спрощення написання програми. Тому машинний код позбавлений деяких базових можливостей.
(Те, що він є “двійковим”, мало що змінює. В компʼютері все “двійкове”. Як є редактор для тексту, так само може бути редактор для машинного коду. І є — наприклад, ImHex це вміє.)
Також в машинному коді немає коментарів, та кожен хто колись виправляв проблеми з package.json
знає, наскільки важко без них. Ну але кожен знає, що це теж незручно, але можливо.
Але чого в машинному коді дійсно не вистачає, це імен. Та ти недооцінюєш, наскільки імена важливі в програмуванні! Бо в машинному коді всі посилання як на дані (змінні), так і на код (розгалуження, цикли, процедури) відбуваються за адресою. А адреса — річ не стала. Захотіли додати інструкцію до гілки — та вже всі адреси нижче треба зсувати. Додали змінну — треба знайти, куди її пристроїти. До того ж — ніяких назв, а одні чисельні адреси (ще й інколи — абсолютні, інколи — відносні). Все це — надзвичайно ускладнює розробку.
Тобто в цілому машинний код можна писати, але гарно б доповнити можливістю призначати імена, а також залишати коментарі. Вітаю, ми винайшли найпростішу мову програмування - асемблер!
19.05.2025
Perplexity - пошук в інтернеті, яким він повинен бути
Давайте відразу зазначимо: пошук в інтернеті з самого початку є чорним ящиком без чітко заданої поведінки. Тому пошук із соусом LLM не є чимсь що перегортає світ чи спотворює чистоту оригіналу. А тепер: Perplexity.
Perplexity це агент для пошуку в інтернеті. Коли ти задаєш йому запит, спочатку він генерує не один, а відразу декілька запитів до пошукових систем. Робить їх. Читає результати. Та видає зведену відповідь, в якій на кожне твердження є посилання на оригінал.
Виходить такий сендвіч з LLM: генерація запитів, пошук, зведення результатів. Це стає корисніше в подальших запитах — бо звісно, перший запит стає ниткою. Тому додаткові запитання можна вже ставити в контексті відповіді, а не ліпити пошук наново. Як старанна LLM, Perplexity згадає важливі параметри та додасть їх до запиту до пошукової системи.
Що мені подобається: такий підхід відтискає воду. Якщо традиційний пошук ще більш-менш легко шукати компʼютерні теми (англійською!), то побутові теми настільки сповнені SEO-води, що треба перебрати десятки сторінок, щоб щось зрозуміти. Perplexity це робить за мене. Також подобається деталізувати результати та заглиблюватися в тему. Мабуть, можна сказати, що найцікавіше починається в глибині.
Наприклад, вчора почав з того, які лампи потрібні для вирощування зелені, а потім перейшов до необхідної на мою площу потужності, відстані кріплення та власне штанг для світильників (бо грядка вертикальна.) А потім зміг переконатися, що ці штанги відносно нескладно зробити власноруч, які потрібні для того кріплення та де їх купити. Все це може за годину досліджень.
Окрім цього зрушив вчора ще може з десять проєктів завдяки тому, що міг швидко почати дослідження та отримати перші результати. Втім, треба бути обережним — захопитися неважливою, але цікавою темою теж стало легше.
18.05.2025
ШІ агенти моторошно нагадують хлопця з Memento
🖋️ “Памʼятай” - один з моїх фільмів на 10/10. Недотично до теми, раджу подивитись. Без спойлерів: головний герой стрічки втратив можливість запамʼятовувати. Натомість йому доводиться вручну “записувати памʼять на зовнішній носій.” Що з цього вийшло? Далі самі дивіться!
Вчора я прочитав допис Авді Грімма “Агенти не джуніори, а шпигуни з амнезією”, де він порівнює агентів с Джейсоном Борном. Теж гарна стрічка! Але аналогія не ідеальна, бо пан Борн (мʼякий спойлер!) з часом відновлює свою памʼять. А ШІ ні.
Поп наука завжди існує в стані “ми вже все-все про це знаємо, ну може не все, а 95%.” Так і з ШІ: “вони вже в принципі не відрізняються від людського мозку — хіба що масштаб треба збільшити”.
Втім, на поточному етапі розвитку в ШІ немає довгострокової памʼяті в тому сенсі, в якому вона є в людини. А саме: всю свою “памʼять” ШІ отримує через контекст. На кожний запит ШІ читає весь контекст наново та генерує відповідь. Автоматичної памʼяті поки ніхто не випустив, але ми маємо доступу до твору запитів та додавання правил агентів. Це квазіпамʼять — всі ці інструкції просто потрапляють в контекст. Ось, до речі, цікава стаття про базовий запит Claude та який він величезний.
В чому проблема памʼяті через контекст? Вона не є загальною — а працює настільки гарно, наскільки написали запит чи правило. А головне, це все, що запамʼятає ШІ. Та якщо нічого не записали — він нічого й не запамʼятає.
Натомість людська — знайома нам — памʼять фізично змінює нашу нейронну мережу. “Нейронна мережа” ШІ вчиться в тисячі разів повільніше ніж людина. Буквально — для зміни коефіцієнтів мережі потрібні тисячі, якщо не мільйони прикладів. Тобто це практично неможливо ні для яких життєвих потреб. Вся відповідальність за навчання перекладається на оператора — в досить прямому сенсі, це ми вчимося використовувати ШІ та витягувати з нього більш корисні результати.
На цей день все, на що можна розраховувати з ШІ — це попросити / найняти / спитати / покохати (?!!) ввічливого пана з Memento - з татухами, IQ 150 та антероградною амнезією.
17.05.2025
Якою мовою пишуться мови програмування?
Отримав (від дружини!) таке цікаве запитання. Бо, власне, чи є тут якась драбина мов, та якщо є — то де вона закінчується?
Драбина якщо є, то дуже коротка. По-перше, в ідеалі засоби виконання мови пишуться тою ж самою мовою. В цього очевидна перевага: щоб розвивати мову, не потрібно знати іншу.
Втім, як вийти з цього парадоксу курки та яйця? Тут зазначу, що є два різновиди мов. Компільовані мови перетворюються в машинний код, який вже не залежить від самої мови. А значить, достатньо першу версію компілятора написати іншою мовою, а згодом, маючи скомпільований компілятор, переписати його “рідною”. Так було з С, C++, Go, Rust, Haskell тощо.
Інтерпретовані мови завжди виконуються в контексті іншої програми — інтерпретатора. Тому інтерпретатор доведеться написати компільованою мовою — за власні підтяжки себе не витягнеш. Наприклад, Ruby та Python написані на C - матері всіх мов. Але більша частина інструментів та бібліотек все одно пишеться рідною мовою.
Є ще мови з віртуальною машиною — це дещо посередині. Їхній код компілюється не в машинний, а в код віртуальної машини (байт-код). Та компілятор цей пишеться рідною мовою. А сама віртуальна машина — повноцінною компільованою мовою. Наприклад, машини Erlang та Java написані знову на C.
Отже, виходить так: зрілі компільовані мови написані самі собою, а інтерпретовані — зазвичай на C/C++. Мови із віртуальною машиною - 50 на 50.
16.05.2025
Reminders2JSON, а також ШІ як клей
Отже, вчора в коментарях зʼясували, що Apple Reminders технічно можна було б вивантажити в JSON через фреймворк EventKit. А сьогодні я вирішив, що це гарний проєкт для того, щоб погратися з ШІ, та майже витягнув його в App Store (!)
Чому гарний для ШІ? Бо я гарно розумію, що треба зробити, але це все одно багато роботи. Це проєкт-“клей”, тобто такий, де потрібно поєднати готові частини в спеціалізоване рішення. Я люблю цитувати статтю You can’t buy integration, але — здається, ШІ чудово виконує “склейку”.
В цьому випадку, я спочатку згенерував функції читання з EventKit та генерації JSON, потім — окремо — експорт будильників та графіків. Потім конфігурацію командного рядка. Потім конвертував застосунок командного рядка у SwiftUI (!) Створив набір значків потрібного розміру. Додав файли Fastlane для публікації. Та більшу частину всього цього зробив Claude.
Але на “інші 80%” пішло набагато більше часу. Наприклад, спочатку я хотів консольну утиліту. Але в моделі безпеки macOS вони не можуть отримати дозвіл на читання Reminders. (Бо ця модель розрахована на “товсті” застосунки, із всілякими підписами.) Та мені так і не вдалося це побороти, хоча наче можливість є — невідомо тільки як до неї прийти. Локально в XCode наче працює, але на іншій машині абсолютно відмовляється.
Промучився пару годин та вирішив зробити очевидне рішення — перетворити на графічний застосунок. Та тут ШІ впорався не тільки зі створенням інтерфейсу, але й, наприклад, із відкриттям діалогу для збереження файлу (ще одна склейка).
Сам застосунок дуже нудний — бере нагадування, зберігає в JSON. А, ще деякі атрибути нагадувань недоступні з EventKit - наприклад, групи списків. Ну то вже таке. Через пару днів має зʼявитися в App Store. А ось вже й в App Store!