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

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

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

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!


15.05.2025

Що вміє Apple Reminders

Останній рік я широко використовую Reminders. Поділюся надбаннями. Щоправда, для зберігання задач він мені не зайшов. Коли я почав працювати з ним активно — певно, головне, що багато редагувати задачі, а не тільки додавати та видаляти — то синхронізація з телефоном роз`їхалася. З таким застереженням давайте далі.

Якщо ти ніколи на них не дивився, то - Reminders зараз досить просунутий застосунок. В ньому є і багато списків, навіть теки для них. Є “розумні” списки — тобто пошук за критеріями. Є нагадування за часом та локацією. Можливість додати нотатки та навіть зображення. URL в нотатках автоматично стають “підкресленими” - дрібниця, але зручно.

Reminders гарні насамперед тим, що вони вбудовані, а значить, навколо них є ціла екосистема застосунків. Від календарних програм до iStat Menus. Та й цілий фреймворк EventKit є.

(До речі: подумав — а чому б не зробити CLI для цього фреймворку? Та дійсно, вже хтось зробив reminders-cli! Дуже круто, але є нюанс: в моделі безпеки Apple не можна надати право доступу до чогось консольному застосунку. Тільки всьому терміналу. Хотілося б дослідити пізніше, але поки не впевнений, що я хочу відкривати настільки широкі ворота).

Проте для мене головне, що Reminders найзручніші для Siri. Тому в них найлегше додавати думки на ходу. Тут порада: Add XXX to Reminders працює дослівно, а Remind me about XXX інтерпретує фразу. Інколи я додаю просто думки чи нагадування, які потім обробляю в кінці дня. Для того корисна дослівна форма. Але також можна відразу додати нагадування на час, причому як at 5 PM, так і in 30 minutes працює, та робить мене в 9 разів пунктуальнішим.

Це були умовно “нагадування на сьогодні”. В мене ще є “нагадування на майбутнє”, більш стабільні, тому для них окремий список — щоб не плутати. Здебільшого вони повторювані, і це ще одна гарна фіча. Повторення тут максимально потужне, я поки не стикався з неможливим графіком. Наприклад, можна зробити “останній робочий день місяця”. А можна й “кожні 6 тижнів”. Мої довготривалі розклади наразі живуть тут. (Є й нагадування на майбутнє, але без повторень.)

А ще можна зробити нагадування по локації. Я так для себе розвʼязав проблему, що на СТО вимикають фари. Нормально вони в мене в авторежимі, тому про вимкнені я зазвичай дізнавався вже коли опинюся на першій темній дорозі, бо вночі під ліхтарями не помічаю. Отже, зробив нагадування коли залишаю локацію СТО, працює бездоганно (та повторюється само, аж поки не поставиш галочку.)

Та останній режим використання — як прості списки з галочкою. Дуже обмежено, щоб не загубилося в інших системах, але й не розрослося саме. Є традиційний список покупок, є списки для окремих магазинів чи навіть інтернет-магазинів. Є “що приготувати”, “що подивитись” та “цікаві ресторани”. Такий список можна зробити спільним.

Для мене Reminders закривають ту маленьку нішу, де звичайний список в Obsidian не впорається: бо потрібні нагадування, потрібна взаємодія з системою тощо. Це чудова проста програма для такої потреби.


14.05.2025

Чому копіпаста — це погано?

Метою написання коду завжди є те, щоб він був зрозумілим. З нуля ми пишемо код один раз, а дописуємо — всю решту часу. Незрозумілий код — це не краще, ніж невірний (бо, по-перше, звідки ти знаєш, що він вірний?) Отже, всякий код повинен насамперед легко читатись, а потім вже все інше.

Копіпаста (а саме, скопійований багато разів код) погана не тільки тим, що вона повторюється. Тоді можна було б її більш-менш ігнорувати. Значно гірше, що копіпаста рідко повторюється дослівно, та ми ніколи не знаємо, чи це так. Доводиться серед нудних повторюваних рядків “знайти 10 відмінностей”. Або — скоріше — просто проґавити.

З появою ШІ це тільки стало гостріше, бо ШІ, на відміну від людини, обробить весь текст з рівною увагою, а значить, найважливішими стануть ті рядки, що повторюються, хоча повинно бути навпаки. ШІ взагалі майстер “згладити нерівності”. А з іншого боку, ШІ залюбки згенерує вам скільки завгодно копіпасти, створивши собі ж пастку на майбутнє. Цьому треба активно запобігати.

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


13.05.2025

Вкладки браузера — в 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 вузла через хеш змісту. Це я попросив зробити, але функцію воно нагенерувало само. Нічого бібліотечного готового немає. Вирішив, нехай залишається.

Знову, таку роботу я б сам не зробив, бо часу немає. Забрати скрипт можна тут.