Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
08.09.2025
Магія та абстракції
У продовження теми Low Magic vs High Magic.
Хороша абстракція — це коли сутності, з якими ти працюєш, виглядають цілісними та складаються в систему з міцною логікою. В цілому, у світі безліч хороших абстракцій, які вірно нам служать, та й у програмуванні теж. Зокрема, рідко хто замислюється, як там працює процесор — а при тому кожна програма втілюється через внутрішню архітектуру процесора. Та ще глибше, електричні потенціали та струм.
На жаль, пошук хорошої абстракції — важка праця. Та ще й така, що не приносить прямої вигоди. Тому ми щоденно стикаємося з поганими абстракціями — для використання них треба розуміти й подробиці нижчого рівня, про які нам взагалі-то й не цікаво знати.
На “допомогу” тут приходить магія (“it just works”). Магія просто приховує всі подробиці “за лаштунки”, де ми їх не можемо побачити. До певної міри це допомагає, бо поки ти працюєш в дозволених межах, “магічна” абстракція може бути дуже красивою. Але як тільки потрібно робити щось незвичне, магія розходиться по швах і доведеться вчити, як воно там всередині влаштоване.
Різниця між магією та абстракцією в тому, що хороша абстракція робить заглиблення в подробиці не потрібним, а магія робить його прихованим та складним. В цілому, одне не виключає іншого, але я завжди прагну першого варіанту — коли подробиці легко доступні. Наприклад, в Go завжди можна відстежити весь шлях виконання, це просто антимагічна мова.
З пришестям мовних машин зʼявився ще третій варіант — найпростіший — це замість абстракції плюватися кодом нижчого рівня. Аби працювало. Перевага очевидна — це швидко. Як я казав, від гарних абстракцій вигода зʼявляється значно пізніше. Оце й бентежить, бо я не хочу увʼязати в коді.
А реалії такі, що код як шльопався, так і буде шльопатися далі без серйозних задумів, тому останнім часом взагалі здається, що це ставлення до коду як до цінності — тимчасове та минуле обмеження за браком ресурсів.. Але час покаже.
06.09.2025
Turning Pro
Сьогодні в мене для вас новини про канал. Нарешті починаю ставитися більш серйозно. Бо, розумієте, до написання статей я завжди ставився серйозно, а до публікації та маркетингу — ні. Через цей дисонанс багато стресу та менше натхнення. Так що, оскільки писати статті гірше я не збираюся, доведеться дотягнути продуктову сторону до відповідного рівня.
Публікувати в цей канал я тепер буду 5 днів на тиждень. Та, скоріше, тематика звужується до більш (чи лише) професійної. Над цим поки думаю.
В мене зʼявляється підписка через Patreon та закритий сервер Discord. Туди переноситься публікація на вихідних. Також планую замість постів робити щось цікавіше як для мене, так і для вас.
(Чому Patreon та Discord? Це доволі типова комбінація, тож довго над цим не думав. Я хотів МоноБазу, але вона змушує привʼязати публікацію до одного з кількох соціальних майданчиків, чого я робити не хочу. Та й інтеграції з Discord там немає. Та й API теж.)
Все це не так легко та швидко налаштувати, як воно мені здавалося. Одна справа — вирішити підняти Патреон, зовсім інша — все в ньому наповнити та продумати.
Такий поки проєкт на вихідний день. :) А тут побачимось в понеділок.
05.09.2025
Світ на тобі не закінчується
Значить, є проблема. З якоїсь причини пару годин був суттєво уповільнений один HTTP сервіс. Треба знайти, чому.
Ускладнення в тому, що це уповільнення ні з чим більше не корелює. Запитів більше не стало. Кількість оброблених байтів теж. Процесор в нормі. Памʼять в нормі. Але щось змусило сервіс відповідати повільно.
Єдине, що хоч трохи вказувало на причину - Sentry підказав, що час витрачається на читання запитів від клієнта. Цікаво! (Інструментація дала дивіденди.) За моїм досвідом, повільне читання завжди пояснюється повільним клієнтом. Може, в нього погане підключення. А може, це цілеспрямована атака.
Ну й давай шукати — якщо це атака — то хто її провів? З яких адрес? Вже ріжу статистику і так, і сяк, а нічого ясного не бачу. Та й взагалі, якщо атака дійсно уповільнила запити для всіх клієнтів, то яким способом? Та чому це не корелює більше ні з якими метриками?
Поки шукав правду в статистиці, спала на думку інша ідея. Вхідні запити до нас обслуговуються ще й сервісом переднього краю, та власне, інфраструктурою провайдера. Тож, думаю, перевірю, чи не було у них інцидентів. Так, про всяк випадок.
Та яке ж було моє здивування, що інцидент був! Та рівно на той час, який ми бачили в метриці. Та складався рівно з того, що мережа була перенавантажена. А значить, запити йшли до нас надто повільно. А ми вже обробляли їх як тільки, так відразу.
Отак дивишся в дані, будуєш собі якісь теорії, а потім за один момент бац — і все зрозуміло.
Гарних вам вихідних без інцидентів!
04.09.2025
Продумай до кінця (чи до початку?)
Одна з цікавих ідей GTD: будь-яка справа ділиться на два етапи: думати та робити. Робити, коли все ясно — достатньо просто (може, не легко.) Мабуть, кожний може згадати час, коли нехай важка, але абсолютно визначена робота була навіть в задоволення. Але в нормі не все ясно. Та ми, замість того, щоб подумати та прояснити, думаємо про справи, та ухиляємося від невизначеності.
Щоб полегшити собі життя, можна продумати справи, а не думати про справи. Це значить — поставити та знайти відповіді на всі запитання потрібні… уважно: для того, щоб зробити наступну дію. Продумати все до кінця під час не те що непрактично, а взагалі неможливо.
Питань виникає багато, деякі очевидні, деякі несподівані. Наприклад: щоб “шукати нові шини” треба хоча б знайти потрібний розмір. А може поговорити з дружиною про бюджет. А може взагалі вони ще не потрібні? Як це перевірити? А який виробник гарний? І так далі. Поки не будеш точно знати — що далі робити.
(До речі, це відчуття оманливе, та його в собі ще потрібно виплекати. Як легкий показник — якщо щось не робиться певний час — спитай себе: чи є обʼєктивні причини відкладати, або “руки не доходять”? В другому разі шукай невизначеність.)
(Ще один гарний критерій - чи можу я доручити цю дію комусь іншому? Якщо ні - значить, не все ще вирішено.)
Звісно, щоб не ходити по колу, всі ці відповіді варто записувати хоч кудись. Тоді буде зʼявлятися визначеність та впевненість. Навіть без всякого GTD можна завести звичку брати аркуш (чи текстовий документ) та проводити інтервʼю сам з собою. А ще й заготувати список питань заздалегідь.
В програмуванні такий підхід особливо потрібний, бо в нас “думати” та “робити” тісно повʼязані. Можна сказати, що на кожному рядку доводиться щось вирішувати. Але в таких дрібних рішеннях немає проблеми. Зате варто продумати: спочатку перелік вимог, потім з нього — загальну архітектуру, потім — план виконання. І на кожному з цих етапів будуть виникати питання, які здатні повністю загальмувати наївну розробку в лоб без продумування. Без цього сидиш тиждень, дивишся в порожній екран та не можеш написати ані рядка. Проблема не в натхненні — а в невизначеності на одному з рівнів. Знайомо?
03.09.2025
Помічник ШІ для прорізного аналізу проєкту
Знайшов вигідну галузь використання агентів (в моєму випадку це все ще Cursor.) Стосується це в першу чергу вебпроєктів, в яких, як знаємо, використовується цілий ворох технологій та мов: Ruby для бекенду, JS для фронтенду, CSS для оформлення, SQL для запитів. Поясню на прикладах.
Є інтеграційний тест — мені не знайомий. Він валиться на натисканні такої-то кнопки. Чому? Зазвичай тут доведеться майстерно грепати, та ще й знати звʼязки між шарами. Натомість виділяю рядок, тисну Cmd+L
та задаю запит: describe code flows that are triggered by this action
. Cursor знаходить в коді React цю кнопку — навіть якщо вона називається “Continue”, то він бачить її в контексті всього тесту. Сила LLM - в розумінні синонімів. Навіть коли тест називається “Profile”, а компонент - Account
, LLM знайде його проміж інших. Звідти описує, що саме робить ця кнопка, які запити на бекенд, і далі розкладає по шарах бекенду, навіть до асинхронних задач. Це надзвичайно потужна можливість для такої доступності.
Або, бачу по CSS що ми використовуємо шрифт, але не можу зрозуміти, де саме. Замість того щоб відстежувати по кроках від CSS - до компонента, від компонента - до ієрархії, і так далі, питаю: which feature spec would load a page that uses the FooBar font?
І так само отримую опис сценаріїв, де шрифт може бути залучений, та навіть конкретний приклад з тестів. Знову, це заміняє пів години ретельного пошуку та відкидання зайвого.
Так само можна робити з попередженнями в JS, які незрозуміло звідки зʼявляються в консолі. Або й в журналі з бекенду. І зовсім не обовʼязково для LLM потрібний прямий збіг за текстом.
Я взагалі завжди пишався своєю власною здатністю відстежити такі звʼязки, але ось в чому різниця. Коли я вручну за годину чи пів знаходжу причину помилки, то зможу виправити тільки найбільш гострі з них. А тепер вистачає можливостей ще й на деякі неприємні, але терпимі ситуації - як-от випадково невдалі тести.
02.09.2025
Легкий спосіб пришвидшити збірку у 2 рази
Взагалі знаєте, що є неформальне правило, що збірка коду на CI повинна тривати менше за 10 хвилин? Тоді в розробника менше шансів втратити контекст, поки тести йдуть.
Так, певного успіху можна досягнути тим, щоб зробити збірку паралельною, та це точно варто зробити в першу чергу. Втім, з часом паралельність втрачає дію: коли підготовчі дії займають вагому долю. Тож потім починається важка кропітка робота з прискорення цих дій: десь кеш, десь прибирання зайвого. Все це, на жаль, дає мізерні покращення.
Є й інший шлях: виправляти випадково невдалі тести. Бо дійсно, якщо збірка впала через випадкову невдачу — доведеться запускати її двічі та витрачати у два рази більше часу. А якщо друга спроба теж невдала — то й третій і четвертий раз. Виходить, випадково невдалі тести бʼють по продуктивності значно більше, ніж просто повільна збірка, особливо якщо збірку доводиться перезапускати вручну.
Я тієї думки, що випадково невдалі тести — як і помилки, завжди зʼявляються. Тому й боротьба з ними має системний, а не разовий характер. Як перший крок, почни записувати ті тести, що не вдалися з першої спроби. Тут доведеться відстежувати історію збірок. А можна поступити простіше та записувати всі тести, що не вдалися в головній гілці, бо там вже не повинно бути недоробок. (Правда?)
Є також рішення типу rspec-retry - перезапускати невдалі тести відразу один по одному, на випадок що вони пройдуть. Так, це певна поступка чистоті підходів, але я гадаю, варта того. Заодно можна відразу збирати список тестів, які пройшли з другої спроби.
01.09.2025
Підключення миші з Bluetooth до Mac... без миші!
Маленьке продовження історії з Mac Mini. Взяв з собою стару мишу, щоб не відʼєднувати свою основну. Та тут виявляється, що вона не спарена! А іншої миші немає. Що робити?
Я надіявся, що все працюватиме з клавіатури. Відкрити системні налаштування через Alfred тривіально. Далі намагаюся клавішею Tab
добратися до налаштувань Bluetooth… а вона не табулює! Точніше, табулює, але тільки між пошуком та переліком розділів. А у сам розділ, в головну панель не входить.
Я трохи часу побув в шоці. Спробував знайти команди термінала для Bluetooth, але не вийшло. Згадав, що є така штучка, як Mouse Keys
- керування мишею з клавіатури — але ж щоб її увімкнути треба так само зайти в налаштування. Хотів залучити AppleScript - та виявилося, що навіть у стандартному діалозі “Дозволити програмі керувати…” табуляція теж не працює!
А потім розшукав, що за замовчуванням на macOS табуляція не проходить по всіх елементах інтерфейсу, а тільки по деякім. На то є налаштування Keyboard navigation
… яке, о диво, можливо змінити через термінал! defaults write NSGlobalDomain AppleKeyboardUIMode -int 3
та нарешті я зміг дістатися до потрібних налаштувань та підʼєднати мишу.
Тепер раджу всім перевірити, що у вас цей режим увімкнений, бо хтозна-коли він може знадобитися.
(PS: а ще можна підʼєднатися до Mac Mini вже налаштованим віддаленим доступом з ноутбука чи навіть телефону та тамтешнею мишею зробити все необхідне. Але це вихід для слабаків.)
(PPS networksetup -setairportnetwork en1 SSID PASSWORD
, щоб з термінала підʼєднатися до незнайомої мережі. Хоч тут все прямолінійно!)
31.08.2025
Проблеми з i18next
Вчора в підтримку Сінтри надійшов лист про те, що у нас поламані листи підтвердження підписки. По-перше, дякую автору, бо небагато хто б потрудився це зробити. По-друге, чи це ще один випадок недостачі моніторингу та насамперед тестування в проєкті двох людей? Авжеж! Бо помилка сиділа роки з два. (І ні, це далеко не перша підписка за цей час.)
Помилка крилася в неправильному шаблоні для i18next. В нас там цікаве рішення для шаблонів листів. Технічно вони використовують “Handlebars”, але на практиці шаблони стають просто рядками перекладу для i18next.
Причин було відразу дві. Одна прямо зовсім проста — неправильний префікс ключа. Знаєте, в i18next можна написати підписка на $t(subscription_interval.{{interval}})
та підставити туди, наприклад, subscription_interval.monthly
. Дуже зручно. Але на це немає перевірок — якщо такого ключа немає в словнику, i18next підставить сам ключ. Що й відбулося, бо я банально переплутав subscription_interval
та subscription_period
.
Зате рівно тільки що дізнався, що є налаштування відсутніх ключів — в тому числі обробник missingKeyHandler
, який міг би кинути помилку.
Друга причина — в моєму форматувальнику дат формат читається з того ж словнику. Дуже зручно (та може варто окремої розповіді.) Але. Словник же ж потребує обраної мови! На фронтенді це працює зрозуміло: у нас є мова користувача, вона встановлена глобально. А на бекенді немає “глобально обраної мови”, бо бекенд надсилає пошту до всіх користувачів. Тому виходило так, що в пошті цей форматувальник не знав мови, використовував хибний формат (знову без помилки…) та видавав сміття.
Розвʼязалося все передачею мови параметром lng
: format = i18next.t(format_name, {lng: userLanguage})
.
Отак, наче нічого складного, але години дві витратив. До речі, дуже допомогло те, що в мене принаймні були скрипти для генерації тих листів.
30.08.2025
Сучасна спадщина Thief
Після минулого поста про Thief я загадався над питанням — чи є сучасні ігри, які її нагадують? Виявилося, що жанр thieflike практично відсутній. Зокрема виділю, що сучасні ігри дають гравцю або високу мобільність, або можливість битися. А весь смак Thief - в обмеженні й того, й іншого. Та рідко хто відтворює механіки як світла, так і звуку на такому ж рівні, як будь-який Thief.
Зате! (А може — через це!) Як виявилося, фанатська сцена для Thief жива й досі. Причому як мінімум у двох формах.
Є The Dark Mod - це фактично повна реалізація механік Thief на рушії Doom 3 (або id Tech 4, якщо педантично.) З відкритим кодом та абсолютно безплатна! Причому The Dark Mod знаходиться в активній розробці, остання версія вийшла березня цього року. Замість звичайного сюжету тут є можливість завантажувати місії з каталогу, де їх наразі майже 200.
Не впевнений, що без досвіду Thief воно гладко зайде концептуально, але в цілому The Dark Mod, мабуть, найкращий спосіб пограти “в сучасний Thief” з усіма механіками та сучасною графікою.
Але насправді мені в старих іграх заважає керування та зручності, а не власне графіка. Зі старою графікою я готовий жити. Та якщо тобі так само, то для Thief 1 є чудовий мод TFix, який прибирає всі вікові недоліки. Там і рушій оновлений, і широкий екран підтримується, і з керуванням проблем немає. (Аналогічно для Thief 2 є T2Fix. Це взагалі дві дуже схожі гри.)
Та що ще крутіше — на Thief 1 та 2 існує досі жива спільнота фанатських місій! Їхня кількість вражає — на Archive.org є архів у 40 Гб рівнів, що у 20 разів більше за розмір обох оригінальних ігор! Та з TFix їх достатньо закинути в одну теку та запускати спеціальною оболонкою. Взагалі найкращий ресурс для фанатських місій - це форум Through The Looking Glass, але він щось не дуже стабільно працює.
Виходить, Thief приєднується до родини старих ігор, що живі завдяки фанатам — як Quake чи Doom. Дуже приємно.
29.08.2025
Раптові витрати памʼяті та куди дивитися
Довелося цього тижня зʼясовувати, чому застосунок на Rails несподівано почав споживати на кілька гігабайтів памʼяті більше. Поверхнево причина була зрозуміла — бо в цей застосунок зайшла частина іншого застосунку, відбувається таке собі злиття. Та тут й крилася проблема: в проблемний реліз потрапила ціла пачка наче безпечних змін — загальним обсягом десь у 20 тисяч рядків. Ну й що з нею робити?
Перша теорія — що це сам код витрачає стільки памʼяті — мені відразу здалася неправдоподібною. Код взагалі не дуже споживає памʼять, навіть в інтерпретованій мові. Продукти виконання коду, тобто стан — можуть. Але тоді стає питання — де той стан зберігається, бо зазвичай у вебзастосунку весь стан відкидається по закінченню запиту.
Почав перевіряти нові константи та класові змінні — ну, знайшов кілька сотень регулярних виразів. Зробив швиденько скрипт, щоб просто завантажити ці вирази та подивитися витрати памʼяті — а вони мінімальні. Ну і якщо чесно, то прямо гігабайти стану це треба ще вигадати, ніхто такого в Ruby не робить. Втім, треба ж шукати причину…
А варто було з усіх змін відразу подивитися на зміни в залежностях. А тут — в проєкт підтягнувся славетний гем mini_racer - обгортка рушія V8 для Ruby. Відчуваєте вагу? V8 це вам не купка регулярок!
Отже, виявилося, що з деяких причин версія mini_racer
була старуватою, та мала проблеми із Ruby 3.4, або може з свіжим сервером Puma - в будь-якому разі, щось заважало цьому гему звільняти старі контексти V8, та вони й засмічували памʼять. Оновленням гему все розвʼязалося.
У таких випадках раптових змін завжди є конкретна причина, залишається її знайти.