Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
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 вузла через хеш змісту. Це я попросив зробити, але функцію воно нагенерувало само. Нічого бібліотечного готового немає. Вирішив, нехай залишається.
Знову, таку роботу я б сам не зробив, бо часу немає. Забрати скрипт можна тут.
12.05.2025
Міграція інтеграційних тестів: погана задача для ШІ
Передісторія. Десь у 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 відпрацьовує швидше (чи не чекає чогось?) та перевірка випереджує стан. Тут ШІ робив якісь незрозумілі кроки. Зокрема - спрощував тести, щоб ті проходили. Тобто з масовим виправленням без людського втручання точно успіху немає.
Якщо підсумувати, то ця міграція, про яку мені навіть казали “так може ШІ це швидко зробить?”, так і залишилась великою та складною задачею.