Канал
У Льоні сьогодні
Нотатки архітектора систем та майстра на всі руки. Що я зробив, що з того вийшло, і що це все значить.
-
Вкладки браузера — в Obsidian Canvas (скрипт)
Я якось писав, що хочу браузер, який буде інтегрований з базою знань. Таке можна потрохи робити. От, наприклад, коли я навідкривав вкладок для якогось дослідження, було б гарно всі їх скинути в Obsidian Canvas на майбутнє. Але ж не вручну? Накрутив собі за пів годинки скрипт, не без помічника ШІ.
Власне, майже всі запчастини в мене вже є, Я вже писав, як отримати список вкладок з Safari та генерувати Canvas зі списку, утиліту для чого pbcopy-chromium я вже публікував. Але одне рішення — на AppleScript, друге — на Ruby. Вирішив, що найбільш елегантно буде зібрати до купи на JavaScript for Mac Automation, про який я теж вже писав. Бо інакше інтеграція буде ще складніша, ніж частини.
Отже. AppleScript у JXA перекладається 1-до-1. Це, за моїм досвідом, найкраща задача для ШІ! Замість години копирсання поганою документацією та поступового налагодження — задля, фактично, “нульового” результату, бо ми тільки перекладаємо логіку на іншу мову — одна команда. Так само вдалося й перекласти генерацію канви з Ruby на JXA - причому, ба більше, ШІ відразу її автоматично “вписав” у попередній скрипт.
Це 80% роботи. Решта 80% була менш автоматичною. Ну, згенерувати команду для виклику
pbcopy-chromiumвийшло, та це гарно, бо там не одна команда, а цілий ланцюг. От тільки як в нього передати зміст? ШІ зробив черезecho. Зрозуміло, що зі складним JSON від цього буде купа проблем. Тоді спробував через тимчасовий файл. ШІ нагенерував відповідні команди — включаючиmktempдля генерації файлу — десь на 80%.Але тепер залишилося найцікавіше, бо запис у файл з JXA не використовує UTF-8. Поради ШІ тут були безпомічні. Тоді знайшов на SO відповідь, як то зробити… мостом в Objective C? Дико, але працює!
Нарешті, про рефакторинг. Сьогодні ШІ накидав все в одну купу; наприклад, вище згадана побудова JSON для канви відбувалася прямо в циклі по вкладках браузера. Звісно, краще ділити логіку за намірами. Зате інструкція “розбий це на дві функції для того та для того” спрацювала.
Ще з суто ШІшного ексцесу (тобто такого, що я б сам не став робити) - генерація ID вузла через хеш змісту. Це я попросив зробити, але функцію воно нагенерувало само. Нічого бібліотечного готового немає. Вирішив, нехай залишається.
Знову, таку роботу я б сам не зробив, бо часу немає. Забрати скрипт можна тут.
-
Міграція інтеграційних тестів: погана задача для ШІ
Передісторія. Десь у 2019 зʼявився Chrome DevTools Protocol - протокол поглибленого керування браузером. Наприклад, він дозволяє прямо встановити кукі, або зробити знімок екрана. Гарна річ, дозволяє позбавитись посередника для автоматизації браузера. Зокрема (але не тільки) для інтеграційних тестів.
Невдовзі вийшов гем Cuprite для тестів на Ruby, який дозволяв позбавитися Selenium. Selenium - не найстабільніший пакет на світі, тому тоді це сприйняли з радістю та мігрували. Радикального виграшу не отримали, хоча поглиблене керування стало в пригоді. Минуло 6 років, та Cuprite так і не переміг Selenium, а до того ж його не дуже гарно підтримують. Інтеграційні тести, на жаль, це така галузь, де вчасна підтримка все вирішує, бо Chrome оновлюється постійно та десь щось ламає. От і зараз це стало великою проблемою… можна було б переїхати назад на Selenium, тільки це проблема ще більша. Питання: чи допоможе із цим впоратись ШІ?
Виправлення, широко кажучи, можна поділити на 3 категорії.
Різниця в пробілах. Для зручності в Capybara можна перевіряти не тільки сухий DOM, але і його текстовий зміст. Кожний із рушіїв по-своєму його будує (зверну увагу, що ніякого стандартного способу тут немає.) Це веде до купи абсолютно тривіальних помилок вигляду
Foo\nBarзамістьFoo Bar. Я думав, що ШІ тут легко все поробить, але ні. Часто він або не розумів задачі, або заміняв надто багато, вигадував своє. Зробив висновок, що ні — дрібні, розсипані по коду виправлення ШІ робить погано. Я сам швидко зʼясував природу змін… от тільки робити їх вручну все одно довго та нудно.Зміна API В Selenium за ці роки зʼявилася підтримка CDP. Втім, звісно, виклики не збігаються. Умовно, замість
driver.set_cookieтреба писатиdriver.devtools.network.set_cookie, та ще й термін передати числом, а не датою. Я сподівався, що “перероби на API Selenium” пройде, але ні. Навіть коли додав “читай цю сторінку із прикладами”. Зрештою виявилося, що я вручну з масовою заміною можу адаптувати виклики швидше, ніж вигадувати запит до ШІ.Складніші зміни. Взагалі, мій перший план був такий: забирати з CI журнал невдалих тестів та передавати до Claude із додатковими інструкціями. Окрім вище описаних змін, були й менш зрозумілі. Наприклад, деколи Selenium відпрацьовує швидше (чи не чекає чогось?) та перевірка випереджує стан. Тут ШІ робив якісь незрозумілі кроки. Зокрема - спрощував тести, щоб ті проходили. Тобто з масовим виправленням без людського втручання точно успіху немає.
Якщо підсумувати, то ця міграція, про яку мені навіть казали “так може ШІ це швидко зробить?”, так і залишилась великою та складною задачею.
-
Терниста дорога до власного сервера CS 1.6
Запропонував колегам зробити місце зустрічі онлайн — сервер Minecraft. Отримав контрпропозицію — а що, як старий та знайомий Counter Strike? Вирішив випробувати.
З першого погляду виявилося що в основі, все непогано! В Steam є термінальний клієнт SteamCMD, який навіть в Docker не те що запускається, а має офіційний образ!
За допомогою цього клієнта можна завантажити сервер гри, та не тільки CS, а багатьох інших. Для більшості, включаючи CS, навіть авторизація не потрібна, тобто можна легко зібрати Dockerfile та відвантажити куди потрібно.
Деякий час в мене SteamCMD вилітав із помилкою
Fatal error: futex robust_list not initialized by pthreads… що пояснювалося 32-бітністю, але не повністю. Бо навіть офіційний образ не хотів запускатись, а там вже вся підготовка ОС виконана. Нарешті здогадався, що проблема в тому, що я все це роблю на Apple Silicon, та щось там в емуляції i386 не гладко. Тут згадав, як я запускав Docker віддалено, та на AMD64 помилка зникла. Звісно, на практиці я б тримав сервер десь в інтернеті, а не на власному ноутбуці.…Але все це марно, бо жодний зі “старих” контрстрайків не працює на свіжих macOS, бо вони всі 32-бітні. Як і весь Half-Life. Є підтримка тільки Counter-Strike 2, який важить 40 Гб та ще й нікому не знайомий. Так я колег не долучу. Може, краще в Doom (GZDoom) чи Quake грати? Або мод Counter Strike для Minecraft… 🙃
Втім, не все ще втрачено, бо я тепер бачу що є прямо декілька портів вихідного коду або реверс-інжинірнутих. (Не всі вони цілять на macOS, одначе.) Це вже коли буде знову настрій для диких експериментів.
-
Нова версія гему Headless та ШІ для розблокування проєктів
Сьогодні випустив оновлення для Headless. За всіма вимірами, це треба було зробити вже давно, але краще пізніше, ніж ніколи — принаймні, якщо люди все ще просять. (Я вже писав), що мені він давно не потрібний. Десь з рік тому до мене зверталася людина та пропонувала зайнятися підтримкою, та я навіть доступи надав, але… в неї теж поки не знайшлося часу, напевно? Історія класична. Десь з тиждень тому мені знову нагадали, що гем не працює з сучасним Ruby, тож вирішив взятись.
Як я себе змусив це зробити. Думаю, ну мені не хочеться - Cursor зможе. Успіхи з Cursor були різного рівня.
Замінити Travis CI на Github Actions - впорався на 95%. (Чомусь запхав у виконання тестів
xvfb-run, хоча вся суть мого гему, що він заміняєxvfb-runдля програм на Ruby.) Це чудово! Я вручну б довго сидів та розбирав синтаксис.Оновити матрицю версій Ruby для CI - впорався на 100%. Теж гарно, бо це наче просто, але довелося б бігати по сторінках завантажень Ruby та JRuby.
Прибрати ключ конфігурації та перейменувати інший — десь 90%, майже все гарно, але трохи додумав зайвого. Втім, не так багато, щоб його було складно видалити.
Додати Rubocop - тут я б сказав 30% успіху — наче щось виходило, але конфігурації забагато та
rubocop-railsнамагався теж впхати.Виправити Rubocop - цього взагалі не раджу робити, хоча в мене були великі сподівання. Бо код псує більше, ніж виправляє. Причому псує в сенсі змінює зміст. А я зрештою замінив Rubocop на Standard - це надбудова над Rubocop із переконаним набором налаштувань. Окрім іншого, Standard добре виправляє помилки та підійде навіть в ролі автоформатувальника. Навіщо ШІ, коли є спеціалізований інструмент?
Якщо підбити підсумки, то агент ШІ допоміг зсунутися з мертвої точки та навіть закрити деякі необхідні, але рутинні оновлення. Та це безумовно успіх. Проєктів багато — мене мало. З агентом мене стає більше.
-
Пошук по каналу на сайті за допомогою Orama
Сьогодні зробив (не без допомоги Claude, зізнаюся) пошук по каналу. Вже кілька років про це думаю, але, знаєте як — пошук це велика задача, ніколи не було часу. А тут за півдня зміг зробити непогану версію, яку можна побачити прямо на сторінці каналу. (Шукайте рядок пошуку.)
Будь-який повнотекстовий пошук спочатку будує індекс, а потім шукає по ньому. Колись в мене на сайті вже був пошук, чисто фронтендовий, через Lunr.js - там індекс сидів прямо в коді скрипту. Втім, це значило, що для пошуку доводилося завантажувати кілька мегабайтів — фактично, весь зміст сайту. Це мені не подобалося. А окрім того, Lunr більше не підтримується.
Тож шукав рішення із бекендом. В Hugo є офіційний перелік, з яких я зупинився на Orama, бо вони обіцяли рішення на чистому JavaScript. (А ще Orama вміє векторизувати запити, що дає можливість шукати по синонімах — але тут поки проблеми.) Нагенерувати базовий код для Orama допоміг Claude, тож не довелося починати з нуля.
Моє рішення складається із чотирьох частин. Спочатку, під час побудови сайту Hugo генерує зміст JSON. Це для мене вже знайомий крок, бо так само працює OmniWOPE.
Потім, теж в скрипті постачі, запускається окремо індексатор. Він перебирає вхідний JSON та генерує величезний (22 Мб!) JSON індексу. Цей файл потрапляє в збірку серверних функцій Vercel.
Далі є функція
/search- вона завантажує той індекс з файлу та викликає пошук Orama — це вже досить прямолінійна дія.Та нарешті, є фронтенд, який теж нагенерував Claude, бо мені не хотілося довго возитися із шаблонами. Фронтенд робить запити до API та виводить список результатів. Попросив Claude “no frameworks, use HTML DOM API” та в цілому отримав те що хотів. Потім додав “use css classes and html tags from post list page”, бо на початку він наробив власних стилів. Якось так.
Поки в цього пошуку є обмеження. Не знайшов, як в Orama мати документи різними мовами — тому поки є тільки українською. Пейджинацію поки не став робити. Але головне — що нарешті закрив навіть власну потребу, бо часто я банально не міг знайти статтю простим пошуком (їх, до речі, тільки-но перевалило за тисячу!). А Google чомусь надзвичайно погано індексує. (Треба і з цим ще помучитись.)
-
Повторювані залежності Yarn зі станом — обережно!
Припустимо, у вас в
package.jsonпідключена інтеграція… ну скажімо@some/coreта@some/angular. Раптом приходить Mend, та каже — в@some/coreвразливість, оновлюємо її! А з@some/angularвсе добре, її залишаємо. Дякуємо Mend, оновлюємо, живемо далі.Проходить час та виявляється, що пакет неініціалізований та інтеграція тихо не працює. Ми потрапили в одну з відомих пасток Yarn. Принаймні, відомих після першого такого випадку — в мене, на жаль, навіть вже не перший.
Річ у тім, що в кожного пакета (та в нашого застосунку) свої вимоги до версій залежностей. Та якщо вони не збігаються, Yarn надає кожному пакету власні екземпляри залежності потрібної версії. Тобто застосунок отримує оновлену версію
@some/core@1.2та@some/angular@1.1, а останній, якщо в нього, як часто буває, жорстко задані версії внутрішніх залежностей, в собі може приховувати@some/core@1.1.В простому випадку це проблема тільки для розміру збірки. (А хто на нього дивиться?) Але, якщо залежність має внутрішній стан, то ми тепер маємо два екземпляри цього стану. Бо кожна версія — це абсолютно окремий пакет! Також нас очікує халепа, якщо пакети містять ООП та перевіряють сумісність прототипів — бо прототипи теж будуть у двох екземплярах. В таке я колись вляпався із
pouchdb.А тепер, що з цим робити? Знайшов плагін для Webpack - DuplicatePackageCheckerPlugin. Він принаймні повідомить, або навіть видасть помилку. Попереджаю, що у вас гарантовано є дублікати, та майже гарантовано від всіх не позбутися — але проблемні (чи навпаки — безпроблемні?) можна ігнорувати.
Також корисною буде команда
yarn dedupe- вона намагається оптимізувати залежності, якщо вони роз`їхалися з часом. Але суттєво вона ситуацію не змінить без корекції версій.Інша корисна команда -
yarn why -R packagename- покаже, чому транзитивна залежність є у вашому застосунку. (-Rце як раз для транзитивних, щоб дивитися глибше одного рівня.) Я нею користуюся ще й коли хочу зрозуміти, чи можна швидко позбавитися якогось негарного пакета.Але головне, що ви тепер знаєте, що таке буває, та роздвоєння особистості в пакета не стане сюрпризом.
-
Опорні конструкції Ruby on Rails
🛤️ Якщо ти ніколи не працював з Ruby on Rails, а тільки дивився, то я гадаю, головні переваги просто залишаться непоміченими. Зокрема, тому, що в Rails безліч зручностей, яких немає в інших середовищах. Ну це як коли несподівано дізнаєшся, що вода з-під крана питна та смачна, та більше не треба думати, де її взяти — цілий шар проблем відпадає.
До мови Ruby можна ставитися по-всякому. Я, як 4 роки тому любив, так досі й люблю. Як і Ruby on Rails. На своєму місці, звісно. Але тоді я писав тільки про якості мови та архітектури застосунку. Які, дійсно, мають власну нішу.
Проте що я залишив неоціненим, це як багато Ruby on Rails допомагає в розробці.
Міграції для бази, рідною мовою та з вбудованою можливістю відкотити, зберегти схему, відтворити будь-де. Конфігурація оточення для розробки, тестів, продакшна. Інтерактивна консоль, де відразу доступні всі можливості застосунку. Генератори коду під все що завгодно. Це все — вже в базовому комплекті.
Також філософія Rails в тому, щоб надати надійну основу для будь-якої задачі. Тому поступово зʼявлялися модулі для: секретів, відкладених задач, завантаження файлів, вебсокетів, тексту з форматуванням. А такі речі, як компіляція JS/CSS, надсилання пошти чи інтернаціоналізація, були “з коробки” ще 15 років тому. Нещодавно зʼявилося офіційне рішення для розгортування через Docker.
Думаю, найпростіше з усім цим можна ознайомитися з Rails Guides.
Якщо ви знаєте фреймворки, де все так само гладко, як у Rails, дуже прошу поділитися, бо я б залюбки мав вибір. Поки доводиться тільки помічати, як всього цього не вистачає, коли його немає.
-
Карі
🥘 Є така приправа — карі. її всі знають. (В Берліні варять сосиску, ріжуть на шматочки, посипають карі та кетчупом — називається карівурст.)
А ще є листя дерева карі, яке використовується в деяких карі. А ще є цмин італійський — який теж звуть “кущ карі”.
Між цими трьома назвами немає прямого звʼязку. Бо в першооснові карі - це страва, родом з Індії, яка фактично та буквально є підливою (зазвичай — для рису.) Та якщо замість чужого “карі” казати рідне “підлива”, то відразу зрозуміло, що є “приправа для підливи”, а є “листя для підливи”, та це такі ж різні речі, як спеції для плову та лаврушка. (Ну, цмин в цю систему не вписується, бо його назвали на честь суміші спецій за пряний аромат. Росте він в середземноморському регіоні та в карі його не кладуть.)
(Ще є японський карі, він же ж каре райсу, який насправді є стравою британського флоту. Тому не містить жодного японського інгредієнта, та й навіть індійського теж, а натомість цілком почувається вдома в європейській кухні.)
Але я взагалі не про те. Є така операція — каррінг, а англійською -
currying. Це коли ви функцію багатьох аргументів розкладаєте у вкладені функції одного аргументу. А в практичному сенсі, зазвичай, навпаки — відкусюєте від великої функції декілька аргументів:function queryDB(db, query) {} const query = _.partial(queryDB, myDB); // queryDB(db)(query) query("SELECT foo");У функціональному стилі програмування через каррінг можна спрощувати код та приховувати деталі реалізації — схоже до інкапсуляції в ООП. Та для того є гарна порада — аргументи функцій завжди впорядковувати від найбільш сталих до найбільш змінних.
А що спільного в каррінга зі смачною підливкою? Аж нічого! Бо названа ця операція на честь математика Гаскелла Каррі, так само як і мова Haskell.
-
Середовища для тестів та для розробки
От вже не знаю, наскільки це поширено, але в Rails в тебе локально завжди є два екземпляри середовища. Одне — для розробки -
development- містить сталі дані. Друге — для тестів -test- витирається начисто на кожний запуск тестів. В “середовище” завжди входить база даних, але також може бути кеш, набір файлів тощо. Ну, та ще й належна конфігурація.Це взагалі дуже корисна ідея, та якщо у вас такого нема, раджу спробувати. Хоча не завжди легко й одне середовище зробити, не те що копію. Багато залежить від того, наскільки у вас параметризований код.
Але я навіть не про те хотів сказати. Я, з подачі колеги, ось що згадав: інструменти для налаштування тестового середовища значно зручніше, ніж робочого. Причина проста — тестове середовище ми відтворюємо багато-багато разів, а робоче… ну може коли новий працівник приходить, чи після якоїсь катастрофи. Гарно, коли для розробки є скрипт (в Rails це
seeds.rb), але за досвідом він майже завжди застарілий, бо його майже не використовують. Та й локальне оточення буде застарілим, якщо єдиний спосіб його оновити — це все перестворити наново.Тому я вже давно, якщо потрібно зробити ілюстрацію якогось стану в застосунку, відтворюю той стан в тестах. Або просто роблю знімок екрана прямо з тесту, який вже написав. Це швидко, а головне, я можу відтворити стан скільки завгодно разів.
А якщо піти далі (що й пропонував колега), то можна запустити інтеграційні тести з відкритим браузером, а посередині поставити на паузу та далі взаємодіяти із застосунком в потрібному стані.
Все це наводить на думку… може до середовища для розробки треба ставитись схоже? Не підтримувати повністю готове оточення, а мати інструменти для його створення — ті ж фабрики, як в тестах. Або додумати підхід із використанням тестів як трампліну для ручної перевірки. Цікаво.
-
Цикли ефективності
Сьогодні в моменті ясності розуму збагнув, що моє життя ходить по колу. Це коло ставлення до життєвих справ, яке фактично задає тон всьому іншому.
🐹 Режим хомʼячка — це коли робиш те, що спадає в очі. Згадав — зробив, не згадав… не зробив. Звісно, здебільшого робиться гостре. А також багато цікавого; особливо цікаво починати нові справи, а не закінчувати старі нудні. В такому режимі можна теж робити круті речі, але рідко ті, що не вимагають від себе уваги.
🧨 Накопичення роботи — зазвичай з таким ставленням стає дедалі більше справ. До того ж справи, які були важливі, але не термінові, не робляться, та згодом стають терміновими. Поступово “режим хомʼячка” створює більше та більше стресу.
💥 Криза — тиск росте, росте, та нарешті просто вимагає організуватися. На щастя, я знаю, як це робити, бо досвід є. (Власне, з першої кризи та першого пошуку рішення все й почалося.)
🧹 Організація — задачі складаються до купи, розставляються пріоритети, відбувається належне планування, та нарешті стає зрозуміло, що куди. На початку це страшно та неприємно робити, зате як почнеш, то просто найкраще відчуття на світі — бачити ясність після багнюки в голові.
🌸 Ефективність — з налагодженою системою робити прогрес стає легко! Навіть ті проєкти, на які хом’ячок дивився як на фантазії, піддаються аналізу та поступовому прогресу. А ти згадуєш ще більше цілей. (Ідеї люблять порожню голову!)
🌊 Забагато зайвого — з такою легкістю можна не помітити та взяти на себе надто багато задач. Ба більше, мені завжди вдається знайти забагато неважливих задач. Та система, що чудово розвʼязала кризу з десяти складних проєктів, захлинається в сотні забаганок.
😵 Та все починається наново — система розвалюється, та я знову починаю робити те, що горить або цікавить. До нової кризи…
Гадаю, дуже цінно знайти свої цикли, бо тоді можна бачити майбутнє та навіть виправляти його. Наприклад, збираю зараз “ящик інструментів” - перелік підходів, які допомагають в організації. Якщо ще й нагадати собі про цей “ящик” запланованим нагадуванням, можна вийти з кризи скоріше. А якщо знати, що забагато задач ведуть до захлинання, можна свідомо та впевнено їм відмовляти, та довше залишатися “на хвилі”. 🏄