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

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

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

16.01.2025

Скільки інженерних практик є прихованими механізмами подолання РДУГ?

Вивчаю тут (для друга) розлад дефіциту уваги та гіперактивності, а також що з ним роблять (немедикаментозно.) Поки що поділюся цікавим спостереженням: деякі заходи, що є рекомендованими для людей з РДУГ, мені давно знайомі як професійні ритуали.

Може все це й збіг (або хибна інтерпретація.) Але я тепер дивлюся на ці підходи з новою повагою: спеціально чи випадково, вони допомагають досягти успіху.


15.01.2025

Чому в Rails крута модель

Вчорашній пост змусив мене знову подумати, чому ж в Ruby on Rails з моделлю працювати зручніше, ніж будь-де. ActiveRecord залишається неперевершеним в простоті використання, навіть для побудови складних та потужних бізнес-процесів.

Отже. В Ruby немає різниці між методами та атрибутами, а точніше, весь публічний інтерфейс обʼєктів є виключно методами. Це приховує силу-силенну складності — аспекти реалізації не обтяжують споживача. Наприклад, User.all.map(&:name) елегантно та непомітно залізе в базу. (Але краще User.pluck(:name), що відразу витягне масив імен.) Фактично в Rails база виглядає, як “внутрішня”, а не “зовнішня” структура даних. Інколи це підштовхує писати неоптимальний код — але, як каже Роб Пайк, не варто оптимізувати код наперед.

Ніякі класи не потребують явно вказаного підключення до бази чи інших параметрів. Практично в будь-якому іншому середовищі використовується Dependency Injection - принаймні для того, щоб класи можна було тестувати без справжньої бази тощо. В Rails всі залежності приховані, а нам ніколи не доводиться про них думати.

Можна подумати, що це робить код Rails неможливим для перевірки. Але, в Ruby весь код живе у спільному просторі імен та доступний для змін під час виконання. Тому практично кожний метод, змінну чи клас можна замінити на тестову копію. Це насправді дуже потужно, та ми постійно цим користуємося. Для прикладу, можна навіть змінювати константи.

Якщо підсумувати, то мова Ruby - королева абстракції. Тут, як ніде, розробник може сам вирішити, які деталі приховати, а які продемонструвати. Навіть зчитати під час виконання схему бази та утворити на основі цієї схеми повну модель даних, яка нічим не відрізняється від коду, написаного вручну - цілком буденне явище.


14.01.2025

Тести з базою на Go

Як я два роки тому писав, так і досі користуюся бібліотекою testfixtures для підготовки тестової бази. Фікстура — це такий файл (шаблонізованого) YAML, в якому сидить зміст однієї таблиці.

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

Одним словом, я все більше дивлюся, як уникати бази в тестах взагалі. Для когось це, може, очевидний підхід, але я звик до Rails та factory_bot, де наочно та стисло можна створювати довільні ієрархії обʼєктів. Втім, factory_bot спирається на ActiveRecord, а для Go в мене нічого схожого немає.

Базу можна замокати — для того є бібліотеки sqlmock чи pgxmock. Вони дають можливість задати результат для кожного запиту. Що приємно, не обовʼязково писати весь запит, достатньо підрядка, наприклад, з використанням sqlc вистачить назви запиту.

З моками бази зручно, що запити будь-якої складності заміщаються легко: достатньо вказати, які рядки він має повертати. Мені тільки не подобається, що моки нічого не знають про структуру бази, тому легко помилитися зі змістом або забути оновити моки після міграцій. В результаті будемо тестувати із хибним прикладом та не знати краще.

Тому мені ще більше подобається підхід вищого рівня. Оскільки код, що виконує запити, ми генеруємо з sqlc, тобто його тестувати не потрібно, то можна мокати згенерований інтерфейс. Він повертає структури, цілісність яких гарантує (в міру можливого) sqlc, а для моків можна використати хоч mockery, хоч будь-яке інше рішення, бо до бази вони вже не мають відношення.

Для повного комплекту гарно ще вкрити тестами модуль sqlc, щоб переконатися що самі запити працюють вірно.


13.01.2025

Недоінжинірінг та переінжинірінг

⚖️ Інженерні рішення часто йдуть по лезу бритви. Хотів зробити як простіше — через два місяці довелося все переробляти, бо рішення не виконує потреб. В іншому місці думав передбачити вимоги — потім доводиться підтримувати важку архітектуру, а вимоги так і не зʼявляються. Звісно, є й рішення, де ми потрапили в золоту середину, але навіть коли їх більшість, все одно увагу привертають ті місця, де помилилися.

🌱 З мого досвіду можу сказати одне: завжди легше спочатку зробити недостатньо, а потім доробити, ніж потім прибирати зайве. Це трохи йде всупереч з Інженерною Етикою, яка вимагає від нас вкласти в роботу стільки зусиль, скільки є часу. Втім, краще хай зусилля йдуть на планування мінімального рішення.

🚧 Чому? Ну, по-перше, складні рішення не тільки важче писати, а й важче потім переробляти. Особливо коли це включає зміни до схеми даних та до інфраструктури, де додати нове відносно легко, а от змінити… та інколи в умовах бізнесу буває просто неможливо!

🏔️ Звідки ще й друга причина: на практиці ніхто не буде переробляти готове, аж доки зовсім не припре. Доведеться жити з тією складністю… яка тільки накопичується та множиться.

Тому закликаю вас обирати недоінжинірінг приблизно завжди.


12.01.2025

Браузер, який я хочу

🌐 Ділюся ідеєю браузера, яка в мене крутиться в голові декілька місяців (десь після переходу на Obsidian Canvas)… але, зрозуміло, поки не вистачає часу спробувати.

💭 Я хочу браузер, інтегрований в базу знань. Та орієнтований на задачі. Я боюся сказати “дослідження”, бо це зараз має свої конотації зі всілякими zettelkasten та Roam Research, тобто з побудовою графа знань, а мені таке зазвичай не потрібно.

🕸️ …Мені потрібно: знайти трекові світильники, перенести сайт на Cloudflare та розказати про браузери. Кожна така задача тягне за собою низку питань, а відповідно — пошуків в інтернеті. От хочу бачити сторінки в контексті задачі. Причому щоб історія теж автоматично привʼязувалася.

📚 Бо зараз доводиться вручну переносити знахідки в базу, а це призводить до того, що тільки меншість досліджень зберігаються на майбутнє. Кожен раз обираєш: чи знадобиться це потім, чи задача на один раз. Та тут друга ідея інтеграції: пошук можна робити не тільки по вебу, а й по базі знань та по контекстній історії. Це автоматично розвʼязало б проблему зберігання на потім.

⚙️ В цілому, ідея більш реальна, ніж здається (я ще в школі робив “браузери”, бо в нас блокували Internet Explorer - одна компонента на Delphi та кілька кнопок це все, що потрібно.) Зараз, мабуть, можна привʼязатися до Electron, чи до WebKit. Але часу немає, тож поки тільки можу фантазувати.

📌 Цікаві поробки в цьому напрямку: SenseMap. Horse browser. Perplexity.AI. Буду радий дізнатись ще.


11.01.2025

Мʼясне сортування

В мене в застосунку для GTD є режим FVP для простого вибору наступної задачі. Він був трошки напівавтоматичний, а саме: переглядати задачі в послідовності, та не стрибати туди-сюди, доводилося вручну — за звичайним списком. Тобто все одно перед очима був повний список.

(Нагадаю, що весь сенс алгоритму FVP - це такий класичний пошук найпріоритетнішої задачі в масиві — запамʼятовуємо першу задачу, перебираємо всі інші та порівнюємо з нею, як найдемо більш приорітетну — запамʼятовуємо її та йдемо далі. Ну всі такий код писали. Але тут воно працює “на мʼясі”.)

👈 Сьогодні, щоб полегшити собі життя, впровадив спеціальний режим, де показуються тільки дві задачі - “найважливіша” та наступна з не перебраних, Залишається тільки обрати між ними (і так N разів). Такий собі “тіндер для задач” замість списку. 👉

Насправді колись я робив цілий сортувальник в такому стилі. Але для FVP потрібно тільки обрати одну задачу, та “мʼясна складність” алгоритму лише O(N).


10.01.2025

Спостереження за станом збірок на GitHub Actions

Маю таку проблему, що коли працюю з купою гілок на GitHub (тобто майже завжди), то важко встежити за результатами збірок — які можуть тривати до 20 хвилин. Хотів зробити віджет, який мені б сповіщав про завершення моїх збірок.

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

Для того в нас є два методи API. Перелічити запуски збірок для репозиторію — вертає всю потрібну інформацію. Їх навіть можна відфільтрувати за автором.

Але є нюанс. Для отримання інформації потрібний “класичний” токен авторизації з повними правами до всіх репозиторіїв — не хотілося б такий тримати відкритим. Або “fine-grained” токен — там є права тільки на читання GH Actions та тільки на конкретні репозиторії — що гарно. Але щоб бачити робочі репозиторії, він повинен належати робочій організації, на що треба ще отримати дозвіл від адміністратора. Що, як на мене, шкереберть — бо ж на класичний токен з повними привілеями дозволу не потрібно.

Друга проблема — що цей API обмежений одним репозиторієм, а в мене їх принаймні 6. Хоча 6 - це не 100, та можна всі їх опитувати по черзі. Є інший API - для сповіщень. Навіть доступний із класичним токеном з точно обмеженими правами. Але… сповіщення про збірки мають сміховинний мінімум інформації: репозиторій та текстовий опис — навіть не структурований. Навіть посилання на збірку в них немає, тому інтерфейс буде на рівні “щось десь впало, піди подивись.” (Краще, ніж нічого, звісно.)

Одним словом, наче й можна зібрати потрібний мені звіт (а саме — де тести впали, а де пройшли) - але виглядає ненадійно.

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


09.01.2025

Син попросив приготувати: сніданки

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

🥬 Понеділок: фотосинтез. Подолавши перше бажання дати лист салату та посвітити в очі ліхтариком, пішов з іншого боку. Ну власне “світлом” не наїсися… що може замінити світло? Як щодо жовтка яйця? Поклав на салат яйце пашот; яйце розрізаємо, жовток розтікається по салату, фотосинтез зараховано. 🌞

😶‍🌫️ Вівторок: аерозоль. Їстівних аерозолів теж не дуже багато. (Ну хіба оті цукерки-спреї… брр.) Але можна ще згадати, що пара — теж аерозоль. (От зараз ще подумав про дим — можна було б теж якийсь елемент включити.) В мене на пару асоціація конкретно з картоплею — як ото дихають картопляною парою від застуди. В середину тарілки йде піала з гарячою вареною картоплею з водичкою, а задля ефекту все це накрите кришкою. До речі, щоб пара дожила до столу, посуд повинен бути гарячим заздалегідь. Що взагалі хороша ідея, коли подаєш щось невелике — наприклад, яєшню. 🥔

🥗 Середа: емульсія. О, це вже значно легше. Бо більшість соусів — це емульсії. Можна було б обмежитися майонезом, але я заколотив прямо за столом айолі. Підсушив в аерогрилі шматочки хліба, порізав печену курячу грудку — салат “Цезар” готовий! (Не дуже автентичний, але де ви бачили автентичний “Цезар”?) 🧄

🥞 Четвер: піна. Тут теж складнощів немає. По-перше, всі хлібобулочні продукти — це тверда піна. По-друге, дві класичні кухонні піни — збиті білки та збиті вершки. Беремо перше, беремо друге… разом будуть панкейки зі збитими вершками зверху! Щоб було щось здорове, посередині кладу притушене яблуко зі спеціями. 🍎

🌯 Завтра беру перерву, бо досить. Взагалі в нас типова страва дитині на сніданок — це мʼясо з овочами в лаваші. І поживно, і здорово, і всередину можна багато всього покласти.


08.01.2025

Генерація JSON на Hugo

Мій бот скрипт для каналу — який бере зміст з файлів блогу на Hugo - був з самого початку збудований як повністю окреме рішення: він сам знаходить пости, сам їх парсить, сам їх надсилає в Telegram. З Hugo в нього спільні тільки домовленості про структуру тек та формат файлів.

Нещодавно мені спало на думку, що взагалі-то перші два кроки Hugo теж робить, та незрівнянно вправніше. Тому гарно було б якось включитися в Hugo та залишити на власний скрипт тільки публікацію. Зокрема, це розблокувало б інші напрямки публікації, бо наразі скрипт дуже завʼязаний на Telegram та розділити його мені поки не вдалося.

Механізму плагінів в Hugo немає, тож що я придумав: Hugo під час збірки буде генерувати мені JSON з всіма даними, що потрібні для публікації, а скрипту залишиться зчитати JSON та, так би мовити, “синхронізувати” його в канал.

…Механізму “згенерувати JSON” в Hugo теж немає. Зате ми можемо створити окрему сторінку, а в її шаблоні назбирати даних, викликати для них jsonify та надрукувати — от і буде JSON. На щастя, принаймні з кожної сторінки є необмежений доступ до даних сайту.

Величезний мінус — збирати дані доведеться всередині шаблону. Hugo використовує звичайні шаблони Go, але ніколи в Go мені не спадало на думку, скажімо, перетворювати в шаблоні масиви даних. Бо мова шаблонів незручна, не має підказок IDE, та ще й документація Hugo неповноцінна (в документації функції називаються, як в Go, а в шаблонах пишуться за псевдонімами, яких у переліках функцій та навіть в пошуці не видно).

Але, попри все це, мені вдалося! Найбільше часу витратив на спроби функціонального перетворення масивів — бо там наче є функція apply - аналог map - але виявилося, що дуже обмежена. А мова шаблонів — все ж імперативна. Тому, наприклад, щоб перетворити масив тегів-обʼєктів у масив назв, я оголошую новий масив та циклом складаю в нього назви. Коли це збагнеш, решта вже не так складно.

Так що чекайте в наступні тижні покращень по скрипту публікації, а може й публікації самого скрипту.


07.01.2025

Міграції в SQLite

🗑️ Стикнувся сьогодні з класичним багом в Ping: видалення тегу звалювалося через порушення FOREIGN KEY. Бо ключ я додати згадав, а ON DELETE CASCADE забув. Почав було виправляти на рівні застосунку (тобто видаляти всі відмітки, а потім сам тег), та й думаю… хіба це не робота для бази даних?

🪨 Але ось що виявилося: в SQLite команда ALTER TABLE здатна тільки на найпростіші зміни схеми: додати / видалити / перейменувати стовпчик, або перейменувати саму таблицю, і все. Що мене, звиклого до PostgreSQL, ввело в ступор: невже я застряг з поганою таблицею?

✂️ Знайшов відповідь на StackOverflow з цікавою порадою: оскільки схема таблиць в SQLite зберігається в спеціальній табличці, то достатньо зняти спеціальний запобігач PRAGMA writable_schema та можна просто замінити схему та додати до неї ON DELETE CASCADE. Ясно, що це виглядає максимально сумнівно.

💣 Сумнівно чи ні, але спробував; виявилося, що на iOS цей метод взагалі не працює, бо там підключення відбувається в особливому defensive mode, де абсолютно заборонені такі “сумнівні дії”. (Якщо в схемі змінити таким чином щось суттєве — типи чи порядок стовпчиків, наприклад — то база гепнеться.)

📖 Тоді почитав трохи документацію, та виявилося, що рекомендований підхід дуже простий: створити другу, “правильну” таблицю, наповнити її даними, а потім підмінити стару таблицю новою. Мінуси цього рішення: не відчуєш себе містером роботом. Плюси: все пройшло успішно, без втрати даних та цілісності.

❓ До речі, причина цього (яка теж є на тій же ж сторінці документації), прагматична. SQLite не зберігає схему в структурній формі, а тільки в текстовій (власне, зберігається команда CREATE TABLE.) Тому будь-які зміни в схемі повинні були б перекладатися назад в текст CREATE TABLE - ще й гарантовано без втрат змісту. Творці SQLite вирішили не ускладнювати цим проєкт та залишили зміни схеми в такому ручному, явному режимі.