Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
02.06.2025
Getting Things Done: що це таке, навіщо, та мій досвід
Знаєте що? Червень — місяць, максимально віддалений від Нового року. А значить, це такий надир продуктивності, коли всі старі новорічні обіцянки вже вигоріли, а до нових ще так далеко. Я гадаю, найкраще часу для пояснення системи самоменеджменту не придумати. 🌚
Отже, GTD. Я багато разів згадував її, але тепер хочу систематично пройтися, бо гарного пояснення рідною мовою ще не бачив. На початку була книга. Українською її перекладають нудно “Як привести справи в порядок: мистецтво продуктивності без стресу”. Хіба це бренд? Я пропоную альтернативну назву: Беремося До Справ — БДС, а оскільки це моя авторська адаптація — то БДС Модифікована: БДС(М). 🐴
Центральна мета БДС — це, як не дивно, не виконувати справи, а зберігати спокій та робити саме те, чого потребує кожний поточний момент. Раніше я думав, що це потребує патологічного контролю над собою, але справжній зміст цієї мети в тому, що це неможливо, поки голова забита справами. Через систематичне спорожнення голови у зовнішню систему ти залишаєшся вільним бути в моменті — хай то важливий дзвінок, прибирання квартири, чи гра з дитиною.
Можна подумати, що “система” це структура даних, але, як я згодом зрозумів, тут головне — як ти думаєш про справи. Якщо бездумно виконувати вимоги структури (про яку я буду писати пізніше), ефект виходить мінімальний. А навпаки, з правильною філософією — багато структури не потрібно.
Також цікаво, що БДС охоплює всі сфери життя. Теоретично можна її впровадити тільки на роботі (бо в мене зараз взагалі дві окремі системи), але повний результат буде тільки від повного застосування. Бо голова ж зайнята всякими справами, що на роботі, що вдома. В БДС немає нічого специфічно “робочого”, та вона чудово допомагає впоратися з побутом.
Я з цією системою вже років 18, та хоч не завжди я її дотримуюсь, але принципи та поняття БДС так невідʼємно зі мною, як арифметика. Останній рік досліджував її поглиблено — шукав пояснень, бо хотілося дійсно розуміти, що мав на увазі автор. Спробую й вам розповісти.
01.06.2025
Скрипт для експорту з Perplexity
Сьогодні дуже продуктивний був день — вчорашню міграцію довів до компіляції (але, на жаль, не до кінця), випустив маленьке оновлення Ping - додав нескінченну пейджинацію пінгів та фільтр тегів за імʼям. А потім, несподівано шльопнув маленький, але дієвий продукт.
Ось пітч: Perplexity мені подобається всім, окрім одного: продукт досліджень залишається у них на сайті. А мені б хотілося відразу все це зберігати в Obsidian. Можливість експортувати є, але тільки по одному діалогу. Та й API на це немає — тільки через вебсайт. Але, думаю, чом би не автоматизувати експорт всього з Perplexity у Markdown за допомогою скрипту? Так і зробив — за допомогою Cursor та того ж Perplexity.
Про Cursor більше не буду, там нічого особливого, робота в режимі дрібних змін. Хоча він мені непоганий скелет згенерував, та навіть переклав потім на TypeScript. Він же ж обрав Puppeteer для керування браузером. Я самого Puppeteer не знаю, але ж на автоматизації соба… тобто капібару зʼїв. З допомогою Cursor (та TypeScript) можна документацію не читати, а прямо казати, яка команда потрібна. Ну майже.
Авторизацію залишив в напівручному режимі — ти сам копіюєш код з пошти у вікно браузера. Тут все прямолінійно, окрім захисту Cloudflare. Який я навіть в ручному режимі не міг пройти. Тоді Perplexity мені сам підказав, що для обходу безпеки є puppeteer-extra-plugin-stealth. З ним сторінка захисту навіть не зʼявляється, бо браузер “виглядає як нормальний”.
Далі цікавим питанням було — як під час експорту зберегти файл туди, куди треба. Чомусь в Puppeteer так само як і в Capybara для цього немає вбудованих функцій, а треба викликати вже знайомий CDP та слухати події. Зокрема, важливо помітити, що файл вже завантажився, та так само — що відбулася помилка. Загорнув все це у клас DownloadManager, який повинен бути універсальним.
Ну й остання перешкода — то обмеження за частотою запитів. Як виявилося, після кількох десятків експортів починається прямо жорсткий блок, потім дає експортувати одну сторінку за кілька хвилин. Довелося впровадити повторні спроби, а взагалі якщо таким скриптом користуватися, то краще запускати його щодня за розкладом. До речі, також для того скрипт памʼятає, які діалоги вже зберігав, та пропускає них.
Осьо perplexity-exporter, можна забрати собі, можна просто почитати.
31.05.2025
Масштабний рефакторинг з агентом ШІ
Досяг поки для себе стелі використання помічника ШІ:
В мене є кілька проєктів для iOS, які використовують SwiftData. Мені він зовсім перестав подобатись, тому хочу переписати все на SQLite (GRDB). Але то вручну робити важко. Зате більшість змін робляться за аналогією, хоча не повною: наприклад, у SwiftData асоціації завантажуються автоматично, коли до них звертаєшся, а в GRDB варто наперед запитати все необхідне до купи.
Отже, що… думаю, напишу інструкцію для Cursor, нехай перекладе. Він технічно щось переклав — але до робочого результату було далеко.
Нормально вийшло перенести: структуру моделей — навіть міграції. Та всю оту нудну підготовку з імпортом пакетів, створенням підключення тощо. Решту змін довелося робити вручну, або давати значно точніші інструкції.
Найбільша перешкода для масштабного використання LLM в інженерії — нестабільність результатів. Нехай вони б навіть були гарні. От написав ти інструкцію. Запустив. Не сподобалося. Трохи відредагував. Та тепер можеш отримати зовсім інший шлях рішення. Причому немає явного шляху на це вплинути.
З маленькими змінами легше, здається, тому що менше варіантів розвʼязку, тому результат більш-менш (але не завжди!) відтворюється. Але зі змінами ось такого, проєктного, рівня в мене, наприклад, міграції то були в тому ж файлі, що модель, то в окремому файлі, то у файлі з підключенням. Та не обовʼязково це можна зафіксувати інструкцією — принаймні, не без появи інших небажаних змін.
Я гадаю, причина тут в тому, що будь-яка модель тренується на обмеженому наборі проєктів. В кожного проєкту — своя комбінація підходів. Кожен проєкт — локальний максимум видачі моделі. Коли наш запит перетинає умовну межу одного локального максимуму в інший, то результат зміниться повністю. Оце люто дратує, та я не думаю, що протидія існує.
А вихід є - обмежувати інструкції дрібними змінами. Це, для мене, поки і є сфера доцільного використання ШІ.
30.05.2025
Я втомився від Capybara
В Ruby on Rails надто зручна система для інтеграційних тестів — з Capybara. Вона дозволяє керувати браузером з Ruby. Та це чудово. Можна поєднувати в одному файлі будь-які налаштування застосунку та бізнес-логіки та всі можливості браузера.
Бо знаєте, не всякі побічні ефекти можна прочитати не те що з інтерфейсу, а навіть з API. А у нас, оскільки інтеграційні тести повністю на Ruby, то можна все що завгодно перевіряти. Стан бази та інших систем — звісно. Але також легко замокати все що завгодно та перевіряти, що відбулися очікувані виклики.
А ще в Capybara приємний DSL, абстрагований майже від всіх нюансів вебсторінок. (Хоча за потребою можна й спуститися на низький рівень чи навіть виконувати JavaScript.)
Такі зручності формують стиль тестів, де браузер для нас — лише ще один спосіб перевірки. Тобто головний фокус тестів залишається на застосунку, єдина різниця що для цих тестів ми ним керуємо з браузера — на відміну від юніт-тестів, де код викликається напряму, та тестів запитів, де ми робимо прямий запит до API застосунку.
Але браузер — річ складна та хаотична. Абстракція тече, та замість простих “перевірок браузером” ми починаємо латати в ній дірки. Хоч є багато системних покращень (як-от Capybara вже давно вміє автоматично чекати, доки на сторінці зʼявиться зміст, який ми перевіряємо), але код тестів вкривається латками все одно.
Минає час, та ми отримуємо незграбну, незрозумілу масу коду, який ще й все одно регулярно дає хибу.
Який я можу зробити висновок… Capybara була чудовим інструментом на той час, коли JavaScript був рідкістю (так, колись взагалі окремо виділяли тести, яким він потрібний — решта навіть браузер не запускала!) Але зараз фокусом інтеграційних тестів є застосунок JavaScript, та набагато легше було б їх писати не на Ruby, а наближено до браузера — тобто в Cypress чи Playwright тощо. Тим паче, що для них вже давно зробили інтеграції, які вміють запускати фабрики, а то й довільний код на Ruby.
29.05.2025
Агент ШІ: Від 0 до вайб-кодінгу
Часто доводиться чути, що використання ШІ в програмуванні — це обовʼязково радикальний відхід від норм. Це все одно, як думати, що якщо купиш болгарку, то нею треба буде різати все, від хліба до нігтів. Ось вам декілька сходинок поступово більшого використання ШІ — обирай ту, яка тобі комфортна.
1. Ставити питання щодо проєкту. Як працює X? Чому тут так написано? Яка різниця між А та B? ШІ здатний швидко обробити багато коду та дати чудові пояснення. При цьому ШІ не пише аніскілечки вашого коду, тому й ніяких ризиків немає. (До речі, тут та далі потрібний редактор з ШІ, як-от Cursor, а не просто чатбот без знання проєкту.)
2. Маніпуляція тексту. Моя улюблена функція поки — це скопіювати шлях до об’єкта в Google Console та попросити зробити з нього виклик CLI. Задачі такого рівня вимагають нудних ручних перетворень, а ШІ з ними порається дивовижно.
3. Локальні доповнення. Це коли тобі пропонує продовження рядка, який ти зараз пишеш. Я цим користуюся багато років з TabNine, але чесно кажучи, LLM краще радять, більш поглиблено.
4. Локальна генерація чи рефакторинг. Можна попросити згенерувати одну функцію, чи скелет класу, чи тесту. В Cursor для цього тиснеш Cmd+K
в потрібному місці. А ще - виділяєш текст, тиснеш Cmd+K
та рефакториш на місці. При цьому зміни не залишають область виділення, а якщо не сподобалося — можна відразу скасувати.
5. Покрокова генерація за інструкціями. Це коли ти агенту кажеш “згенеруй мені клас”, “згенеруй тести для нього”, “пересунь функцію в інший клас” - та коректуєш після кожного кроку. Виходить програмування в напівавтоматичному режимі — машина робить нудні речі, ми дописуємо важливі.
6. Покроковий рефакторинг за інструкціями. Я виділив в окрему сходинку, бо рефакторинг має більше ризиків попсувати код, тому потрібно ретельніше переглядати його результати. Зате в цілому можна робити навіть на кшталт “прибери повторення”, або “знайди схожу функцію в інших місцях проєкту та використай її”. Так що не слопом єдиним!
7. Генерувати плани за допомогою ШІ. Цим я сам ще не займався. Але технічно ШІ так само може генерувати план, як і код (або навіть краще.)
8. Піддатися та прийняти вайб. Ну слухайте, я досі не вірю, що хтось це робить навсправжки.
28.05.2025
Сліпці та AI слон
🤼 Надзвичайно важко говорити про генеративний AI щось раціональне для загальної аудиторії. Бо визначні голоси поділилися на релігійних AI-оптимістів та таких саме релігійних AI-заперечувачів. Та, як завжди, реальність десь посередині. Щоб побачити її потрібно позбавитися багатьох хиб, які натомість вбиваються дедалі глибше з протилежних сторін. Ось мої думки, як релігійного прагматика.
Спочатку про оптимізм. Слон в цій кімнаті — це те, що генеративний AI став поточним бумом, а значить, на ньому заробляють купу грошей. Не користувачі, а насамперед постачальники (як-от компанія OpenAI). Не з користувачів, а з інвесторів. Поки є оптимізм — будуть інвестиції. Гроші ллються на підґрунтя двох міфів.
Перший міф — що генеративний AI здатний найближчим часом переступити за межі перетворення запиту на результат та стати чимсь більшим. Як я це розумію, для того потрібний черговий науковий стрибок. Масштабуванням з ліхтаря ніяк не зробиш сонце. Це — найсумніший міф, бо пересічні люди проєктують наукову фантастику на реальність, а вся медійно-корпоративна структура їх тільки заохочує.
Другий міф — що на розвиток AI потрібно дедалі більше ресурсів. Це справжня золота жила, бо якщо раніше треба було наймати людей чи принаймні пояснювати інвестору, куди підуть гроші, то зараз все ясно — датацентри! датацентри! датацентри! Та сотні мільярдів інвестицій на них. Тому поява цього року моделі від DeepSeek так розхитала ринок: якщо є дешевший шлях, то, може, не варто спалювати стільки грошей? За логікою споживача, це успіх, але за логікою постачальника — суцільна катастрофа, тому треба скоріше про це забути, бо ось-ось прорвемось, дайте ще грошей!
Це гарний перехід до AI-заперечувачів, бо цю тезу вони охоче підхоплюють. Втім, якщо перевіряти, то виходить, що запити до AI витрачають менше енергії, ніж інші повсякденні витрати. А тому принаймні можна розглядати генеративний AI як реальний інструмент, та дивитися, які переваги він приносить, та які ресурси заощаджує. Знаєте, що ще витрачало багато енергії? Перші ЕОМ.
Та наступний міф заперечувачів — що генеративний AI здатний тільки на нісенітницю. Я розумію, це реакція на розгнузданий AI-оптимізм. Втім достатньо хоч трохи випробувати LLM, щоб побачити, що це не так. Що результат залежить від твоїх запитів та твого нагляду. Звісно, якщо потребувати від LLM всього та відразу, то вийде сміття. Але це все одно що ображатися на перфоратор, за те що він не зробить тобі ремонт.
(Та так, доводиться дивитися на генеративний AI в контексті попередніх бумів — крипти, Web3, NFT, метаверсу — де ніякої практичної користі не було, була тільки бульбашка. І дуже важко було змусити себе подивитися поза цією черговою бульбашкою. Але обіцяю, в AI користь є.)
ОК, на чому хочу сьогодні закінчити — так, для програмного інженера від поміркованого використання LLM є багато переваг. Нехай фантазери мацають слона та базікають, а ми поки його навантажимо та поїдемо у справах. 🐘
27.05.2025
Функціональний стиль чи декларативний?
Зробив цікаву помилку. Це код власної перевірки для бібліотеки для тестування RSpec:
# expect(response).to be_foobar
RSpec::Matchers.define :be_foobar do |bar|
match do |actual|
check_foo(actual)
if bar
check_bar(actual, bar)
else
true
end
end
end
Помилка тут в тому, що якщо bar
немає, то перевірка завжди повертає успіх. Причина наче очевидна, якщо вже про неї знати: блок match
повинен повертати true
, якщо перевірка пройшла. Це звичайнісінька функція. Тому в мене перша перевірка фактично нічого не робить, бо її результат ніяк не враховується.
Мабуть, я це писав під впливом SwiftUI, де все не так:
VStack {
Text("Foo")
if bar != "" {
Text("Bar \(bar)")
}
}
Це вірний код: перший рядок буде видимим завжди, а другий — тільки якщо змінна не порожня. Власне, так саме працює й JSX та інші мови розмітки, але SwiftUI відрізняється тим, що це все нормальний код Swift.
Якби це був лісп — наприклад, Clojure - то там не тільки легко зробити макрос, який би виконував такий синтаксис та збирав результати, а й такого “дивного” більше, ніж традиційних функцій. Для простого прикладу наведу протяжні макроси (кложуристи, допоможіть, бо я трохи призабув):
(as-> result _
(check-foo _)
(and bar (check-bar _))
)
Але у Swift таких потужних макросів немає. Виявляється, заради SwiftUI придумали більш спеціалізований механізм - Result Builder. За наявністю анотації @resultBuilder
він збирає послідовність викликів у масив, а потім надсилає у відповідний інтерфейс. Ось так, не побоялися повністю перевизначати поведінку блоку коду без видимих на те ознак.
26.05.2025
Як не вчитися на програміста
В мене спитали поради щодо навчання в компʼютерній академії IT STEP. А саме, чи це гарне місце для того, щоб юнаку стати програмістом? В самого немає досвіду навчання там, є тільки відгуки рекрутерів, ну й інформація про курс з сайту. Отже.
Те, що я бачу — цей курс триває два роки. Це дуже великий термін для того, щоб навчитися програмувати! Зате — програма намагається охопити таку кількість дисциплін, що в неї немає жодного шансу освітити бодай одну повноцінно.
Як на мій погляд, то це такий гранд-тур програмування. Якщо є гроші та час — то можна “проїздом” побачити все - від скриптів на Python до застосунків на iOS та Android. З такого боку, не так воно й погано.
Але ж далі вони обіцяють, що випускник курсу буде все це вміти робити, та бути готовим шукати роботу… А ось це ніяк не може бути правдою. Мало того, що за такий стислий час можна побачити тільки основи чогось, але й ці знання будуть вилітати так же ж швидко, як на заміну ним приходять нові.
Бо єдиний спосіб засвоїти щось в програмуванні — це практика. Багато практики. В кращому випадку, студент знайде серед тем те, що його захопить, та залишиться там, вивчати глибше. Поза школою. Самостійно. А “перелік засвоєних тем” нічого не вартий та розрахований приголомшити незнайомих з програмуванням батьків.
На заміну я рекомендував знайти курс конкретної дисципліни та почати з неї. Бо сама структура курсів це корисно — викладач, група, плани, терміни — з всім цим легше вчитися, ніж самостійно. Зате з однією дисципліною є шанс дійсно щось вивчити. Хоча все одно, без самостійної практики та інтересу нічого не вийде.
А так… ну не знаю, виправте мене, якщо я не правий, але програмування — справа для тих, хто готовий самостійно колупати незнайомі теми, бо вони ніколи не закінчаться.
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 - суть авторизації абсолютно не змінюється. Класичний патерн адаптера. (Але це не точно).